Repository: clovaai/TedEval
Branch: master
Commit: f24e003a97dd
Files: 22
Total size: 164.4 KB
Directory structure:
gitextract_ex9hoqm4/
├── .gitignore
├── LICENSE
├── NOTICE
├── README.MD
├── config/
│ └── config.py
├── rrc_evaluation_funcs.py
├── script.py
├── static/
│ ├── funcs.js
│ ├── jquery-mousewheel.js
│ ├── jquery.form-3.51.js
│ ├── ranking.js
│ ├── style.css
│ ├── visualization.css
│ └── visualization_default.js
├── static_custom/
│ ├── contents.txt
│ ├── visualization_TL_iou.css
│ └── visualization_TL_iou.js
├── views/
│ ├── index.tpl
│ ├── method.tpl
│ ├── sample.tpl
│ └── upload.tpl
└── web.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
__pycache__*/
.idea/
output/*
._*
*/._*
.pyc
.DS_Store
*.swp
================================================
FILE: LICENSE
================================================
Copyright (c) 2019-present NAVER Corp.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: NOTICE
================================================
TedEval
Copyright (c) 2019-present NAVER Corp.
This project contains subcomponents with separate copyright notices and license terms.
Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses.
=====================
TedEval solves the drawbacks of previous metrics such as IoU and DetEval.
This code is based on ICDAR15 official evaluation code from https://rrc.cvc.uab.es/.
=====================
jquery/jquery from http://jquery.com/
=====================
Copyright jQuery Foundation and other contributors, https://jquery.org/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.
=====================
jquery/jquery-ui from https://github.com/jquery/jquery-ui
=====================
Copyright jQuery Foundation and other contributors, https://jquery.org/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery-ui
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code contained within the demos directory.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.
=====================
malsup/form from https://github.com/malsup/form
=====================
Copyright 2006-2013 (c) M. Alsup
All versions, present and past, of the jQuery Form plugin are dual licensed under the MIT and GPL licenses:
MIT
GPL
You may use either license. The MIT License is recommended for most projects because it is simple and easy to understand and it places almost no restrictions on what you can do with the plugin.
If the GPL suits your project better you are also free to use the plugin under that license.
You don't have to do anything special to choose one license or the other and you don't have to notify anyone which license you are using. You are free to use the jQuery Form Plugin in commercial projects as long as the copyright header is left intact.
-----
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=====
================================================
FILE: README.MD
================================================
# TedEval: A Fair Evaluation Metric for Scene Text Detectors
Official Python 3 implementation of TedEval | [paper](https://arxiv.org/abs/1907.01227) | [slides](https://docs.google.com/presentation/d/1EFK_WjpdLExZVDPt4C7yCcxjpXNvIyAOL9zUnKx1VoY/edit?usp=sharing)
**[Chae Young Lee](mailto:cylee7133@gmail.com), Youngmin Baek, and Hwalsuk Lee.**
Clova AI Research, NAVER Corp.
### Overview
We propose a new evaluation metric for scene text detectors called TedEval. Through separate instance-level matching policy and character-level scoring policy, TedEval solves the drawbacks of previous metrics such as IoU and DetEval. This code is based on [ICDAR15 official evaluation code](http://rrc.cvc.uab.es/).
## Methodology
### 1. Mathcing Policy
- Non-exclusively gathers all possible matches of not only one-to-one but also one-to-many and many-to-one.
- The threshold of both area recall and area precision are set to 0.4.
- Multiline is identified and rejected when _|min(theta, 180 - theta)| > 45_ from Fig. 2.
<p align="center"><img height=200 src=images/multiline.png>
### 2. Scoring Policy
We compute Pseudo Character Center (PCC) from word-level bounding boxes and penalize matches when PCCs are missing or overlapping.
<p align="center"><img height=170 src=images/pcc.png>
### Sample Evaluation
<img src='images/sample.png'>
## Experiments
We evaluated state-of-the-art scene text detectors with TedEval on two benchmark datasets: ICDAR 2013 Focused Scene Text (IC13) and ICDAR 2015 Incidental Scene Text (IC15). Detectors are listed in the order of published dates.
### ICDAR 2013
| Detector | Date (YY/MM/DD) | Recall (%) | Precision (%) | H-mean (%) |
| :-----------------------------------------------------: | :-------------: | :--------: | :-----------: | :--------: |
| [CTPN](https://arxiv.org/pdf/1609.03605.pdf) | 16/09/12 | 82.1 | 92.7 | 87.6 |
| [RRPN](https://arxiv.org/pdf/1703.01086.pdf) | 17/03/03 | 89.0 | 94.2 | 91.6 |
| [SegLink](https://arxiv.org/pdf/1703.06520.pdf) | 17/03/19 | 65.6 | 74.9 | 70.0 |
| [EAST](https://arxiv.org/pdf/1704.03155.pdf) | 17/04/11 | 77.7 | 87.1 | 82.5 |
| [WordSup](https://arxiv.org/pdf/1708.06720) | 17/08/22 | 87.5 | 92.2 | 90.2 |
| [PixelLink](https://arxiv.org/pdf/1801.01315.pdf) | 18/01/04 | 84.0 | 87.2 | 86.1 |
| [FOTS](https://arxiv.org/pdf/1801.01671.pdf) | 18/01/05 | 91.5 | 93.0 | 92.6 |
| [TextBoxes++](https://arxiv.org/pdf/1801.02765.pdf) | 18/01/09 | 87.4 | 92.3 | 90.0 |
| [MaskTextSpotter](https://arxiv.org/pdf/1807.02242.pdf) | 18/07/06 | 90.2 | 95.4 | 92.9 |
| [PMTD](https://arxiv.org/pdf/1903.11800.pdf) | 19/03/28 | 94.0 | 95.2 | 94.7 |
| [CRAFT](https://arxiv.org/pdf/1904.01941.pdf) | 19/04/03 | 93.6 | 96.5 | 95.1 |
### ICDAR 2015
| Detector | Date (YY/MM/DD) | Recall (%) | Precision (%) | H-mean (%) |
| :-----------------------------------------------------: | :-------------: | :--------: | :-----------: | :--------: |
| [CTPN](https://arxiv.org/pdf/1609.03605.pdf) | 16/09/12 | 85.0 | 81.1 | 67.8 |
| [RRPN](https://arxiv.org/pdf/1703.01086.pdf) | 17/03/03 | 79.5 | 85.9 | 82.6 |
| [SegLink](https://arxiv.org/pdf/1703.06520.pdf) | 17/03/19 | 77.1 | 83.9 | 80.6 |
| [EAST](https://arxiv.org/pdf/1704.03155.pdf) | 17/04/11 | 82.5 | 90.0 | 86.3 |
| [WordSup](https://arxiv.org/pdf/1708.06720) | 17/08/22 | 83.2 | 87.1 | 85.2 |
| [PixelLink](https://arxiv.org/pdf/1801.01315.pdf) | 18/01/04 | 85.7 | 86.1 | 86.0 |
| [FOTS](https://arxiv.org/pdf/1801.01671.pdf) | 18/01/05 | 89.0 | 93.4 | 91.2 |
| [TextBoxes++](https://arxiv.org/pdf/1801.02765.pdf) | 18/01/09 | 82.4 | 90.8 | 86.5 |
| [MaskTextSpotter](https://arxiv.org/pdf/1807.02242.pdf) | 18/07/06 | 82.5 | 91.8 | 86.9 |
| [PMTD](https://arxiv.org/pdf/1903.11800.pdf) | 19/03/28 | 89.2 | 92.8 | 91.0 |
| [CRAFT](https://arxiv.org/pdf/1904.01941.pdf) | 19/04/03 | 88.5 | 93.1 | 90.9 |
### Frequency
<img src='images/counts.png'>
## Getting Started
### Clone repository
`git clone https://github.com/clovaai/TedEval.git`
### Requirements
- python 3
- python 3.x Polygon, Bottle, Pillow
```python3
# install
pip3 install Polygon3 bottle Pillow
```
### Supported Annotation Type
- LTRB (xmin, ymin, xmax, ymax)
- QUAD (x1, y1, x2, y2, x3, y3, x4, y4)
## Evaluation
### Prepare data
The ground truth and the result data should be text files, one for each sample. Note that the default naming rule of each text file is that there must be `img_{number}` in the filename and that the number indicate the image sample (this can be changed in `default_evaluation_params()` in `script.py`).
```
# gt/gt_img_38.txt
644,101,932,113,932,168,643,156,concierge@L3
477,138,487,139,488,149,477,148,###
344,131,398,130,398,149,344,149,###
1195,148,1277,138,1277,177,1194,187,###
23,270,128,267,128,282,23,284,###
# result/res_img_38.txt
644,101,932,113,932,168,643,156,{Transcription},{Confidence}
477,138,487,139,488,149,477,148
344,131,398,130,398,149,344,149
1195,148,1277,138,1277,177,1194,187
23,270,128,267,128,282,23,284
```
Compress these text files without the parent directory.
```python3
zip gt.zip gt/*
zip result.zip result/*
```
Refer to `gt/result.zip` and `gt/gt_*.zip` for examples.
### Run stand-alone evaluation
```python3
python script.py –g=gt/gt.zip –s=result/result.zip
```
For evaluation setup, please refer to the following parameter list to edit `default_evaluation_params()` in `script.py`.
### Important Parameters
<!--
### Paramters for evaluation script
| name | type | default | description |
| ---- | ---- | ------- | ---- |
| -g | ```string``` | | path to ground truth zip file |
| -s | ```string``` | | path to result zip file |
| -o | ```string``` | | path to save per-sample result file 'results.zip' | -->
| name | type | default | description |
| ------------------------- | --------- | ------- | ------------------------------------------------------------- |
| AREA_RECALL_CONSTRAINT | `float` | `0.4` | area recall constraint (0 <= R <= 1) |
| AREA_PRECISION_CONSTRAINT | `float` | `0.4` | area precision constraint (0 <= P <= 1) |
| GT_LTRB | `boolean` | `False` | GT file annotation type (True if LTRB, False if QUAD) |
| DET_LTRB | `boolean` | `False` | prediction file annotation type (True if LTRB, False if QUAD) |
| TRANSCRIPTION | `boolean` | `False` | set True if result file has transcription |
| CONFIDENCES | `boolean` | `False` | set True if result file has confidence |
### Run Visualizer
```python3
python web.py
```
- Place the zip file of images and GTs of the dataset named `images.zip` and `gt.zip`, respectively, in the `gt` directory.
- Create an empty directory name `output`. This is where the DB, submission files, and result files will be created.
- You can change the host and port number in the final line of `web.py`.
The file structure should then be:
```
.
├── gt
│ ├── gt.zip
│ └── images.zip
├── output # empty dir
├── script.py
├── web.py
├── README.md
└── ...
```
<img src='images/visualizer.png'>
## Citation
```
@article{lee2019tedeval,
title={TedEval: A Fair Evaluation Metric for Scene Text Detectors},
author={Lee, Chae Young and Baek, Youngmin and Lee, Hwalsuk},
journal={arXiv preprint arXiv:1907.01227},
year={2019}
}
```
## Contact us
We welcome any feedbacks to our metric. Please contact the authors via `{cylee7133, youngmin.baek, hwalsuk.lee}@gmail.com`. In case of code errors, open an issue and we will get to you.
## License
```
Copyright (c) 2019-present NAVER Corp.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```
================================================
FILE: config/config.py
================================================
#!/usr/bin/env python3
#encoding: UTF-8
import json
#Name of the script used for the evalution
evaluation_script = 'script'
#Upload instructions
instructions = """<ul>
<li>A single zip file is expected, containing a set of text files.</li>
<li>No directory structure within the zip file is permitted, just the set of text files.</li>
<li>The containing text files should be named as <strong><em>res_img_#.txt</em></strong>, where <em><strong>#</strong></em> is the number of the corresponding test-set image.</li>
<li>Each text file should contain as many lines as text bounding boxes found. Each line should contain eight comma separated values only. The values should correspond to the coordinates of the four corners of the bounding quadrilateral of the word.</li>
<li>New lines in the text files should be indicated with the windows CR/LF termination.</li>
</ul>
<p>The submitted zip file is automatically checked at the time of submission, and a submission log is presented to the user along with a confirmation of the submission. The checks performed are the following:</p>
<ul>
<li>That the file submitted is a valid zip file, it can be opened and the contents can be extracted.</li>
<li>That the names of the text files contained are correct and the image numbers are within the bounds of the test set.</li>
<li>That each text file contains eight comma separated values per line.</li>
<li>That the coordinates passed are within the bounds of the image and that the coordinates are in clocwise order</li>
</ul>
<p>See here an example of the <a href="http://rrc.cvc.uab.es/files/task1_ch4_sample.zip">expected submission file</a></p>
"""
#Extension of the GT file. gt.[extension]
gt_ext = "zip"
#Acronym for the task. It's used to cache the Images
acronym = "IST-T1"
#Title of the Task
title = "Incidental Scene Text - Task 1 Text Localization TEST DATASET (evaluation:IoU)"
#Custom JavaScript for the visualiztion.
customJS = 'visualization_TL_iou.js'
#Custom CSS for the visualiztion.
customCSS = 'visualization_TL_iou.css'
#Parameters used to show the results of a method and the method's ranking
method_params = json.loads("""{"recall":{"long_name":"Recall","type":"double","order":"","grafic":"1","format":"perc"},"precision":{"long_name":"Precision","type":"double","order":"","grafic":"1","format":"perc"},"hmean":{"long_name":"Hmean","type":"double","order":"desc","grafic":"1","format":"perc"}}""")
#Parameters to show for each sample
sample_params = json.loads("""{"recall":{"long_name":"Recall","type":"double","order":"","grafic":"","format":"perc"},"precision":{"long_name":"Precision","type":"double","order":"","grafic":"","format":"perc"},"hmean":{"long_name":"Hmean","type":"double","order":"desc","grafic":"","format":"perc"}}""")
#Parameters to ask for for each submition
submit_params = json.loads("""{}""")
#Regular expression to get the Sample ID from the image name. ID must be the first capturing group.
image_name_to_id_str = '*([0-9]+)*.(jpg|gif|png)'
================================================
FILE: rrc_evaluation_funcs.py
================================================
#!/usr/bin/env python3
#encoding: UTF-8
import json
import sys;sys.path.append('./')
import zipfile
import re
import sys
import os
import codecs
import importlib
from io import StringIO
def print_help():
sys.stdout.write('Usage: python %s.py -g=<gtFile> -s=<submFile> [-o=<outputFolder> -p=<jsonParams>]' %sys.argv[0])
sys.exit(2)
def load_zip_file_keys(file,fileNameRegExp=''):
"""
Returns an array with the entries of the ZIP file that match with the regular expression.
The key's are the names or the file or the capturing group definied in the fileNameRegExp
"""
try:
archive=zipfile.ZipFile(file, mode='r', allowZip64=True)
except :
raise Exception('Error loading the ZIP archive.')
pairs = []
for name in archive.namelist():
addFile = True
keyName = name
if fileNameRegExp!="":
m = re.match(fileNameRegExp,name)
if m == None:
addFile = False
else:
if len(m.groups())>0:
keyName = m.group(1)
if addFile:
pairs.append( keyName )
return pairs
def load_zip_file(file,fileNameRegExp='',allEntries=False):
"""
Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file.
The key's are the names or the file or the capturing group definied in the fileNameRegExp
allEntries validates that all entries in the ZIP file pass the fileNameRegExp
"""
try:
archive=zipfile.ZipFile(file, mode='r', allowZip64=True)
except :
raise Exception('Error loading the ZIP archive')
pairs = []
for name in archive.namelist():
addFile = True
keyName = name
# if fileNameRegExp!="":
# m = re.match(fileNameRegExp,name)
# if m == None:
# addFile = False
# else:
# if len(m.groups())>0:
# keyName = m.group(1)
keyName = name.replace('gt_', '').replace('res_', '').replace('.txt', '')
if addFile:
pairs.append( [ keyName , archive.read(name)] )
else:
if allEntries:
raise Exception('ZIP entry not valid: %s' %name)
return dict(pairs)
def decode_utf8(raw):
"""
Returns a Unicode object on success, or None on failure
"""
try:
raw = codecs.decode(raw,'utf-8', 'replace')
#extracts BOM if exists
raw = raw.encode('utf8')
if raw.startswith(codecs.BOM_UTF8):
raw = raw.replace(codecs.BOM_UTF8, '', 1)
return raw.decode('utf-8')
except:
return None
def validate_lines_in_file(fileName,file_contents,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
"""
This function validates that all lines of the file calling the Line validation function for each line
"""
utf8File = decode_utf8(file_contents)
if (utf8File is None) :
raise Exception("The file %s is not UTF-8" %fileName)
lines = utf8File.split( "\r\n" if CRLF else "\n" )
for line in lines:
line = line.replace("\r","").replace("\n","")
# if(line != ""):
# try:
# validate_tl_line(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
# except Exception as e:
# raise Exception(("Line in sample not valid. Sample: %s Line: %s Error: %s" %(fileName,line,str(e))).encode('utf-8', 'replace'))
def validate_tl_line(line,LTRB=True,withTranscription=True,withConfidence=True,imWidth=0,imHeight=0):
"""
Validate the format of the line. If the line is not valid an exception will be raised.
If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
Posible values are:
LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
"""
get_tl_line_values(line,LTRB,withTranscription,withConfidence,imWidth,imHeight)
def get_tl_line_values(line,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0):
"""
Validate the format of the line. If the line is not valid an exception will be raised.
If maxWidth and maxHeight are specified, all points must be inside the imgage bounds.
Posible values are:
LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription]
LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription]
Returns values from a textline. Points , [Confidences], [Transcriptions]
"""
confidence = 0.0
transcription = "";
points = []
numPoints = 4;
if LTRB:
numPoints = 4;
if withTranscription and withConfidence:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$',line)
if m == None :
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$',line)
raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence,transcription")
elif withConfidence:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$',line)
if m == None :
raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence")
elif withTranscription:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,(.*)$',line)
if m == None :
raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax,transcription")
else:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,?\s*$',line)
if m == None :
raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax")
xmin = int(m.group(1))
ymin = int(m.group(2))
xmax = int(m.group(3))
ymax = int(m.group(4))
if(xmax<xmin):
raise Exception("Xmax value (%s) not valid (Xmax < Xmin)." %(xmax))
if(ymax<ymin):
raise Exception("Ymax value (%s) not valid (Ymax < Ymin)." %(ymax))
points = [ float(m.group(i)) for i in range(1, (numPoints+1) ) ]
if (imWidth>0 and imHeight>0):
validate_point_inside_bounds(xmin,ymin,imWidth,imHeight);
validate_point_inside_bounds(xmax,ymax,imWidth,imHeight);
else:
numPoints = 8;
if withTranscription and withConfidence:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$',line)
if m == None :
raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence,transcription")
elif withConfidence:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$',line)
if m == None :
raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence")
elif withTranscription:
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,(.*)$',line)
if m == None :
raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,transcription")
else:
if line[-1] == ',' : line = line[:-1]
m = re.match(r'^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*$',line)
if m == None :
raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4")
points = [ float(m.group(i)) for i in range(1, (numPoints+1) ) ]
validate_clockwise_points(points)
if (imWidth>0 and imHeight>0):
validate_point_inside_bounds(points[0],points[1],imWidth,imHeight);
validate_point_inside_bounds(points[2],points[3],imWidth,imHeight);
validate_point_inside_bounds(points[4],points[5],imWidth,imHeight);
validate_point_inside_bounds(points[6],points[7],imWidth,imHeight);
if withConfidence:
try:
confidence = float(m.group(numPoints+1))
except ValueError:
raise Exception("Confidence value must be a float")
if withTranscription:
posTranscription = numPoints + (2 if withConfidence else 1)
transcription = m.group(posTranscription)
m2 = re.match(r'^\s*\"(.*)\"\s*$',transcription)
if m2 != None : #Transcription with double quotes, we extract the value and replace escaped characters
transcription = m2.group(1).replace("\\\\", "\\").replace("\\\"", "\"")
return points,confidence,transcription
def validate_point_inside_bounds(x,y,imWidth,imHeight):
if(x<0 or x>imWidth):
raise Exception("X value (%s) not valid. Image dimensions: (%s,%s)" %(xmin,imWidth,imHeight))
if(y<0 or y>imHeight):
raise Exception("Y value (%s) not valid. Image dimensions: (%s,%s) Sample: %s Line:%s" %(ymin,imWidth,imHeight))
def validate_clockwise_points(points):
"""
Validates that the points that the 4 points that dlimite a polygon are in clockwise order.
"""
if len(points) != 8:
raise Exception("Points list not valid." + str(len(points)))
point = [
[int(points[0]) , int(points[1])],
[int(points[2]) , int(points[3])],
[int(points[4]) , int(points[5])],
[int(points[6]) , int(points[7])]
]
edge = [
( point[1][0] - point[0][0])*( point[1][1] + point[0][1]),
( point[2][0] - point[1][0])*( point[2][1] + point[1][1]),
( point[3][0] - point[2][0])*( point[3][1] + point[2][1]),
( point[0][0] - point[3][0])*( point[0][1] + point[3][1])
]
summatory = edge[0] + edge[1] + edge[2] + edge[3];
# if summatory>0:
# raise Exception("Points are not clockwise. The coordinates of bounding quadrilaterals have to be given in clockwise order. Regarding the correct interpretation of 'clockwise' remember that the image coordinate system used is the standard one, with the image origin at the upper left, the X axis extending to the right and Y axis extending downwards.")
def get_tl_line_values_from_file_contents(content,CRLF=True,LTRB=True,withTranscription=False,withConfidence=False,imWidth=0,imHeight=0,sort_by_confidences=True):
"""
Returns all points, confindences and transcriptions of a file in lists. Valid line formats:
xmin,ymin,xmax,ymax,[confidence],[transcription]
x1,y1,x2,y2,x3,y3,x4,y4,[confidence],[transcription]
"""
pointsList = []
transcriptionsList = []
confidencesList = []
lines = content.split( "\r\n" if CRLF else "\n" )
for line in lines:
line = line.replace("\r","").replace("\n","")
if(line != "") :
points, confidence, transcription = get_tl_line_values(line,LTRB,withTranscription,withConfidence,imWidth,imHeight);
pointsList.append(points)
transcriptionsList.append(transcription)
confidencesList.append(confidence)
if withConfidence and len(confidencesList)>0 and sort_by_confidences:
import numpy as np
sorted_ind = np.argsort(-np.array(confidencesList))
confidencesList = [confidencesList[i] for i in sorted_ind]
pointsList = [pointsList[i] for i in sorted_ind]
transcriptionsList = [transcriptionsList[i] for i in sorted_ind]
return pointsList,confidencesList,transcriptionsList
def main_evaluation(p,default_evaluation_params_fn,validate_data_fn,evaluate_method_fn,show_result=True,per_sample=True):
"""
This process validates a method, evaluates it and if it succed generates a ZIP file with a JSON entry for each sample.
Params:
p: Dictionary of parmeters with the GT/submission locations. If None is passed, the parameters send by the system are used.
default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation
validate_data_fn: points to a method that validates the corrct format of the submission
evaluate_method_fn: points to a function that evaluated the submission and return a Dictionary with the results
"""
if (p == None):
p = dict([s[1:].split('=') for s in sys.argv[1:]])
if(len(sys.argv)<3):
print_help()
evalParams = default_evaluation_params_fn()
if 'p' in p.keys():
evalParams.update( p['p'] if isinstance(p['p'], dict) else json.loads(p['p'][1:-1]) )
resDict={'calculated':True,'Message':'','method':'{}','per_sample':'{}'}
validate_data_fn(p['g'], p['s'], evalParams)
evalData = evaluate_method_fn(p['g'], p['s'], evalParams)
resDict.update(evalData)
if 'o' in p:
if not os.path.exists(p['o']):
os.makedirs(p['o'])
resultsOutputname = p['o'] + '/results.zip'
outZip = zipfile.ZipFile(resultsOutputname, mode='w', allowZip64=True)
del resDict['per_sample']
if 'output_items' in resDict.keys():
del resDict['output_items']
outZip.writestr('method.json',json.dumps(resDict))
if not resDict['calculated']:
if show_result:
sys.stderr.write('Error!\n'+ resDict['Message'])
if 'o' in p:
outZip.close()
return resDict
if 'o' in p:
if per_sample == True:
for k,v in evalData['per_sample'].items():
outZip.writestr( k + '.json',json.dumps(v))
if 'output_items' in evalData.keys():
for k, v in evalData['output_items'].items():
outZip.writestr( k,v)
outZip.close()
if show_result:
sys.stdout.write("Calculated!")
sys.stdout.write(json.dumps(resDict['method']))
return resDict
def main_validation(default_evaluation_params_fn,validate_data_fn):
"""
This process validates a method
Params:
default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation
validate_data_fn: points to a method that validates the corrct format of the submission
"""
p = dict([s[1:].split('=') for s in sys.argv[1:]])
evalParams = default_evaluation_params_fn()
if 'p' in p.keys():
evalParams.update( p['p'] if isinstance(p['p'], dict) else json.loads(p['p'][1:-1]) )
validate_data_fn(p['g'], p['s'], evalParams)
print ('SUCCESS')
sys.exit(0)
================================================
FILE: script.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from collections import namedtuple
import rrc_evaluation_funcs
import importlib
import math
def evaluation_imports():
"""
evaluation_imports: Dictionary ( key = module name , value = alias ) with python modules used in the evaluation.
"""
return {
'Polygon':'plg',
'numpy':'np'
}
def default_evaluation_params():
"""
default_evaluation_params: Default parameters to use for the validation and evaluation.
"""
return {
'AREA_RECALL_CONSTRAINT' : 0.4,
'AREA_PRECISION_CONSTRAINT' :0.4,
'EV_PARAM_IND_CENTER_DIFF_THR': 1,
'GT_SAMPLE_NAME_2_ID':'.*([0-9]+).*',
'DET_SAMPLE_NAME_2_ID':'.*([0-9]+).*',
'GT_LTRB': False, # LTRB: 2points(left,top,right,bottom) or 4 points(x1,y1,x2,y2,x3,y3,x4,y4)
'GT_CRLF': False, # Lines are delimited by Windows CRLF format
'DET_LTRB': False, # LTRB: 2points(left,top,right,bottom) or 4 points(x1,y1,x2,y2,x3,y3,x4,y4)
'DET_CRLF': False, # Lines are delimited by Windows CRLF format
'CONFIDENCES': False, # Detections must include confidence value. AP will be calculated
'TRANSCRIPTION': False, # Does prediction has transcription or not
'PER_SAMPLE_RESULTS': True, # Generate per sample results and produce data for visualization
}
def validate_data(gtFilePath, submFilePath,evaluationParams):
"""
Method validate_data: validates that all files in the results folder are correct (have the correct name contents).
Validates also that there are no missing files in the folder.
If some error detected, the method raises the error
"""
gt = rrc_evaluation_funcs.load_zip_file(gtFilePath,evaluationParams['GT_SAMPLE_NAME_2_ID'])
subm = rrc_evaluation_funcs.load_zip_file(submFilePath,evaluationParams['DET_SAMPLE_NAME_2_ID'],True)
#Validate format of GroundTruth
for k in gt:
rrc_evaluation_funcs.validate_lines_in_file(k,gt[k],evaluationParams['GT_CRLF'],evaluationParams['GT_LTRB'],True)
#Validate format of results
for k in subm:
if (k in gt) == False :
raise Exception("The sample %s not present in GT" %k)
rrc_evaluation_funcs.validate_lines_in_file(k,subm[k],evaluationParams['DET_CRLF'],evaluationParams['DET_LTRB'],evaluationParams['TRANSCRIPTION'],evaluationParams['CONFIDENCES'])
def evaluate_method(gtFilePath, submFilePath, evaluationParams):
"""
Method evaluate_method: evaluate method and returns the results
Results. Dictionary with the following values:
- method (required) Global method metrics. Ex: { 'Precision':0.8,'Recall':0.9 }
- samples (optional) Per sample metrics. Ex: {'sample1' : { 'Precision':0.8,'Recall':0.9 } , 'sample2' : { 'Precision':0.8,'Recall':0.9 }
"""
for module,alias in evaluation_imports().items():
globals()[alias] = importlib.import_module(module)
def polygon_from_points(points):
"""
Returns a Polygon object to use with the Polygon2 class from a list of 8 points: x1,y1,x2,y2,x3,y3,x4,y4
"""
resBoxes=np.empty([1,8],dtype='int32')
resBoxes[0,0]=int(points[0])
resBoxes[0,4]=int(points[1])
resBoxes[0,1]=int(points[2])
resBoxes[0,5]=int(points[3])
resBoxes[0,2]=int(points[4])
resBoxes[0,6]=int(points[5])
resBoxes[0,3]=int(points[6])
resBoxes[0,7]=int(points[7])
pointMat = resBoxes[0].reshape([2,4]).T
return plg.Polygon( pointMat)
def rectangle_to_polygon(rect):
resBoxes=np.empty([1,8],dtype='int32')
resBoxes[0,0]=int(rect.xmin)
resBoxes[0,4]=int(rect.ymin)
resBoxes[0,1]=int(rect.xmax)
resBoxes[0,5]=int(rect.ymin)
resBoxes[0,2]=int(rect.xmax)
resBoxes[0,6]=int(rect.ymax)
resBoxes[0,3]=int(rect.xmin)
resBoxes[0,7]=int(rect.ymax)
pointMat = resBoxes[0].reshape([2,4]).T
return plg.Polygon( pointMat)
def rectangle_to_points(rect):
points = [int(rect.xmin), int(rect.ymax), int(rect.xmax), int(rect.ymax), int(rect.xmax), int(rect.ymin), int(rect.xmin), int(rect.ymin)]
return points
def polygon_to_points(pol):
pointMat = []
for p in pol:
for i in range(len(p)):
pointMat.extend(p[i])
return pointMat
def get_intersection(pD,pG):
pInt = pD & pG
if len(pInt) == 0:
return 0
return pInt.area()
def compute_ap(confList, matchList,numGtCare):
correct = 0
AP = 0
if len(confList)>0:
confList = np.array(confList)
matchList = np.array(matchList)
sorted_ind = np.argsort(-confList)
confList = confList[sorted_ind]
matchList = matchList[sorted_ind]
for n in range(len(confList)):
match = matchList[n]
if match:
correct += 1
AP += float(correct)/(n + 1)
if numGtCare>0:
AP /= numGtCare
return AP
def point_distance(a, b):
distx = math.fabs(a[0] - b[0])
disty = math.fabs(a[1] - b[1])
return math.sqrt(distx * distx + disty * disty)
def diag(points):
diag1 = point_distance((points[0], points[1]), (points[4], points[5]))
diag2 = point_distance((points[2], points[3]), (points[6], points[7]))
return (diag1 + diag2) / 2
def center_distance(p1, p2):
return point_distance(p1.center(), p2.center())
def get_midpoints(p1,p2):
return ((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)
def get_angle_3pt(a, b, c):
"""Counterclockwise angle in degrees by turning from a to c around b
Returns a float between 0.0 and 360.0"""
ang = math.degrees(
math.atan2(c[1]-b[1], c[0]-b[0]) - math.atan2(a[1]-b[1], a[0]-b[0]))
return ang + 360 if ang < 0 else ang
def gtBoxtoChars(num, points):
chars = []
assert len(points) == 8
p1 = get_midpoints([points[0],points[1]], [points[6],points[7]])
p2 = get_midpoints([points[2],points[3]], [points[4],points[5]])
unitx = (p2[0] - p1[0]) / num
unity = (p2[1] - p1[1]) / num
for i in range(num):
x = p1[0] + unitx/2 + unitx * i
y = p1[1] + unity/2 + unity * i
chars.append((x,y))
return chars
def char_fill(detNums, matchMat):
for detNum in detNums:
detPol = detPols[detNum]
for gtNum, gtChars in enumerate(gtCharPoints):
if matchMat[gtNum, detNum] == 1:
for gtCharNum, gtChar in enumerate(gtChars):
if detPol.isInside(gtChar[0], gtChar[1]):
gtCharCounts[gtNum][detNum][gtCharNum] = 1
def one_to_one_match(row, col):
cont = 0
for j in range(len(recallMat[0])):
if recallMat[row,j] >= evaluationParams['AREA_RECALL_CONSTRAINT'] and precisionMat[row,j] >= evaluationParams['AREA_PRECISION_CONSTRAINT'] :
cont = cont +1
if (cont != 1):
return False
cont = 0
for i in range(len(recallMat)):
if recallMat[i,col] >= evaluationParams['AREA_RECALL_CONSTRAINT'] and precisionMat[i,col] >= evaluationParams['AREA_PRECISION_CONSTRAINT'] :
cont = cont +1
if (cont != 1):
return False
if recallMat[row,col] >= evaluationParams['AREA_RECALL_CONSTRAINT'] and precisionMat[row,col] >= evaluationParams['AREA_PRECISION_CONSTRAINT'] :
return True
return False
def one_to_many_match(gtNum):
many_sum = 0
detRects = []
for detNum in range(len(recallMat[0])):
if detNum not in detDontCarePolsNum and gtExcludeMat[gtNum] == 0 and detExcludeMat[detNum] == 0:
if precisionMat[gtNum,detNum] >= evaluationParams['AREA_PRECISION_CONSTRAINT']:
many_sum += recallMat[gtNum,detNum]
detRects.append(detNum)
if many_sum >= evaluationParams['AREA_RECALL_CONSTRAINT'] and len(detRects) >= 2:
pivots = []
for matchDet in detRects:
pD = polygon_from_points(detPolPoints[matchDet])
pivots.append([get_midpoints(pD[0][0], pD[0][3]), pD.center()])
for i in range(len(pivots)):
for k in range(len(pivots)):
if k == i:
continue
angle = get_angle_3pt(pivots[i][0], pivots[k][1], pivots[i][1])
if angle > 180:
angle = 360 - angle
if min(angle, 180 - angle) >= 45:
return False, []
return True, detRects
else:
return False, []
def many_to_one_match(detNum):
many_sum = 0
gtRects = []
for gtNum in range(len(recallMat)):
if gtNum not in gtDontCarePolsNum and gtExcludeMat[gtNum] == 0 and detExcludeMat[detNum] == 0:
if recallMat[gtNum,detNum] >= evaluationParams['AREA_RECALL_CONSTRAINT']:
many_sum += precisionMat[gtNum,detNum]
gtRects.append(gtNum)
if many_sum >= evaluationParams['AREA_PRECISION_CONSTRAINT'] and len(gtRects) >= 2:
pivots = []
for matchGt in gtRects:
pG = gtPols[matchGt]
pivots.append([get_midpoints(pG[0][0], pG[0][3]), pG.center()])
for i in range(len(pivots)):
for k in range(len(pivots)):
if k == i:
continue
angle = get_angle_3pt(pivots[i][0], pivots[k][1], pivots[i][1])
if angle > 180:
angle = 360 - angle
if min(angle, 180 - angle) >= 45:
return False, []
return True, gtRects
else:
return False, []
perSampleMetrics = {}
methodRecallSum = 0
methodPrecisionSum = 0
Rectangle = namedtuple('Rectangle', 'xmin ymin xmax ymax')
gt = rrc_evaluation_funcs.load_zip_file(gtFilePath,evaluationParams['GT_SAMPLE_NAME_2_ID'])
subm = rrc_evaluation_funcs.load_zip_file(submFilePath,evaluationParams['DET_SAMPLE_NAME_2_ID'],True)
numGlobalCareGt = 0;
numGlobalCareDet = 0;
arrGlobalConfidences = [];
arrGlobalMatches = [];
for resFile in gt:
gtFile = rrc_evaluation_funcs.decode_utf8(gt[resFile])
recall = 0
precision = 0
hmean = 0
recallAccum = 0.
precisionAccum = 0.
detMatched = 0
numGtCare = 0
numDetCare = 0
recallMat = np.empty([1,1])
precisionMat = np.empty([1,1])
matchMat = np.zeros([1,1])
gtPols = []
detPols = []
gtPolPoints = []
detPolPoints = []
# pseudo character centers
gtCharPoints = []
gtCharCounts = []
# visualization
charCounts = np.zeros([1,1])
recallScore = list()
precisionScore = list()
#Array of Ground Truth Polygons' keys marked as don't Care
gtDontCarePolsNum = []
#Array of Detected Polygons' matched with a don't Care GT
detDontCarePolsNum = []
pairs = []
detMatchedNums = []
gtExcludeNums = []
arrSampleConfidences = [];
arrSampleMatch = [];
sampleAP = 0;
evaluationLog = ""
pointsList,_,transcriptionsList = rrc_evaluation_funcs.get_tl_line_values_from_file_contents(gtFile, evaluationParams['GT_CRLF'], evaluationParams['GT_LTRB'], True, False)
for n in range(len(pointsList)):
points = pointsList[n]
transcription = transcriptionsList[n]
dontCare = transcription == "###"
if evaluationParams['GT_LTRB']:
gtRect = Rectangle(*points)
gtPol = rectangle_to_polygon(gtRect)
points = polygon_to_points(gtPol)
else:
gtPol = polygon_from_points(points)
gtPols.append(gtPol)
if dontCare:
gtDontCarePolsNum.append( len(gtPols)-1 )
gtPolPoints.append(points)
gtCharPoints.append([])
else:
gtCharSize = len(transcription)
aspect_ratio = gtPol.aspectRatio()
if aspect_ratio > 1.5:
points_ver = [points[6], points[7], points[0], points[1], points[2], points[3], points[4], points[5]]
gtPolPoints.append(points_ver)
gtCharPoints.append(gtBoxtoChars(gtCharSize, points_ver))
else:
gtCharPoints.append(gtBoxtoChars(gtCharSize, points))
gtPolPoints.append(points)
evaluationLog += "GT polygons: " + str(len(gtPols)) + (" (" + str(len(gtDontCarePolsNum)) + " don't care)\n" if len(gtDontCarePolsNum)>0 else "\n")
# GT Don't Care overlap
for DontCare in gtDontCarePolsNum:
for gtNum in list(set(range(len(gtPols))) - set(gtDontCarePolsNum)):
if get_intersection(gtPols[gtNum], gtPols[DontCare]) > 0:
gtPols[DontCare] -= gtPols[gtNum]
if resFile in subm:
detFile = rrc_evaluation_funcs.decode_utf8(subm[resFile])
pointsList,confidencesList,_ = rrc_evaluation_funcs.get_tl_line_values_from_file_contents(detFile,evaluationParams['DET_CRLF'],evaluationParams['DET_LTRB'],evaluationParams['TRANSCRIPTION'],evaluationParams['CONFIDENCES'])
for n in range(len(pointsList)):
points = pointsList[n]
if evaluationParams['DET_LTRB']:
detRect = Rectangle(*points)
detPol = rectangle_to_polygon(detRect)
points = polygon_to_points(detPol)
else:
detPol = polygon_from_points(points)
detPols.append(detPol)
detPolPoints.append(points)
evaluationLog += "DET polygons: " + str(len(detPols))
if len(gtPols)>0 and len(detPols)>0:
#Calculate IoU and precision matrixs
outputShape=[len(gtPols),len(detPols)]
recallMat = np.empty(outputShape)
precisionMat = np.empty(outputShape)
matchMat = np.zeros(outputShape)
gtRectMat = np.zeros(len(gtPols),np.int8)
detRectMat = np.zeros(len(detPols),np.int8)
gtExcludeMat = np.zeros(len(gtPols),np.int8)
detExcludeMat = np.zeros(len(detPols),np.int8)
for gtNum in range(len(gtPols)):
detCharCounts = []
for detNum in range(len(detPols)):
pG = gtPols[gtNum]
pD = detPols[detNum]
intersected_area = get_intersection(pD,pG)
recallMat[gtNum,detNum] = 0 if pG.area()==0 else intersected_area / pG.area()
precisionMat[gtNum,detNum] = 0 if pD.area()==0 else intersected_area / pD.area()
detCharCounts.append(np.zeros(len(gtCharPoints[gtNum])))
gtCharCounts.append(detCharCounts)
# Find detection Don't Care
if len(gtDontCarePolsNum)>0 :
for detNum in range(len(detPols)):
# many-to-one
many_sum = 0
for gtNum in gtDontCarePolsNum:
if recallMat[gtNum, detNum] > evaluationParams['AREA_RECALL_CONSTRAINT']:
many_sum += precisionMat[gtNum, detNum]
if many_sum >= evaluationParams['AREA_PRECISION_CONSTRAINT']:
detDontCarePolsNum.append(detNum)
else:
for gtNum in gtDontCarePolsNum:
if precisionMat[gtNum, detNum] > evaluationParams['AREA_PRECISION_CONSTRAINT']:
detDontCarePolsNum.append(detNum)
break
# many-to-one for mixed DC and non-DC
for gtNum in gtDontCarePolsNum:
if recallMat[gtNum, detNum] > 0:
detPols[detNum] -= gtPols[gtNum]
evaluationLog += " (" + str(len(detDontCarePolsNum)) + " don't care)\n" if len(detDontCarePolsNum)>0 else "\n"
# Recalculate matrices
for gtNum in range(len(gtPols)):
for detNum in range(len(detPols)):
pG = gtPols[gtNum]
pD = detPols[detNum]
intersected_area = get_intersection(pD,pG)
recallMat[gtNum,detNum] = 0 if pG.area()==0 else intersected_area / pG.area()
precisionMat[gtNum,detNum] = 0 if pD.area()==0 else intersected_area / pD.area()
# Find many-to-one matches
evaluationLog += "Find many-to-one matches\n"
for detNum in range(len(detPols)):
if detNum not in detDontCarePolsNum:
match, matchesGt = many_to_one_match(detNum)
if match:
pairs.append({'gt':matchesGt, 'det':[detNum], 'type':'MO'})
evaluationLog += "Match GT #" + str(matchesGt) + " with Det #" + str(detNum) + "\n"
# Find one-to-one matches
evaluationLog += "Find one-to-one matches\n"
for gtNum in range(len(gtPols)):
for detNum in range(len(detPols)):
if gtNum not in gtDontCarePolsNum and detNum not in detDontCarePolsNum :
match = one_to_one_match(gtNum, detNum)
if match:
normDist = center_distance(gtPols[gtNum], detPols[detNum]);
normDist /= diag(gtPolPoints[gtNum]) + diag(detPolPoints[detNum]);
normDist *= 2.0;
if normDist < evaluationParams['EV_PARAM_IND_CENTER_DIFF_THR'] :
pairs.append({'gt':[gtNum],'det':[detNum],'type':'OO'})
evaluationLog += "Match GT #" + str(gtNum) + " with Det #" + str(detNum) + "\n"
# Find one-to-many matches
evaluationLog += "Find one-to-many matches\n"
for gtNum in range(len(gtPols)):
if gtNum not in gtDontCarePolsNum:
match, matchesDet = one_to_many_match(gtNum)
if match:
pairs.append({'gt':[gtNum], 'det':matchesDet, 'type':'OM'})
evaluationLog += "Match Gt #" + str(gtNum) + " with Det #" + str(matchesDet) + "\n"
# Fill match matrix
for pair in pairs:
matchMat[pair['gt'],pair['det']] = 1
# Fill character matrix
char_fill(np.where(matchMat.sum(axis=0) > 0)[0], matchMat)
# Recall score
for gtNum in range(len(gtRectMat)):
if matchMat.sum(axis=1)[gtNum] > 0:
recallAccum += len(np.where(sum(gtCharCounts[gtNum]) == 1)[0]) / len(gtCharPoints[gtNum])
if len(np.where(sum(gtCharCounts[gtNum]) == 1)[0]) / len(gtCharPoints[gtNum]) < 1:
recallScore.append("<font color=red>" + str(len(np.where(sum(gtCharCounts[gtNum]) == 1)[0])) + "/" + str(len(gtCharPoints[gtNum])) + "</font>")
else: recallScore.append(str(len(np.where(sum(gtCharCounts[gtNum]) == 1)[0])) + "/" + str(len(gtCharPoints[gtNum])))
else: recallScore.append("")
# Precision score
for detNum in range(len(detRectMat)):
if matchMat.sum(axis=0)[detNum] > 0:
detTotal = 0; detContain = 0
for gtNum in range(len(gtRectMat)):
if matchMat[gtNum, detNum] > 0:
detTotal += len(gtCharCounts[gtNum][detNum])
detContain += len(np.where(gtCharCounts[gtNum][detNum] == 1)[0])
precisionAccum += detContain / detTotal
if detContain / detTotal < 1:
precisionScore.append("<font color=red>" + str(detContain) + "/" + str(detTotal) + "</font>")
else: precisionScore.append(str(detContain) + "/" + str(detTotal))
else:
precisionScore.append("")
# Visualization
charCounts = np.zeros((len(gtRectMat), len(detRectMat)))
for gtNum in range(len(gtRectMat)):
for detNum in range(len(detRectMat)):
charCounts[gtNum][detNum] = sum(gtCharCounts[gtNum][detNum])
if evaluationParams['CONFIDENCES']:
for detNum in range(len(detPols)):
if detNum not in detDontCarePolsNum :
match = detNum in detMatchedNums
arrSampleConfidences.append(confidencesList[detNum])
arrSampleMatch.append(match)
arrGlobalConfidences.append(confidencesList[detNum]);
arrGlobalMatches.append(match);
numGtCare = (len(gtPols) - len(gtDontCarePolsNum))
numDetCare = (len(detPols) - len(detDontCarePolsNum))
if numGtCare == 0:
recall = float(1)
precision = float(0) if numDetCare >0 else float(1)
sampleAP = precision
else:
recall = float(recallAccum) / numGtCare
precision = float(0) if numDetCare==0 else float(precisionAccum) / numDetCare
if evaluationParams['CONFIDENCES'] and evaluationParams['PER_SAMPLE_RESULTS']:
sampleAP = compute_ap(arrSampleConfidences, arrSampleMatch, numGtCare )
hmean = 0 if (precision + recall)==0 else 2.0 * precision * recall / (precision + recall)
evaluationLog += "<b>Recall = " + str(round(recallAccum,2)) + " / " + str(numGtCare) + " = " + str(round(recall,2)) + "\n</b>"
evaluationLog += "<b>Precision = " + str(round(precisionAccum,2)) + " / " + str(numDetCare) + " = "+ str(round(precision,2)) + "\n</b>"
methodRecallSum += recallAccum
methodPrecisionSum += precisionAccum
numGlobalCareGt += numGtCare
numGlobalCareDet += numDetCare
if evaluationParams['PER_SAMPLE_RESULTS']:
perSampleMetrics[resFile] = {
'precision':precision,
'recall':recall,
'hmean':hmean,
'pairs':pairs,
'AP':sampleAP,
'recallMat':[] if len(detPols)>100 else recallMat.tolist(),
'precisionMat':[] if len(detPols)>100 else precisionMat.tolist(),
'gtPolPoints':gtPolPoints,
'detPolPoints':detPolPoints,
'gtCharPoints':gtCharPoints,
'gtCharCounts':[sum(k).tolist() for k in gtCharCounts],
'charCounts': charCounts.tolist(),
'recallScore': recallScore,
'precisionScore': precisionScore,
'gtDontCare':gtDontCarePolsNum,
'detDontCare':detDontCarePolsNum,
'evaluationParams': evaluationParams,
'evaluationLog': evaluationLog
}
# Compute MAP and MAR
AP = 0
if evaluationParams['CONFIDENCES']:
AP = compute_ap(arrGlobalConfidences, arrGlobalMatches, numGlobalCareGt)
methodRecall = 0 if numGlobalCareGt == 0 else methodRecallSum/numGlobalCareGt
methodPrecision = 0 if numGlobalCareDet == 0 else methodPrecisionSum/numGlobalCareDet
methodHmean = 0 if methodRecall + methodPrecision==0 else 2* methodRecall * methodPrecision / (methodRecall + methodPrecision)
methodMetrics = {'recall':methodRecall, 'precision':methodPrecision, 'hmean':methodHmean, 'AP':AP }
resDict = {'calculated':True,'Message':'','method': methodMetrics,'per_sample': perSampleMetrics}
return resDict;
if __name__=='__main__':
rrc_evaluation_funcs.main_evaluation(None, default_evaluation_params, validate_data, evaluate_method)
================================================
FILE: static/funcs.js
================================================
var getUrlParameter = function getUrlParameter(sParam) {
var sPageURL = decodeURIComponent(window.location.search.substring(1)),
sURLVariables = sPageURL.split('&'),
sParameterName,
i;
for (i = 0; i < sURLVariables.length; i++) {
sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) {
return sParameterName[1] === undefined ? true : sParameterName[1];
}
}
};
================================================
FILE: static/jquery-mousewheel.js
================================================
/**
*
* credits for this plugin go to brandonaaron.net
*
* unfortunately his site is down
*
* @param {Object} up
* @param {Object} down
* @param {Object} preventDefault
*/
jQuery.fn.extend({
mousewheel: function(up, down, preventDefault) {
return this.hover(
function() {
jQuery.event.mousewheel.giveFocus(this, up, down, preventDefault);
},
function() {
jQuery.event.mousewheel.removeFocus(this);
}
);
},
mousewheeldown: function(fn, preventDefault) {
return this.mousewheel(function(){}, fn, preventDefault);
},
mousewheelup: function(fn, preventDefault) {
return this.mousewheel(fn, function(){}, preventDefault);
},
unmousewheel: function() {
return this.each(function() {
jQuery(this).unmouseover().unmouseout();
jQuery.event.mousewheel.removeFocus(this);
});
},
unmousewheeldown: jQuery.fn.unmousewheel,
unmousewheelup: jQuery.fn.unmousewheel
});
jQuery.event.mousewheel = {
giveFocus: function(el, up, down, preventDefault) {
if (el._handleMousewheel) jQuery(el).unmousewheel();
if (preventDefault == window.undefined && down && down.constructor != Function) {
preventDefault = down;
down = null;
}
el._handleMousewheel = function(event) {
if (!event) event = window.event;
if (preventDefault)
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
var delta = 0;
if (event.wheelDelta) {
delta = event.wheelDelta/120;
if (window.opera) delta = -delta;
} else if (event.detail) {
delta = -event.detail/3;
}
if (up && (delta > 0 || !down))
up.apply(el, [event, delta]);
else if (down && delta < 0)
down.apply(el, [event, delta]);
};
if (window.addEventListener)
window.addEventListener('DOMMouseScroll', el._handleMousewheel, false);
window.onmousewheel = document.onmousewheel = el._handleMousewheel;
},
removeFocus: function(el) {
if (!el._handleMousewheel) return;
if (window.removeEventListener)
window.removeEventListener('DOMMouseScroll', el._handleMousewheel, false);
window.onmousewheel = document.onmousewheel = null;
el._handleMousewheel = null;
}
};
================================================
FILE: static/jquery.form-3.51.js
================================================
/*!
* jQuery Form Plugin
* version: 3.51.0-2014.06.20
* Requires jQuery v1.5 or later
* Copyright (c) 2014 M. Alsup
* Examples and documentation at: http://malsup.com/jquery/form/
* Project repository: https://github.com/malsup/form
* Dual licensed under the MIT and GPL licenses.
* https://github.com/malsup/form#copyright-and-license
*/
!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):e("undefined"!=typeof jQuery?jQuery:window.Zepto)}(function(e){"use strict";function t(t){var r=t.data;t.isDefaultPrevented()||(t.preventDefault(),e(t.target).ajaxSubmit(r))}function r(t){var r=t.target,a=e(r);if(!a.is("[type=submit],[type=image]")){var n=a.closest("[type=submit]");if(0===n.length)return;r=n[0]}var i=this;if(i.clk=r,"image"==r.type)if(void 0!==t.offsetX)i.clk_x=t.offsetX,i.clk_y=t.offsetY;else if("function"==typeof e.fn.offset){var o=a.offset();i.clk_x=t.pageX-o.left,i.clk_y=t.pageY-o.top}else i.clk_x=t.pageX-r.offsetLeft,i.clk_y=t.pageY-r.offsetTop;setTimeout(function(){i.clk=i.clk_x=i.clk_y=null},100)}function a(){if(e.fn.ajaxSubmit.debug){var t="[jquery.form] "+Array.prototype.join.call(arguments,"");window.console&&window.console.log?window.console.log(t):window.opera&&window.opera.postError&&window.opera.postError(t)}}var n={};n.fileapi=void 0!==e("<input type='file'/>").get(0).files,n.formdata=void 0!==window.FormData;var i=!!e.fn.prop;e.fn.attr2=function(){if(!i)return this.attr.apply(this,arguments);var e=this.prop.apply(this,arguments);return e&&e.jquery||"string"==typeof e?e:this.attr.apply(this,arguments)},e.fn.ajaxSubmit=function(t){function r(r){var a,n,i=e.param(r,t.traditional).split("&"),o=i.length,s=[];for(a=0;o>a;a++)i[a]=i[a].replace(/\+/g," "),n=i[a].split("="),s.push([decodeURIComponent(n[0]),decodeURIComponent(n[1])]);return s}function o(a){for(var n=new FormData,i=0;i<a.length;i++)n.append(a[i].name,a[i].value);if(t.extraData){var o=r(t.extraData);for(i=0;i<o.length;i++)o[i]&&n.append(o[i][0],o[i][1])}t.data=null;var s=e.extend(!0,{},e.ajaxSettings,t,{contentType:!1,processData:!1,cache:!1,type:u||"POST"});t.uploadProgress&&(s.xhr=function(){var r=e.ajaxSettings.xhr();return r.upload&&r.upload.addEventListener("progress",function(e){var r=0,a=e.loaded||e.position,n=e.total;e.lengthComputable&&(r=Math.ceil(a/n*100)),t.uploadProgress(e,a,n,r)},!1),r}),s.data=null;var c=s.beforeSend;return s.beforeSend=function(e,r){r.data=t.formData?t.formData:n,c&&c.call(this,e,r)},e.ajax(s)}function s(r){function n(e){var t=null;try{e.contentWindow&&(t=e.contentWindow.document)}catch(r){a("cannot get iframe.contentWindow document: "+r)}if(t)return t;try{t=e.contentDocument?e.contentDocument:e.document}catch(r){a("cannot get iframe.contentDocument: "+r),t=e.document}return t}function o(){function t(){try{var e=n(g).readyState;a("state = "+e),e&&"uninitialized"==e.toLowerCase()&&setTimeout(t,50)}catch(r){a("Server abort: ",r," (",r.name,")"),s(k),j&&clearTimeout(j),j=void 0}}var r=f.attr2("target"),i=f.attr2("action"),o="multipart/form-data",c=f.attr("enctype")||f.attr("encoding")||o;w.setAttribute("target",p),(!u||/post/i.test(u))&&w.setAttribute("method","POST"),i!=m.url&&w.setAttribute("action",m.url),m.skipEncodingOverride||u&&!/post/i.test(u)||f.attr({encoding:"multipart/form-data",enctype:"multipart/form-data"}),m.timeout&&(j=setTimeout(function(){T=!0,s(D)},m.timeout));var l=[];try{if(m.extraData)for(var d in m.extraData)m.extraData.hasOwnProperty(d)&&l.push(e.isPlainObject(m.extraData[d])&&m.extraData[d].hasOwnProperty("name")&&m.extraData[d].hasOwnProperty("value")?e('<input type="hidden" name="'+m.extraData[d].name+'">').val(m.extraData[d].value).appendTo(w)[0]:e('<input type="hidden" name="'+d+'">').val(m.extraData[d]).appendTo(w)[0]);m.iframeTarget||v.appendTo("body"),g.attachEvent?g.attachEvent("onload",s):g.addEventListener("load",s,!1),setTimeout(t,15);try{w.submit()}catch(h){var x=document.createElement("form").submit;x.apply(w)}}finally{w.setAttribute("action",i),w.setAttribute("enctype",c),r?w.setAttribute("target",r):f.removeAttr("target"),e(l).remove()}}function s(t){if(!x.aborted&&!F){if(M=n(g),M||(a("cannot access response document"),t=k),t===D&&x)return x.abort("timeout"),void S.reject(x,"timeout");if(t==k&&x)return x.abort("server abort"),void S.reject(x,"error","server abort");if(M&&M.location.href!=m.iframeSrc||T){g.detachEvent?g.detachEvent("onload",s):g.removeEventListener("load",s,!1);var r,i="success";try{if(T)throw"timeout";var o="xml"==m.dataType||M.XMLDocument||e.isXMLDoc(M);if(a("isXml="+o),!o&&window.opera&&(null===M.body||!M.body.innerHTML)&&--O)return a("requeing onLoad callback, DOM not available"),void setTimeout(s,250);var u=M.body?M.body:M.documentElement;x.responseText=u?u.innerHTML:null,x.responseXML=M.XMLDocument?M.XMLDocument:M,o&&(m.dataType="xml"),x.getResponseHeader=function(e){var t={"content-type":m.dataType};return t[e.toLowerCase()]},u&&(x.status=Number(u.getAttribute("status"))||x.status,x.statusText=u.getAttribute("statusText")||x.statusText);var c=(m.dataType||"").toLowerCase(),l=/(json|script|text)/.test(c);if(l||m.textarea){var f=M.getElementsByTagName("textarea")[0];if(f)x.responseText=f.value,x.status=Number(f.getAttribute("status"))||x.status,x.statusText=f.getAttribute("statusText")||x.statusText;else if(l){var p=M.getElementsByTagName("pre")[0],h=M.getElementsByTagName("body")[0];p?x.responseText=p.textContent?p.textContent:p.innerText:h&&(x.responseText=h.textContent?h.textContent:h.innerText)}}else"xml"==c&&!x.responseXML&&x.responseText&&(x.responseXML=X(x.responseText));try{E=_(x,c,m)}catch(y){i="parsererror",x.error=r=y||i}}catch(y){a("error caught: ",y),i="error",x.error=r=y||i}x.aborted&&(a("upload aborted"),i=null),x.status&&(i=x.status>=200&&x.status<300||304===x.status?"success":"error"),"success"===i?(m.success&&m.success.call(m.context,E,"success",x),S.resolve(x.responseText,"success",x),d&&e.event.trigger("ajaxSuccess",[x,m])):i&&(void 0===r&&(r=x.statusText),m.error&&m.error.call(m.context,x,i,r),S.reject(x,"error",r),d&&e.event.trigger("ajaxError",[x,m,r])),d&&e.event.trigger("ajaxComplete",[x,m]),d&&!--e.active&&e.event.trigger("ajaxStop"),m.complete&&m.complete.call(m.context,x,i),F=!0,m.timeout&&clearTimeout(j),setTimeout(function(){m.iframeTarget?v.attr("src",m.iframeSrc):v.remove(),x.responseXML=null},100)}}}var c,l,m,d,p,v,g,x,y,b,T,j,w=f[0],S=e.Deferred();if(S.abort=function(e){x.abort(e)},r)for(l=0;l<h.length;l++)c=e(h[l]),i?c.prop("disabled",!1):c.removeAttr("disabled");if(m=e.extend(!0,{},e.ajaxSettings,t),m.context=m.context||m,p="jqFormIO"+(new Date).getTime(),m.iframeTarget?(v=e(m.iframeTarget),b=v.attr2("name"),b?p=b:v.attr2("name",p)):(v=e('<iframe name="'+p+'" src="'+m.iframeSrc+'" />'),v.css({position:"absolute",top:"-1000px",left:"-1000px"})),g=v[0],x={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(t){var r="timeout"===t?"timeout":"aborted";a("aborting upload... "+r),this.aborted=1;try{g.contentWindow.document.execCommand&&g.contentWindow.document.execCommand("Stop")}catch(n){}v.attr("src",m.iframeSrc),x.error=r,m.error&&m.error.call(m.context,x,r,t),d&&e.event.trigger("ajaxError",[x,m,r]),m.complete&&m.complete.call(m.context,x,r)}},d=m.global,d&&0===e.active++&&e.event.trigger("ajaxStart"),d&&e.event.trigger("ajaxSend",[x,m]),m.beforeSend&&m.beforeSend.call(m.context,x,m)===!1)return m.global&&e.active--,S.reject(),S;if(x.aborted)return S.reject(),S;y=w.clk,y&&(b=y.name,b&&!y.disabled&&(m.extraData=m.extraData||{},m.extraData[b]=y.value,"image"==y.type&&(m.extraData[b+".x"]=w.clk_x,m.extraData[b+".y"]=w.clk_y)));var D=1,k=2,A=e("meta[name=csrf-token]").attr("content"),L=e("meta[name=csrf-param]").attr("content");L&&A&&(m.extraData=m.extraData||{},m.extraData[L]=A),m.forceSync?o():setTimeout(o,10);var E,M,F,O=50,X=e.parseXML||function(e,t){return window.ActiveXObject?(t=new ActiveXObject("Microsoft.XMLDOM"),t.async="false",t.loadXML(e)):t=(new DOMParser).parseFromString(e,"text/xml"),t&&t.documentElement&&"parsererror"!=t.documentElement.nodeName?t:null},C=e.parseJSON||function(e){return window.eval("("+e+")")},_=function(t,r,a){var n=t.getResponseHeader("content-type")||"",i="xml"===r||!r&&n.indexOf("xml")>=0,o=i?t.responseXML:t.responseText;return i&&"parsererror"===o.documentElement.nodeName&&e.error&&e.error("parsererror"),a&&a.dataFilter&&(o=a.dataFilter(o,r)),"string"==typeof o&&("json"===r||!r&&n.indexOf("json")>=0?o=C(o):("script"===r||!r&&n.indexOf("javascript")>=0)&&e.globalEval(o)),o};return S}if(!this.length)return a("ajaxSubmit: skipping submit process - no element selected"),this;var u,c,l,f=this;"function"==typeof t?t={success:t}:void 0===t&&(t={}),u=t.type||this.attr2("method"),c=t.url||this.attr2("action"),l="string"==typeof c?e.trim(c):"",l=l||window.location.href||"",l&&(l=(l.match(/^([^#]+)/)||[])[1]),t=e.extend(!0,{url:l,success:e.ajaxSettings.success,type:u||e.ajaxSettings.type,iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},t);var m={};if(this.trigger("form-pre-serialize",[this,t,m]),m.veto)return a("ajaxSubmit: submit vetoed via form-pre-serialize trigger"),this;if(t.beforeSerialize&&t.beforeSerialize(this,t)===!1)return a("ajaxSubmit: submit aborted via beforeSerialize callback"),this;var d=t.traditional;void 0===d&&(d=e.ajaxSettings.traditional);var p,h=[],v=this.formToArray(t.semantic,h);if(t.data&&(t.extraData=t.data,p=e.param(t.data,d)),t.beforeSubmit&&t.beforeSubmit(v,this,t)===!1)return a("ajaxSubmit: submit aborted via beforeSubmit callback"),this;if(this.trigger("form-submit-validate",[v,this,t,m]),m.veto)return a("ajaxSubmit: submit vetoed via form-submit-validate trigger"),this;var g=e.param(v,d);p&&(g=g?g+"&"+p:p),"GET"==t.type.toUpperCase()?(t.url+=(t.url.indexOf("?")>=0?"&":"?")+g,t.data=null):t.data=g;var x=[];if(t.resetForm&&x.push(function(){f.resetForm()}),t.clearForm&&x.push(function(){f.clearForm(t.includeHidden)}),!t.dataType&&t.target){var y=t.success||function(){};x.push(function(r){var a=t.replaceTarget?"replaceWith":"html";e(t.target)[a](r).each(y,arguments)})}else t.success&&x.push(t.success);if(t.success=function(e,r,a){for(var n=t.context||this,i=0,o=x.length;o>i;i++)x[i].apply(n,[e,r,a||f,f])},t.error){var b=t.error;t.error=function(e,r,a){var n=t.context||this;b.apply(n,[e,r,a,f])}}if(t.complete){var T=t.complete;t.complete=function(e,r){var a=t.context||this;T.apply(a,[e,r,f])}}var j=e("input[type=file]:enabled",this).filter(function(){return""!==e(this).val()}),w=j.length>0,S="multipart/form-data",D=f.attr("enctype")==S||f.attr("encoding")==S,k=n.fileapi&&n.formdata;a("fileAPI :"+k);var A,L=(w||D)&&!k;t.iframe!==!1&&(t.iframe||L)?t.closeKeepAlive?e.get(t.closeKeepAlive,function(){A=s(v)}):A=s(v):A=(w||D)&&k?o(v):e.ajax(t),f.removeData("jqxhr").data("jqxhr",A);for(var E=0;E<h.length;E++)h[E]=null;return this.trigger("form-submit-notify",[this,t]),this},e.fn.ajaxForm=function(n){if(n=n||{},n.delegation=n.delegation&&e.isFunction(e.fn.on),!n.delegation&&0===this.length){var i={s:this.selector,c:this.context};return!e.isReady&&i.s?(a("DOM not ready, queuing ajaxForm"),e(function(){e(i.s,i.c).ajaxForm(n)}),this):(a("terminating; zero elements found by selector"+(e.isReady?"":" (DOM not ready)")),this)}return n.delegation?(e(document).off("submit.form-plugin",this.selector,t).off("click.form-plugin",this.selector,r).on("submit.form-plugin",this.selector,n,t).on("click.form-plugin",this.selector,n,r),this):this.ajaxFormUnbind().bind("submit.form-plugin",n,t).bind("click.form-plugin",n,r)},e.fn.ajaxFormUnbind=function(){return this.unbind("submit.form-plugin click.form-plugin")},e.fn.formToArray=function(t,r){var a=[];if(0===this.length)return a;var i,o=this[0],s=this.attr("id"),u=t?o.getElementsByTagName("*"):o.elements;if(u&&!/MSIE [678]/.test(navigator.userAgent)&&(u=e(u).get()),s&&(i=e(':input[form="'+s+'"]').get(),i.length&&(u=(u||[]).concat(i))),!u||!u.length)return a;var c,l,f,m,d,p,h;for(c=0,p=u.length;p>c;c++)if(d=u[c],f=d.name,f&&!d.disabled)if(t&&o.clk&&"image"==d.type)o.clk==d&&(a.push({name:f,value:e(d).val(),type:d.type}),a.push({name:f+".x",value:o.clk_x},{name:f+".y",value:o.clk_y}));else if(m=e.fieldValue(d,!0),m&&m.constructor==Array)for(r&&r.push(d),l=0,h=m.length;h>l;l++)a.push({name:f,value:m[l]});else if(n.fileapi&&"file"==d.type){r&&r.push(d);var v=d.files;if(v.length)for(l=0;l<v.length;l++)a.push({name:f,value:v[l],type:d.type});else a.push({name:f,value:"",type:d.type})}else null!==m&&"undefined"!=typeof m&&(r&&r.push(d),a.push({name:f,value:m,type:d.type,required:d.required}));if(!t&&o.clk){var g=e(o.clk),x=g[0];f=x.name,f&&!x.disabled&&"image"==x.type&&(a.push({name:f,value:g.val()}),a.push({name:f+".x",value:o.clk_x},{name:f+".y",value:o.clk_y}))}return a},e.fn.formSerialize=function(t){return e.param(this.formToArray(t))},e.fn.fieldSerialize=function(t){var r=[];return this.each(function(){var a=this.name;if(a){var n=e.fieldValue(this,t);if(n&&n.constructor==Array)for(var i=0,o=n.length;o>i;i++)r.push({name:a,value:n[i]});else null!==n&&"undefined"!=typeof n&&r.push({name:this.name,value:n})}}),e.param(r)},e.fn.fieldValue=function(t){for(var r=[],a=0,n=this.length;n>a;a++){var i=this[a],o=e.fieldValue(i,t);null===o||"undefined"==typeof o||o.constructor==Array&&!o.length||(o.constructor==Array?e.merge(r,o):r.push(o))}return r},e.fieldValue=function(t,r){var a=t.name,n=t.type,i=t.tagName.toLowerCase();if(void 0===r&&(r=!0),r&&(!a||t.disabled||"reset"==n||"button"==n||("checkbox"==n||"radio"==n)&&!t.checked||("submit"==n||"image"==n)&&t.form&&t.form.clk!=t||"select"==i&&-1==t.selectedIndex))return null;if("select"==i){var o=t.selectedIndex;if(0>o)return null;for(var s=[],u=t.options,c="select-one"==n,l=c?o+1:u.length,f=c?o:0;l>f;f++){var m=u[f];if(m.selected){var d=m.value;if(d||(d=m.attributes&&m.attributes.value&&!m.attributes.value.specified?m.text:m.value),c)return d;s.push(d)}}return s}return e(t).val()},e.fn.clearForm=function(t){return this.each(function(){e("input,select,textarea",this).clearFields(t)})},e.fn.clearFields=e.fn.clearInputs=function(t){var r=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var a=this.type,n=this.tagName.toLowerCase();r.test(a)||"textarea"==n?this.value="":"checkbox"==a||"radio"==a?this.checked=!1:"select"==n?this.selectedIndex=-1:"file"==a?/MSIE/.test(navigator.userAgent)?e(this).replaceWith(e(this).clone(!0)):e(this).val(""):t&&(t===!0&&/hidden/.test(a)||"string"==typeof t&&e(this).is(t))&&(this.value="")})},e.fn.resetForm=function(){return this.each(function(){("function"==typeof this.reset||"object"==typeof this.reset&&!this.reset.nodeType)&&this.reset()})},e.fn.enable=function(e){return void 0===e&&(e=!0),this.each(function(){this.disabled=!e})},e.fn.selected=function(t){return void 0===t&&(t=!0),this.each(function(){var r=this.type;if("checkbox"==r||"radio"==r)this.checked=t;else if("option"==this.tagName.toLowerCase()){var a=e(this).parent("select");t&&a[0]&&"select-one"==a[0].type&&a.find("option").selected(!1),this.selected=t}})},e.fn.ajaxSubmit.debug=!1});
================================================
FILE: static/ranking.js
================================================
/* global web, google */
var ranking_task_graphic_options_default = {
height: 240,
fontSize:12,
animation:{'duration':0},
title:'',
backgroundColor:'transparent',
chartArea:{left:50,top:20,width:350,height:180},
width: 400,
focusTarget:'category',
legend: {position:'bottom'},
vAxis:{
format:'none',
textStyle:{fontSize: 8},
//title:'%',
titleTextStyle:{fontSize: 12,fontStyle:'bold'},
textPosition:'out'
},
hAxis:{
textStyle:{fontSize: 8}
}
};
function delete_methods(){
if(!confirm("Are you sure to delete all methods?")){
return;
}
var url = "/delete_all";
$.post(url, function (data) {
document.location.reload();
});
}
function delete_method(id){
if(!confirm("Are you sure to delete the method?")){
return;
}
var url = "/delete_method";
$.post(url,{"id":id} , function (data) {
document.location.reload();
});
}
function edit_method(id,el){
var current_name = $(el).closest("tr").find("span.title").text();
var name = prompt("Enter the method's name", current_name);
if (name != null) {
var url = "/edit_method";
$.post(url,{"id":id,"name":name} , function (data) {
document.location.reload();
});
}
}
function upload_subm(){
$("form").submit();
}
function wait_screen(msg){
$("body").append("<div class='overlay'>" + (msg!=undefined? "<div class='info'><span class='msg'>" + msg + "</span><img class='wait' src='/static/wait.gif'></div>" : "<img src='/static/wait.gif'>") + "</div>");
}
function close_overlay(){
$("div.overlay").remove();
}
function show_error(msg){
if(!$("div.overlay").length){
$("body").append("<div class='overlay'></div>");
}
$("div.overlay").html("<div class='error'><span class='msg'>" + msg + "</span><button onclick='close_overlay()'>OK</button></div>");
}
function show_info(msg){
if(!$("div.overlay").length){
$("body").append("<div class='overlay'></div>");
}
$("div.overlay").html("<div class='info'><span class='msg'>" + msg + "</span><button onclick='close_overlay()'>OK</button></div>");
}
$(document).ready( function(){
$("form").submit(function() {
wait_screen("Please wait, your results are being uploaded,validated and evaluated..");
var options = {
url : '/evaluate?json=1' ,
dataType: 'json',
success : function(result){
close_overlay();
if (result.calculated){
document.location.href = "/method/?m=" + result.id;
}else{
show_error(result.Message);
}
},
error : function(){
close_overlay();
show_error("Error on server");
}
};
var $form = $(this);
setTimeout(function(){
$form.ajaxSubmit(options);
},10);
return false;
});
function carregar_api_grafics(callback){
google.load('visualization', '1',
{packages:['corechart'],callback:callback});
};
var grafic_1 = null;
var grafic_2 = null;
var loaded=false;
function init_graphic(){
carregar_api_grafics(function(){
grafic_1 = new google.visualization.ColumnChart(document.getElementById('div_ranking_1'));
if($("#div_ranking_2").length){
grafic_2 = new google.visualization.ColumnChart(document.getElementById('div_ranking_2'));
}
loaded=true;
show_current_graphic();
});
}
if ($("#div_ranking_1").length){
show_current_graphic();
}
function show_current_graphic(){
if(!loaded){
init_graphic();
return;
}
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3;}}return t}};
var id_data = "graphic";
var dadesArray = eval(Base64.decode($("#" + id_data + "").val()));
var ordenacio = $("#" + id_data + "-sort").val();
var format = $("#" + id_data + "-format").val();
var type = $("#" + id_data + "-type").val();
var dades = google.visualization.arrayToDataTable(dadesArray);
var width = $(window).width() - $("table.results").width()-40;
if (width<$(window).width()/2){
width = $(window).width() - 20;
$("#div_rankings").removeClass("ib");
}else{
$("#div_rankings").addClass("ib");
}
if(grafic_2!=null){
width = width/2 -10;
}
var height = $(window).height() - 320;
var options = jQuery.extend(true, {}, ranking_task_graphic_options_default);
options.animation.duration= 300;
options.width= width;
options.height= height;
options.chartArea.width = width-40;
options.chartArea.height = height-100;
options.vAxis.title = ordenacio;
options.vAxis.format = (format=="perc"? 'percent' : ( type!="string"? 'decimal' : 'none' ) );
if(format=="perc"){
options.vAxis.minValue = 0;
options.vAxis.maxValue = 1;
}
grafic_1.draw(dades,options);
if(grafic_2!=null){
var ordenacio = $("#" + id_data + "-gr2-sort").val();
var format = $("#" + id_data + "-gr2-format").val();
var type = $("#" + id_data + "-gr2-type").val();
var options = jQuery.extend(true, {}, ranking_task_graphic_options_default);
options.animation.duration= 300;
options.width= width;
options.height= height;
options.chartArea.width = width-40;
options.chartArea.height = height-100;
options.vAxis.title = ordenacio;
options.vAxis.format = (format=="perc"? 'percent' : ( type!="string"? 'decimal' : 'none' ) );
if(format=="perc"){
options.vAxis.minValue = 0;
options.vAxis.maxValue = 1;
}
var dadesArray = eval(Base64.decode($("#" + id_data + "-gr2").val()));
var dades = google.visualization.arrayToDataTable(dadesArray);
options.vAxis.title = ordenacio;
grafic_2.draw(dades,options);
}
}
});
function instructions(){
$("#div_instructions").removeClass("hidden");
}
$(document).ready(function(){
$("#div_instructions button.close").click(function(){
$("#div_instructions").addClass("hidden");
});
});
================================================
FILE: static/style.css
================================================
body{
font-family: 'Open Sans',helvetica,arial,sans-serif;
font-size: 1.0em;
margin: 10px;
}
a{
color:#2694E8;
text-decoration: none;
}
.right{
float: right;
}
a:hover{
color:#0580de;
}
#logo{
height: 30px;
margin-right: 20px;
margin-top: -8px;
margin-bottom: -8px;
}
.ib{
display: inline-block;
vertical-align: top;
}
.ml20{
margin-left: 20px;
}
.mr5{
margin-right: 5px;
}
.small{
font-size: 0.8em;
}
img.wait{
display: block;
margin: 10px auto;
}
div.overlay{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content:center;
align-items:center;
}
div.overlay div.info{
background-color: #ececec;
border: 2px solid #4c4c4c;
padding: 10px;
color: #2b2b2b;
}
div.overlay div.error{
background-color: #F44336;
border: 2px solid #C62828;
padding: 10px;
color: #fff;
}
div.overlay button{
display: block;
margin: 0px auto;
margin-top: 20px;
}
h1{
background-color: #4CAF50;
color:#fff;
margin: -10px;
padding: 8px;
font-size: 1.0em;
border-bottom: 1px solid #999;
margin-bottom: 0px;
}
div.breadcrumbs {
display: block;
vertical-align: middle;
position: relative;
background-color: #eee;
border: 1px solid #999;
border-top: none;
padding: 8px;
margin: 0px -10px;
margin-bottom: 6px;
font-size: 0.9em;
}
table.samples{
border-collapse: collapse;
}
table.samples th{
border: 1px solid #999;
background-color: #B3E5FC;
}
table.samples td{
border: 1px solid #aaa;
text-align: right;
padding: 2px;
}
form{
padding: 10px;
margin: -6px -10px;
margin-bottom: 10px;
background-color: #616161;
color: #fff;
}
div.samples_list{
display: flex;
flex-wrap: wrap;
background-color: #eee;
border: 1px solid #999;
padding: 10px;
}
div.samples_list div.sample{
flex-basis: 205px;
font-size: 0.8em;
text-align: center;
background-color: #fff;
border: 1px solid #dadada;
margin: 5px;
}
div.samples_list div.sample img{
display: block;
margin: 0px auto;
}
div.samples_list div.sample p{
margin: 0px;
}
table.results{ font-size: 8pt; border-collapse: collapse; border: 1px solid #aaa;}
table.results thead th{ background-color:#ccc; line-height: 18px; text-align: left; padding: 3px 2px; }
table.results tbody td{ border-bottom: 1px solid #aaa; padding: 3px 6px;}
table.results a.methodname{
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width:100px;
display: block;
}
div.summary{
background-color: #c7c7c7;
border: 1px solid #999;
padding: 8px;
}
div.summary h2{
margin: 0px;
display: inline-block;
margin-right: 10px;
font-size: 1.1em;
}
div.summary p{
margin: 0px 5px;
display: inline-block;
}
div.navigation {
display: block;
vertical-align: middle;
position: relative;
background-color: #d8d7d7;
border: 1px solid #999;
border-top:none;
border-bottom:none;
padding: 2px 20px;
margin: 0px;
}
.pure-button {
/* Structure */
display: inline-block;
zoom: 1;
line-height: normal;
white-space: nowrap;
vertical-align: middle;
text-align: center;
cursor: pointer;
-webkit-user-drag: none;
user-select: none;
box-sizing: border-box;
}
/* Firefox: Get rid of the inner focus border */
.pure-button::-moz-focus-inner {
padding: 0;
border: 0;
}
.pure-button {
font-family: inherit;
font-size: 0.8rem;
padding: 0.5em 1em;
color: #444; /* rgba not supported (IE 8) */
color: rgba(0, 0, 0, 0.80); /* rgba supported */
border: 1px solid #999; /*IE 6/7/8*/
border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/
background-color: #E6E6E6;
text-decoration: none;
border-radius: 2px;
}
.pure-button-hover,
.pure-button:hover,
.pure-button:focus {
/* csslint ignore:start */
filter: alpha(opacity=90);
/* csslint ignore:end */
background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
text-decoration: none;
}
.pure-button:focus {
outline: 0;
}
.pure-button-active,
.pure-button:active {
box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
border-color: #000\9;
}
.pure-button[disabled],
.pure-button-disabled,
.pure-button-disabled:hover,
.pure-button-disabled:focus,
.pure-button-disabled:active {
border: none;
background-image: none;
/* csslint ignore:start */
filter: alpha(opacity=40);
/* csslint ignore:end */
opacity: 0.40;
cursor: not-allowed;
box-shadow: none;
pointer-events: none;
}
.pure-button-hidden {
display: none;
}
.pure-button-primary,
.pure-button-selected,
a.pure-button-primary,
a.pure-button-selected {
background-color: rgb(0, 120, 231);
color: #fff;
}
/* Button Groups */
.pure-button-group .pure-button {
margin: 0;
border-radius: 0;
border-right: 1px solid #111; /* fallback color for rgba() for IE7/8 */
border-right: 1px solid rgba(0, 0, 0, 0.2);
}
.pure-button-group .pure-button:first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.pure-button-group .pure-button:last-child {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
border-right: none;
}
.button-success,
.button-error,
.button-warning,
.button-secondary {
color: white !important;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.button-success {
background: rgb(28, 184, 65); /* this is a green */
}
.button-error {
background: rgb(202, 60, 60); /* this is a maroon */
}
.button-warning {
background: rgb(223, 117, 20); /* this is an orange */
}
.button-secondary {
background: rgb(66, 184, 221); /* this is a light blue */
}
table.results .pure-button{
font-size: 8pt;
}
p.info{
border: 1px solid #dad55e;
padding: 0.8em;
background: #fffa90;
color: #777620;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
}
fieldset{
border: 1px solid #ddd;
display: inline-block;
}
fieldset legend{
border: 1px solid #ccc;
color:#000;
padding:4px 8px;
background-color: #ddd;
}
#div_instructions{
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0,0,0,0.4);
display: flex;
justify-content:center;
align-items:center;
}
#div_instructions div.wrap{
background-color: #ececec;
border: 2px solid #4c4c4c;
padding: 10px;
color: #2b2b2b;
max-width: 600px;
}
#div_instructions h1{
background-color:#9E9E9E;
}
#div_instructions button.close{
float: right;
margin-top: -6px;
}
.hidden{
display: none !important;
}
================================================
FILE: static/visualization.css
================================================
#div_sample{
position: relative;
}
div.filter {
display: block;
vertical-align: middle;
position: relative;
background-color: #eee;
border: 1px solid #999;
padding: 2px 20px;
margin-bottom: 6px;
}
div.im_filters{
position: absolute;
left:160px;
top:5px;
z-index: 9;
}
div.im_filters.det{
left:50%;
margin-left:160px;
}
div.filter #span_evaluation_nav{
margin-left: 20px;
}
div.filter #span_evaluation_nav a{
margin: 0px 4px;
}
div.method_info{
background-color: #f2f2f2;
margin: 20px 0px;
padding: 0px;
box-shadow: 2px 2px 4px rgba(0,0,0,0.4);
border: 1px solid #999;
}
div.method_info.small{
box-shadow: none;
}
h2.method{
background-color: #d8d8d8;
padding-top: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #999;
padding-left: 10px;
margin: 0px;
}
div.method_info span.method{
color:#666;font-size:12pt;
}
h2.method.competition{
background-color: #e1f5fe;
}
div.method_info.small h2.method.competition img{
width: 16px; height: 16px;
}
#div_summary{
padding: 10px;
}
div.top{
display: flex;
justify-content:space-between;
}
div.top.methods_list{
flex-wrap: wrap;
}
div.top.methods_list div.method_info{
flex-basis: 32%;
margin: 0.5%;
}
div.method_info.small{
flex-basis: 33%;
width:33%;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
}
div.method_info.small #div_summary{
box-shadow: none;
font-size: 8pt;
}
div.method_info.small h2.method{
box-shadow:none;
font-size: 10pt;
}
div.method_info.small h2.method a{
color: #2694E8;
text-decoration: none;
}
div.method_info.small h2.method a:hover{ color:#F6A828}
div.method_info.small span.method{
color:#666;font-size:8pt;
}
#div_comparation{
/*padding: 10px;
margin: 9px;
border: 1px solid #999;*/
max-height: 200px;
overflow: auto;
margin-bottom: 6px;
}
#div_comparation h3{
background-color: #eee;
margin: -10px;
padding: 6px 10px;
margin-bottom: 20px;
}
table.sample_methods{
border-collapse: collapse;
}
table.sample_methods td,table.sample_methods th{
border: 1px solid #aaa;
padding: 2px;
text-align: right;
}
table.sample_methods td.method,table.sample_methods th.method{
text-align: left;
}
table.sample_methods th{
background-color: #c7c7c7;
}
table.sample_methods tr.current td{
background-color: #FFC107;
font-weight: bold;
}
table.sample_methods td a{
text-decoration: none;
color: #2694E8;
}
.div_log{
font-size: 8pt;
}
div.group{
border:1px solid #c5c5c5;
padding: 10px;
background-color: #fff;
border-radius: 5px;
}
div.group div.header{
background-color: #dedede;
padding: 10px;
margin: -7px;
margin-bottom: 10px;
border:1px solid #c5c5c5;
border-radius: 5px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
line-height: 20px;
}
================================================
FILE: static/visualization_default.js
================================================
/* global web */
var ClassVisualization = function(){
this.sampleData = null;
//Parameters for GT/Det Canvas visualization
this.canvas_gt = null;
this.canvas_det = null;
this.ctx_gt = null;
this.ctx_det = null;
this.scale=2;
this.offset_x=0;
this.offset_y=0;
this.im_w=0;
this.im_h=0;
this.curr_im_w=0;
this.curr_im_h=0;
this.det_rect=-1;
this.gt_rect=-1;
this.draws=0;
//Mouse events on canvas
this.mm_point=null;
this.initial_point=null;
this.last_point=null;
this.mouse_clicked=false;
this.initial_offset=null;
this.image_loaded = false;
this.image_details_loaded=false;
this.sampleData = null;
var self = this;
this.load_sample_info = function(){
//web.pantalla_espera();
var urlInfo = "/sampleInfo/?m=" + getUrlParameter("m");
var extraParmasNames = ["file","eval","sample","gtv"];
for(var i=0;i<extraParmasNames.length;i++){
var parameterValue = getUrlParameter( extraParmasNames[i] );
if (parameterValue!= undefined){
urlInfo += "&" + extraParmasNames[i] + "=" + parameterValue;
}
}
$.get(urlInfo, function (data) {
visualization.sampleData = data;
visualization.load_visualization();
}, "json");
};
this.init_image_details = function(){
this.canvas_gt = document.createElement("canvas");
var dest = document.getElementById("div_canvas_gt");
dest.appendChild(this.canvas_gt);
this.canvas_gt.setAttribute("id","canvas_gt");
this.canvas_det = document.createElement("canvas");
var dest2 = document.getElementById("div_canvas_det");
dest2.appendChild(this.canvas_det);
this.canvas_det.setAttribute("id","canvas_det");
this.canvas_gt.width=$("#div_canvas_gt").width();
this.canvas_gt.height=$("#div_canvas_gt").height();
this.canvas_det.width=$("#div_canvas_gt").width();
this.canvas_det.height=$("#div_canvas_gt").height();
var self = this;
$("#canvas_gt").mousedown(function(e) {self.mousedown(e);});
$("#canvas_gt").mousemove(function(e) {self.mousemove(e);});
$("#canvas_gt").mouseup(function(e) {self.mouseup(e);});
$("#canvas_gt").mouseleave(function(e) {self.mouseleave(e);});
$("#canvas_gt").mousewheel(function(e, d) {self.mousewheel(e,d);});
$("#canvas_det").mousedown(function(e) {self.mousedown(e);});
$("#canvas_det").mousemove(function(e) {self.mousemove(e);});
$("#canvas_det").mouseup(function(e) {self.mouseup(e);});
$("#canvas_det").mouseleave(function(e) {self.mouseleave(e);});
$("#canvas_det").mousewheel(function(e, d) {self.mousewheel(e,d);});
this.ctx_gt = canvas_gt.getContext("2d");
this.ctx_det = canvas_det.getContext("2d");
this.ctx_gt.mozImageSmoothingEnabled = false;
this.ctx_gt.webkitImageSmoothingEnabled = false;
this.ctx_det.mozImageSmoothingEnabled = false;
this.ctx_det.webkitImageSmoothingEnabled = false;
this.scale = Math.min($("#canvas_gt").width()/this.im_w,$("#canvas_gt").height()/this.im_h );
setTimeout(function(){self.adapt_controls();},500);
};
this.adapt_controls = function(){
if(!this.image_details_loaded){
return;
}
var height = Math.max(220,$(window).height()-536);
$("#div_container_gt").css("height",height + "px");
$("#div_container_det").css("height",height + "px");
this.canvas_gt.width=$("#div_canvas_gt").width();
this.canvas_gt.height=$("#div_canvas_gt").height();
this.canvas_det.width=$("#div_canvas_gt").width();
this.canvas_det.height=$("#div_canvas_gt").height();
this.ctx_gt.mozImageSmoothingEnabled = false;
this.ctx_gt.webkitImageSmoothingEnabled = false;
this.ctx_det.mozImageSmoothingEnabled = false;
this.ctx_det.webkitImageSmoothingEnabled = false;
$("#div_container_method").css({"height": ($(window).height()-80)+ "px"});
this.table_sizes();
this.zoom_changed();
this.correct_image_offset();
this.draw();
};
this.mousemove = function(e){
var layer = this.getOffset(e);
this.mm_point = Array(layer.x,layer.y);
};
this.mousedown = function(e){
var layer = this.getOffset(e);
var mouseX = layer.x;
var mouseY = layer.y;
this.initial_point = Array(mouseX,mouseY);
this.initial_offset = Array(this.offset_x,this.offset_y);
this.mm_point = Array(mouseX,mouseY);
this.last_point = Array(mouseX,mouseY);
this.mouse_clicked = true;
this.refresh_canvas_position_on_mousemove();
};
this.mouseup = function(e){
this.mouse_clicked = false;
};
this.mouseleave = function(e){
this.mouse_clicked = false;
};
this.mousewheel = function(e,d){
var new_scale = this.scale + ((d>0)? this.scale*0.1 : -this.scale*0.1);
var point = this.mm_point;
var real_point = this.zoom_to_original(point);
var dx = point[0] - this.original_to_zoom_val(real_point[0]);
var dy = point[1] - this.original_to_zoom_val_y(real_point[1]);
this.offset_x= point[0] - real_point[0]*new_scale - dx;// - this.scale;
this.offset_y= point[1] - real_point[1]*new_scale - dy;// - this.scale;
this.scale = new_scale;
this.zoom_changed();
this.correct_image_offset();
this.draw();
e.preventDefault();
return false;
};
this.original_to_zoom = function(punt){
return Array(this.original_to_zoom_val(punt[0]),this.original_to_zoom_val_y(punt[1]));
};
this.original_to_zoom_val = function(x){
return Math.floor(x*this.scale + this.offset_x);
};
this.original_to_zoom_val_y = function(y){
return Math.floor(y*this.scale+this.offset_y);
};
this.zoom_to_original = function(punt){
return Array(this.zoom_to_original_val(punt[0]),this.zoom_to_original_val_y(punt[1]));
};
this.zoom_to_original_val = function(x){
return Math.floor((x-this.offset_x)/this.scale);
};
this.zoom_to_original_val_y = function(y){
//return Math.floor(y/this.scale-this.offsetY);
return Math.floor((y-this.offset_y)/this.scale);
};
this.zoom_changed = function(){
this.curr_im_w = this.im_w * this.scale;
this.curr_im_h = this.im_h * this.scale;
};
this.correct_image_offset = function(){
//Ensure that image position is correct and center image
if ( this.curr_im_w < this.canvas_gt.width){
this.offset_x = (this.canvas_gt.width - this.curr_im_w)/2;
}else{
if (this.offset_x>0) this.offset_x= 0;
if (this.offset_x < (this.canvas_gt.width - this.curr_im_w)) this.offset_x = this.canvas_gt.width - this.curr_im_w ;
}
if (this.curr_im_h < this.canvas_gt.height){
this.offset_y = ( this.canvas_gt.height - this.curr_im_h)/2;
}else{
if (this.offset_y>0) this.offset_y = 0;
if (this.offset_y< (this.canvas_gt.height-this.curr_im_h)) this.offset_y = this.canvas_gt.height - this.curr_im_h ;
}
};
this.refresh_canvas_position_on_mousemove = function(){
var dx = self.mm_point[0]-self.initial_point[0];
var dy = self.mm_point[1]-self.initial_point[1];
var ox = self.initial_offset[0] + dx;
var oy = self.initial_offset[1] + dy;
var co = self.correct_offset(ox,oy);
if (self.mouse_clicked){
if (co[0]!=self.offset_x ||co[1]!=self.offset_y) {
self.offset_x = self.initial_offset[0] + dx;
self.offset_y = self.initial_offset[1] + dy;
self.correct_image_offset();
self.draw();
}
setTimeout(self.refresh_canvas_position_on_mousemove,100);
}
};
this.correct_offset = function(ox,oy){
//Ensure that image position is correct and center image
if ( this.curr_im_w < this.canvas_gt.width){
ox = (this.canvas_gt.width - this.curr_im_w)/2;
}else{
if (ox>0) ox = 0;
if (ox< (this.canvas_gt.width - this.curr_im_w)) ox = this.canvas_gt.width - this.curr_im_w ;
}
if (this.curr_im_h < this.canvas_gt.height){
oy = (this.canvas_gt.height - this.curr_im_h)/2;
}else{
if (oy>0) oy = 0;
if (oy< (this.canvas_gt.height - this.curr_im_h)) oy = this.canvas_gt.height - this.curr_im_h ;
}
return Array(ox,oy);
};
this.getOffset = function(evt){
var el = evt.target,
x = y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
x += el.offsetLeft - el.scrollLeft;
y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
x = evt.clientX - x;
y = evt.clientY - y;
return {x: x, y: y};
};
this.table_sizes = function(){
$(".div_table").scroll(function(e){
var pos_y = $(this).scrollTop();
var pos_x = $(this).scrollLeft();
$(".div_table").not(this).scrollTop(pos_y).scrollLeft(pos_x);
});
};
this.writeText = function(ctx,bb,text){
var TL,TR,BL,BR;
if (bb.length == 8){
//bb has 8 points, we want to find TL,TR,BL,BR
//1st. sort points by Y
var p1 = {"x":bb[0],"y":bb[1]};
var p2 = {"x":bb[2],"y":bb[3]};
var p3 = {"x":bb[4],"y":bb[5]};
var p4 = {"x":bb[6],"y":bb[7]};
var pointsList = [p1,p2,p3,p4];
pointsList = pointsList.sort(function sortPointsByY(a,b){
if (a.y<b.y){
return 1;
}else if (a.y==b.y){
return 0;
}else{
return -1;
}
});
if (pointsList[0].x < pointsList[1].x){
TL = pointsList[0];
TR = pointsList[1];
}else{
TL = pointsList[1];
TR = pointsList[0];
}
if (pointsList[2].x < pointsList[3].x){
BL = pointsList[2];
BR = pointsList[3];
}else{
BL = pointsList[3];
BR = pointsList[2];
}
}else{
TL = {"x" : bb[0] , "y":bb[3]};
TR = {"x" : bb[2] , "y":bb[3]};
BL = {"x" : bb[0] , "y":bb[1]};
BR = {"x" : bb[2] , "y":bb[1]};
}
var height = Math.round(this.original_to_zoom_val_y(parseInt( Math.min(TL.y,TR.y) )+1) - this.original_to_zoom_val_y(parseInt(Math.max(BL.y,BR.y)))) - 3;
var width = Math.round(this.original_to_zoom_val(parseInt( Math.min(TR.x,BR.x) )+1) - this.original_to_zoom_val(parseInt(Math.max(TL.x,BL.x)))) - 3;
var fontSize = height;
if(fontSize<6){
fontSize=6;
}
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.font= fontSize + "px Verdana";
var metrics = ctx.measureText(text);
var textWidth = metrics.width;
while(textWidth>width && fontSize>6){
fontSize--;
ctx.font= fontSize + "px Verdana";
metrics = ctx.measureText(text);
textWidth = metrics.width;
}
ctx.fillText(text,this.original_to_zoom_val(parseInt(BL.x)) + 3 , this.original_to_zoom_val_y(parseInt(BL.y)) + fontSize);
};
};
ClassVisualization.prototype.load_visualization = function(){
var urlGtImg = "/image/?sample=" + getUrlParameter("sample");
var extraParmasNames = ["ch","task","gtv"];
for(var i=0;i<extraParmasNames.length;i++){
var parameterValue = getUrlParameter( extraParmasNames[i] );
if (parameterValue!= undefined){
urlGtImg += "&" + extraParmasNames[i] + "=" + parameterValue;
}
}
$("#div_sample").append("<img src='" + urlGtImg + "'>");
for (var key in visualization.sampleData){
$("#div_sample").append("<br>" + key + " = " + visualization.sampleData[key]);
}
//web.tancar_pantalla_espera();
};
ClassVisualization.prototype.draw = function(){
};
var visualization = new ClassVisualization();
$(document).ready(function () {
visualization.load_sample_info();
});
$(window).resize(function(){
visualization.adapt_controls();
});
================================================
FILE: static_custom/contents.txt
================================================
This folder are designed to store CSS and JS files for a custom visualization.
================================================
FILE: static_custom/visualization_TL_iou.css
================================================
#div_canvas_gt{
position: absolute;
left: 0px;
right: 0px;
top: 30px;
bottom: 0px;
}
#div_canvas_det{
position: absolute;
left: 0px;
right: 0px;
top: 30px;
bottom: 0px;
}
div.container_canvas{
width: 50%;
vertical-align: top;
position: relative;
height: 400px;
display: inline-block;
border: 1px solid #999;
box-sizing: border-box;
}
div.container_canvas h3, #div_sample_info h3{
margin: 0px;
padding: 0px 10px;
background-color: #eee;
border-bottom: 1px solid #999;
line-height: 29px;
}
#img_gt_image2{
display: none;
}
#div_sample_info{
display: flex;
}
#div_matrices{
flex-grow: 2;
}
#div_recall{
flex-grow: 2;
}
#div_precision{
flex-grow: 2;
}
#div_char{
flex-grow: 2;
}
.div_table{
display: block;
}
.div_table{
display: block;
box-sizing: border-box;
height: 300px;
width: 400px;
overflow: auto;
}
.div_log{
height: 300px;
font-size: 15px;
overflow: auto;
}
.div_table table{
border-collapse: collapse;
border-style: hidden;
font-size: 12px;
}
.div_table td{
text-align: center;
width:24px;
}
.div_table td, th{
border: 1px solid black;
}
.div_table td.row_selected{
background-color: #ccc;
}
.div_table td.col_selected{
background-color: #ccc;
}
.div_table td.selected{
background-color: #888;
}
.div_table td.OO{
background-color: rgb(0,190,0);
}
.div_table td.NO{
background-color: rgb(38,148,232);
color: #c8e6c9;
}
.div_table td.OO.green{
color: #006E24;
}
================================================
FILE: static_custom/visualization_TL_iou.js
================================================
/* global web, visualization, ClassVisualization */
ClassVisualization.prototype.load_visualization = function(){
var self = this;
var sampleData = this.sampleData;
var urlImg = "/image/?ch=" + getUrlParameter("ch") + "&task=" + getUrlParameter("task") + "&sample=" + getUrlParameter("sample") + ">v=" + getUrlParameter("gtv");
var template = "<div class='im_filters'><input type='checkbox' checked='checked' id='chk_image'><label for='chk_image'>Show Image</label></div>"+
"<div class='container_canvas'>" +
"<h3>Ground Truth</h3>" +
"<div id='div_canvas_gt'></div>" +
"</div>"+
"<div class='container_canvas'>" +
"<h3>Detection</h3>" +
"<div id='div_canvas_det'></div>" +
"</div>"+
"<img id='img_gt_image2'>"+
"<div id='div_sample_info'>"+
"<div id='div_recall'><div class='div_table'><h3>Recall</h3>loading..</div></div>"+
"<div id='div_precision'><div class='div_table'><h3>Precision</h3>loading..</div></div>"+
"<div id='div_char'><div class='div_table'><h3>Character score</h3>loading..</div></div></div>"+
"<div id='div_logs'><h3>Log</h3><span class='red'>loading..</span></div>";
$("#div_sample").html(template);
if(!this.image_details_loaded){
this.image_details_loaded=true;
this.init_image_details();
}
this.image_loaded = false;
this.draw();
$("#chk_image").change(function(){
self.draw();
});
$("#img_gt_image2").attr("src",urlImg).one("load",function(){
self.image_loaded = true;
self.im_w = this.width;
self.im_h = this.height;
self.scale = Math.min($("#div_canvas_gt").width()/self.im_w,$("#div_canvas_det").height()/self.im_h );
self.zoom_changed();
self.correct_image_offset();
self.draw();
});
var numGt = sampleData.gtPolPoints==undefined? 0 : sampleData.gtPolPoints.length;
var numDet = sampleData.detPolPoints==undefined? 0 : sampleData.detPolPoints.length;
var html_recall = "";
var html_precision = "";
var stylesMat = new Array();
for ( var j=0;j<numGt;j++){
stylesMat[j] = new Array();
for ( var i=0;i<numDet;i++){
stylesMat[j][i] = "value";
}
}
sampleData.gtTypes = new Array();
sampleData.detTypes = new Array();
for ( var j=0;j<numGt;j++){
var gtDontCare = $.inArray(j,sampleData.gtDontCare)>-1;
sampleData.gtTypes.push( gtDontCare? 'DC' : 'NM' );
}
for ( var j=0;j<numDet;j++){
var detDontCare = $.inArray(j,sampleData.detDontCare)>-1;
sampleData.detTypes.push( detDontCare? 'DC' : 'NM' );
}
if (sampleData.pairs!=undefined){
for ( var k=0;k<sampleData.pairs.length;k++){
var pair = sampleData.pairs[k];
var gts = new Array();
var dets = new Array();
if(pair.gt.length==undefined){
gts.push(pair.gt);
}else{
gts = pair.gt;
}
if(pair.det.length==undefined){
dets.push(pair.det);
}else{
dets = pair.det;
}
for(var i=0;i<gts.length;i++){
for(var j=0;j<dets.length;j++){
stylesMat[gts[i]][dets[j]] += " " + "OO";
sampleData.gtTypes[gts[i]] = "OO";
sampleData.detTypes[dets[j]] = "OO";
}
}
}
}
if(numDet>100){
html_recall = "<p class='red'>The algorithm has detected more than 100 bounding boxes, the visualization are not posible</p></p>";
}else{
var html_recall = "<table><thead><tr><th>GT / Det</th>";
for ( var i=0;i<numDet;i++){
var detDontCare = $.inArray(i,sampleData.detDontCare)>-1;
html_recall += "<th style='" + (detDontCare? "" : "font-weight:bold;") + "'>#" + i + "</th>";
}
html_recall += "</tr></thead><tbody id='tbody_recall'>";
for ( var j=0;j<numGt;j++){
var gtDontCare = $.inArray(j,sampleData.gtDontCare)>-1;
html_recall += "<tr>";
html_recall += "<td style='" + (gtDontCare? "" : "font-weight:bold;") + "'>#" + j + "</td>";
for ( var i=0;i<numDet;i++){
var recallClass = (sampleData.recallMat[j][i]>=sampleData.evaluationParams.AREA_RECALL_CONSTRAINT ? ' green' : ' red' );
html_recall += "<td data-col='" + i + "' data-row='" + j + "' class='" + stylesMat[j][i] + " " + recallClass + "'>" + Math.round(sampleData.recallMat[j][i]*10000)/100 + "</td>";
}
html_recall += "</tr>";
}
html_recall += "</tbody></table>";
}
$("#div_recall").html("<div class='div_table'><h3>Recall</h3>" + html_recall + "</div>");
if(numDet>100){
html_precision = "<p class='red'>The algorithm has detected more than 100 bounding boxes, the visualization are not posible</p></p>";
}else{
var html_precision = "<table><thead><tr><th>GT / Det</th>";
for ( var i=0;i<numDet;i++){
var detDontCare = $.inArray(i,sampleData.detDontCare)>-1;
html_precision += "<th style='" + (detDontCare? "" : "font-weight:bold;") + "'>#" + i + "</th>";
}
html_precision += "</tr></thead><tbody id='tbody_precision'>";
for ( var j=0;j<numGt;j++){
var gtDontCare = $.inArray(j,sampleData.gtDontCare)>-1;
html_precision += "<tr>";
html_precision += "<td style='" + (gtDontCare? "" : "font-weight:bold;") + "'>#" + j + "</td>";
for ( var i=0;i<numDet;i++){
var precisionClass = (sampleData.precisionMat[j][i]>=sampleData.evaluationParams.AREA_PRECISION_CONSTRAINT ? ' green' : ' red' );
html_precision += "<td data-col='" + i + "' data-row='" + j + "' class='" + stylesMat[j][i] + " " + precisionClass + "'>" + Math.round(sampleData.precisionMat[j][i]*10000)/100 + "</td>";
}
html_precision += "</tr>";
}
html_precision += "</tbody></table>";
}
$("#div_precision").html("<div class='div_table'><h3>Precision</h3>" + html_precision + "</div>");
if(numDet>100){
html_char = "<p class='red'>The algorithm has detected more than 100 bounding boxes, the visualization are not posible</p></p>";
}else{
var html_char = "<table><thead><tr><th>GT / Det</th>";
for ( var i=0;i<numDet;i++){
var detDontCare = $.inArray(i,sampleData.detDontCare)>-1;
html_char += "<th style='" + (detDontCare? "" : "font-weight:bold;") + "'>#" + i + "</th>";
}
html_char += "<th style='" + "font-weight:bold;" + "'>" + "Rec Score" + "</th>";
html_char += "</tr></thead><tbody id='tbody_char'>";
for ( var j=0;j<numGt;j++){
var gtDontCare = $.inArray(j,sampleData.gtDontCare)>-1;
html_char += "<tr>";
html_char += "<td style='" + (gtDontCare? "" : "font-weight:bold;") + "'>#" + j + "</td>";
for ( var i=0;i<numDet;i++){
var charClass = 'green';
html_char += "<td data-col='" + i + "' data-row='" + j + "' class='" + stylesMat[j][i] + " " + charClass + "'>" + sampleData.charCounts[j][i] + "</td>";
}
html_char += "<td data-col='" + (i+1) + "' data-row='" + j + "' class='" + "value" + " " + charClass + "'>" + sampleData.recallScore[j] + "</td>";
html_char += "</tr>";
}
html_char += "<tr>";
html_char += "<tr><td style='" + "font-weight:bold" + "'>" + "Prec Score" + "</td>";
for ( var i=0;i<numDet;i++){
var charClass = 'green';
html_char += "<td data-col='" + i + "' data-row='" + (j+1) + "' class='" + "value" + " " + charClass + "'>" + sampleData.precisionScore[i] + "</td>";
}
html_char += "<td data-col='" + (i+1) + "' data-row='" + (j+1) + "' class='" + "value" + " " + charClass + "'>" + "</td>";
html_char += "</tr>";
html_char += "</tbody></table>";
}
$("#div_char").html("<div class='div_table'><h3>Character score</h3>" + html_char + "</div>");
var evalLog = sampleData.evaluationLog;
if (evalLog==undefined){
evalLog = "";
}else{
evalLog = evalLog.replace(/\n/g, "<br/>")
}
$("#div_logs").html("<div class='div_log'><h3>Log</h3>" + evalLog + "</div>");
this.table_sizes();
$("#div_matrices tbody td").mouseover(function(){
self.det_rect = -1;
self.gt_rect = -1;
if ( $(this).attr("data-col")!=undefined && $(this).attr("data-row")!=undefined){
self.det_rect = $(this).attr("data-col");
self.gt_rect = $(this).attr("data-row");
$("#div_matrices tbody td").removeClass("selected");
$("#div_matrices tbody td").removeClass("col_selected").removeClass("row_selected");
$(this).addClass("selected");
$("td[data-col=" + $(this).attr("data-col") + "]").addClass("col_selected");
$("td[data-row=" + $(this).attr("data-row") + "]").addClass("row_selected");
}
self.draw();
});
$("#div_recall tbody td").mouseover(function(){
self.det_rect = -1;
self.gt_rect = -1;
if ( $(this).attr("data-col")!=undefined && $(this).attr("data-row")!=undefined){
self.det_rect = $(this).attr("data-col");
self.gt_rect = $(this).attr("data-row");
$("#div_recall tbody td").removeClass("selected");
$("#div_recall tbody td").removeClass("col_selected").removeClass("row_selected");
$(this).addClass("selected");
$("td[data-col=" + $(this).attr("data-col") + "]").addClass("col_selected");
$("td[data-row=" + $(this).attr("data-row") + "]").addClass("row_selected");
}
self.draw();
});
$("#div_precision tbody td").mouseover(function(){
self.det_rect = -1;
self.gt_rect = -1;
if ( $(this).attr("data-col")!=undefined && $(this).attr("data-row")!=undefined){
self.det_rect = $(this).attr("data-col");
self.gt_rect = $(this).attr("data-row");
$("#div_precision tbody td").removeClass("selected");
$("#div_precision tbody td").removeClass("col_selected").removeClass("row_selected");
$(this).addClass("selected");
$("td[data-col=" + $(this).attr("data-col") + "]").addClass("col_selected");
$("td[data-row=" + $(this).attr("data-row") + "]").addClass("row_selected");
}
self.draw();
});
$("#div_char tbody td").mouseover(function(){
self.det_rect = -1;
self.gt_rect = -1;
if ( $(this).attr("data-col")!=undefined && $(this).attr("data-row")!=undefined){
self.det_rect = $(this).attr("data-col");
self.gt_rect = $(this).attr("data-row");
$("#div_char tbody td").removeClass("selected");
$("#div_char tbody td").removeClass("col_selected").removeClass("row_selected");
$(this).addClass("selected");
$("td[data-col=" + $(this).attr("data-col") + "]").addClass("col_selected");
$("td[data-row=" + $(this).attr("data-row") + "]").addClass("row_selected");
}
self.draw();
});
this.draw();
};
ClassVisualization.prototype.draw = function(){
this.ctx_gt.clearRect(0,0,this.canvas_gt.width,this.canvas_gt.height);
this.ctx_det.clearRect(0,0,this.canvas_gt.width,this.canvas_gt.height);
if(!this.image_loaded){
this.ctx_det.fillStyle = "rgba(255,0,0,1)";
this.ctx_det.font= "12px Verdana";
this.ctx_det.fillText("Loading image..", 20,60);
this.ctx_gt.fillStyle = "rgba(255,0,0,1)";
this.ctx_gt.font= "12px Verdana";
this.ctx_gt.fillText("Loading image..", 20,60);
return;
}
if( $("#chk_image").is(":checked")){
this.ctx_gt.drawImage(img_gt_image2,this.offset_x,this.offset_y,this.curr_im_w,this.curr_im_h);
}else{
this.ctx_gt.strokeStyle = "rgba(0,0,0,1)";
this.ctx_gt.strokeRect(this.offset_x,this.offset_y,this.curr_im_w,this.curr_im_h);
}
if (this.sampleData==null){
this.ctx_gt.fillStyle = "rgba(255,0,0,1)";
this.ctx_gt.font= "12px Verdana";
this.ctx_gt.fillText("Loading method..", 20,60);
this.ctx_det.fillStyle = "rgba(255,0,0,1)";
this.ctx_det.font= "12px Verdana";
this.ctx_det.fillText("Loading method..", 20,60);
return;
}else{
if (this.sampleData.gtPolPoints==undefined){
this.sampleData.gtPolPoints = [];
}
}
for (var i=0;i<this.sampleData.gtPolPoints.length;i++){
//if (bb.id_s==current_id_submit){
var opacity = 0.6;//(gt_rect==bb.i)? "0.9" : "0.6";
var bb = this.sampleData.gtPolPoints[i];
var type = this.sampleData.gtTypes[i];
var gtDontCare = $.inArray(i,this.sampleData.gtDontCare)>-1;
if(type=="DC"){
this.ctx_gt.fillStyle = "rgba(50,50,50," + opacity + ")";
}else if (type=="OO"){
this.ctx_gt.fillStyle = "rgba(0,190,0," + opacity + ")";
}else if (type=="NO"){
this.ctx_gt.fillStyle = "rgba(38,148,232," + opacity + ")";
}else{
this.ctx_gt.fillStyle = "rgba(255,0,0," + opacity + ")";
}
if (bb.length==4){
var x = this.original_to_zoom_val(parseInt(bb[0]));
var y = this.original_to_zoom_val_y(parseInt(bb[1]));
var x2 = this.original_to_zoom_val(parseInt(bb[2]));
var y2 = this.original_to_zoom_val_y(parseInt(bb[3]));
var w = x2-x+1;
var h = y2-y+1;
this.ctx_gt.fillRect(x,y,w,h);
if(this.gt_rect==i){
this.ctx_gt.lineWidth = 2;
this.ctx_gt.strokeStyle = 'red';
this.ctx_gt.strokeRect(x,y,w,h);
}
}else{
this.ctx_gt.beginPath();
this.ctx_gt.moveTo(this.original_to_zoom_val(parseInt(bb[0])), this.original_to_zoom_val_y(parseInt(bb[1])));
this.ctx_gt.lineTo(this.original_to_zoom_val(parseInt(bb[2])+1), this.original_to_zoom_val_y(parseInt(bb[3])));
this.ctx_gt.lineTo(this.original_to_zoom_val(parseInt(bb[4])+1), this.original_to_zoom_val_y(parseInt(bb[5])+1));
this.ctx_gt.lineTo(this.original_to_zoom_val(parseInt(bb[6])), this.original_to_zoom_val_y(parseInt(bb[7])+1));
this.ctx_gt.closePath();
this.ctx_gt.fill();
//ctx_gt.fillRect( original_to_zoom_val(parseInt(bb.x)),original_to_zoom_val_y(parseInt(bb.y)),parseInt(bb.w)*scale,parseInt(bb.h)*scale);
if(this.gt_rect==i){
this.ctx_gt.lineWidth = 2;
this.ctx_gt.strokeStyle = 'red';
this.ctx_gt.stroke();
}
}
//pseudo character centers
for (var k=0;k<this.sampleData.gtCharPoints[i].length;k++){
var center = this.sampleData.gtCharPoints[i][k];
var count = this.sampleData.gtCharCounts[i][k];
if(count==1){
this.ctx_gt.fillStyle = "rgba(0,190,0," + opacity + ")";
}else{
this.ctx_gt.fillStyle = "rgba(255,0,0," + opacity + ")";
}
var x = this.original_to_zoom_val(center[0]);
var y = this.original_to_zoom_val_y(center[1]);
this.ctx_gt.beginPath();
this.ctx_gt.arc(x, y, 5, 0, 2 * Math.PI, false);
this.ctx_gt.closePath();
this.ctx_gt.fill();
if(this.gt_rect==i){
this.ctx_gt.lineWidth = 2;
this.ctx_gt.strokeStyle = 'red';
this.ctx_gt.stroke();
}else{
this.ctx_gt.lineWidth = 2;
this.ctx_gt.strokeStyle = "rgba(50,50,50," + opacity + ")";
this.ctx_gt.stroke();
}
}
//}
}
this.ctx_det.clearRect(0,0,this.canvas_gt.width,this.canvas_gt.height);
if( $("#chk_image").is(":checked")){
this.ctx_det.drawImage(img_gt_image2,this.offset_x,this.offset_y,this.curr_im_w,this.curr_im_h);
}else{
this.ctx_det.strokeStyle = "rgba(0,0,0,1)";
this.ctx_det.strokeRect(this.offset_x,this.offset_y,this.curr_im_w,this.curr_im_h);
}
for (var i=0;i<this.sampleData.detPolPoints.length;i++){
var bb = this.sampleData.detPolPoints[i];
var type = this.sampleData.detTypes[i];
var opacity = 0.6;//(det_rect==bb.i)? "0.9" : "0.6";
if(type=="DC"){
this.ctx_det.fillStyle = "rgba(50,50,50," + opacity + ")";
}else if (type=="OO"){
this.ctx_det.fillStyle = "rgba(0,190,0," + opacity + ")";
}else if (type=="NO"){
this.ctx_det.fillStyle = "rgba(38,148,232," + opacity + ")";
}else{
this.ctx_det.fillStyle = "rgba(255,0,0," + opacity + ")";
}
if (bb.length==4){
var x = this.original_to_zoom_val(parseInt(bb[0]));
var y = this.original_to_zoom_val_y(parseInt(bb[1]));
var x2 = this.original_to_zoom_val(parseInt(bb[2]));
var y2 = this.original_to_zoom_val_y(parseInt(bb[3]));
var w = x2-x+1;
var h = y2-y+1;
this.ctx_det.fillRect(x,y,w,h);
if(this.det_rect==i){
this.ctx_det.lineWidth = 2;
this.ctx_det.strokeStyle = 'red';
this.ctx_det.strokeRect(x,y,w,h);
}
}else{
this.ctx_det.beginPath();
this.ctx_det.moveTo(this.original_to_zoom_val(parseInt(bb[0])), this.original_to_zoom_val_y(parseInt(bb[1])));
this.ctx_det.lineTo(this.original_to_zoom_val(parseInt(bb[2])+1), this.original_to_zoom_val_y(parseInt(bb[3])));
this.ctx_det.lineTo(this.original_to_zoom_val(parseInt(bb[4])+1), this.original_to_zoom_val_y(parseInt(bb[5])+1));
this.ctx_det.lineTo(this.original_to_zoom_val(parseInt(bb[6])), this.original_to_zoom_val_y(parseInt(bb[7])+1));
this.ctx_det.closePath();
this.ctx_det.fill();
//ctx_gt.fillRect( original_to_zoom_val(parseInt(bb.x)),original_to_zoom_val_y(parseInt(bb.y)),parseInt(bb.w)*scale,parseInt(bb.h)*scale);
if(this.det_rect==i){
this.ctx_det.lineWidth = 2;
this.ctx_det.strokeStyle = 'red';
this.ctx_det.stroke();
}
}
}
this.draws++;
};
================================================
FILE: views/index.tpl
================================================
% import json
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
<meta charset="utf-8" />
<link rel='stylesheet' href='{{ url('static', path='style.css') }}' />
<script type="text/javascript" src="{{ url('static', path='jquery-1.8.2.min.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url('static', path='jquery.form-3.51.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url('static', path='ranking.js') }}" charset="utf-8"></script>
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
</head>
<body>
<h1><a href="http://rrc.cvc.uab.es/" target="_blank"><img id='logo' src='/static/CVC.png'></a>{{title}}</h1>
<div class='breadcrumbs'>
Methods
% if len(subm_data)>0:
<button class='ml20 button-error pure-button' onclick="delete_methods()">Delete all methods</button> <span class="small">(You can also delete all methods by supressing all files from the output folder)</span>
% end
<a class="right" href="/exit">Exit</a>
</div>
<form action="/evaluate" method="post" enctype="multipart/form-data">
Upload your method:
<label for='inp_title'>Title:</label><input type='text' name='title' maxlength="50" id='inp_title'>
File:
<input type="file" name="submissionFile" />
% for k,v in submit_params.items():
<label for='inp_{{k}}'>{{v['title']}}: </label>
<select id='inp_{{k}}' name='{{k}}'>
% for option in v['values']:
<option value='{{option['value']}}'>{{option['name']}}</option>
% end
</select>
% end
<button class="pure-button pure-button-primary" type="button" onclick="upload_subm()" >Evaluate</button>
</form>
<p class='info'>Dataset files of this Standalone: <a href='gt/images.zip'>Images</a> - <a href='gt/gt.{{extension}}'>Ground Truth</a> <button class="ml20 pure-button pure-button-secondary" type="button" onclick="instructions()" >See upload instructions..</button></p>
<%
if len(subm_data)>0:
graphicRows = []
graphic2Rows = []
%>
<table class='results ib'>
<thead>
<th>Method</th>
<th>Submit date</th>
<%
row = ["'Title'"]
row2 = ["'Title'"]
num_column = -1
num_column_order = -1
show2ndGraphic = False
for k,v in method_params.items():
num_column+=1
if v['grafic'] == "1":
row.append("'" + v['long_name'] + "'")
elif v['grafic'] == "2":
row2.append("'" + v['long_name'] + "'")
end
if v['order'] != "":
if v['grafic'] == "1":
num_column_order = num_column
sort_name = k
sort_name_long = v['long_name']
sort_order = v['order']
sort_format = v['format']
sort_type = v['type']
elif v['grafic'] == "2":
show2ndGraphic = True
sort2_name = k
sort2_name_long = v['long_name']
sort2_order = v['order']
sort2_format = v['format']
sort2_type = v['type']
end
end
%>
<th>{{v['long_name']}}</th>
% end
<th></th>
% graphicRows.append("[" + ','.join(row) + "]")
% graphic2Rows.append("[" + ','.join(row2) + "]")
</thead>
<tbody>
<%
methodsData = []
for id, title, date, methodResultJson in subm_data:
methodData = [id, title, date]
methodResult = json.loads(methodResultJson)
for k,v in method_params.items():
methodData.append(methodResult[k])
end
methodsData.append(methodData)
end
methodsData = sorted(methodsData, key=lambda methodData: methodData[2+num_column_order],reverse=sort_order=="desc")
for methodData in methodsData:
id = methodData[0]
title = methodData[1]
date = methodData[2]
%>
<tr>
<td><a class='methodname' href='method/?m={{id}}'>{{id}}: <span class="title">{{title}}</span></a></td>
<td><a href='method/?m={{id}}'>{{date}}</a></td>
<%
row = ["'" + title.replace("'","\'") + "'"]
row2 = ["'" + title.replace("'","\'") + "'"]
index=0
for k,v in method_params.items():
colValue = methodData[3+index]
if v['format'] == "perc" :
value = str(round(colValue*100,2)) + " %"
graphicValue = "{v:" + str(colValue) + ", f:'" + str(round(colValue*100,2)) + " %'}";
elif v['type'] == "double" :
value = str(round(colValue*100,2))
graphicValue = "{v:" + str(colValue) + ", f:'" + str(round(colValue*100,2)) + "'}";
else:
value = colValue
graphicValue = colValue
end
if v['grafic'] == "1":
row.append(graphicValue)
elif v['grafic'] == "2":
row2.append(graphicValue)
end
%>
<td>{{value}}</td>
<%
index += 1
end %>
<td><button class="mr5 pure-button" onclick="edit_method({{id}},this)">edit</button><button class="pure-button button-error" onclick="delete_method({{id}})">x</button></td>
% graphicRows.append("[" + ','.join(row) + "]")
% graphic2Rows.append("[" + ','.join(row2) + "]")
</tr>
<% end
graphicData = "[" + ','.join(graphicRows) + "]"
base64Data = graphicData.encode('utf-8')
graphic2Data = "[" + ','.join(graphic2Rows) + "]"
base64Data2 = graphic2Data.encode('utf-8')
%>
</tbody>
</table>
<input type="hidden" id='graphic' value='{{base64Data}}'>
<input type="hidden" id='graphic-sort' value='{{sort_name_long}}'>
<input type="hidden" id='graphic-type' value='{{sort_type}}'>
<input type="hidden" id='graphic-format' value='{{sort_format}}'>
<div id="div_rankings">
<div id='div_ranking_1' style='overflow:hidden;display:inline-block;' class='ib'></div>
% if show2ndGraphic:
<input type="hidden" id='graphic-gr2' value='{{base64Data2}}'>
<input type="hidden" id='graphic-gr2-sort' value='{{sort2_name_long}}'>
<input type="hidden" id='graphic-gr2-type' value='{{sort2_type}}'>
<input type="hidden" id='graphic-gr2-format' value='{{sort2_format}}'>
<div id='div_ranking_2' style='overflow:hidden;display:inline-block;' class='ib'></div>
% end
</div>
% else:
<p class='info'>Upload your methods to see the method's ranking. </p>
% end
<div id='div_instructions' class='hidden'><div class='wrap'><button class='close pure-button button-error'>close</button><h1>Upload instructions</h1>
<p class='info'>Note that the following instructions are for the Test Dataset, the example links may not work here if the dataset is not the Test Set.</p>
{{ !instructions }}
</div></div>
</body>
</html>
================================================
FILE: views/method.tpl
================================================
<%
import json
import math
import web
%>
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
<meta charset="utf-8" />
<link rel='stylesheet' href='{{ url('static', path='style.css') }}' />
</head>
<body>
% submitId, methodTitle, submitDate, methodResultJson = subm_data
<h1><a href="http://rrc.cvc.uab.es/" target="_blank"><img id='logo' src='/static/CVC.png'></a>{{title}}</h1>
<div class='breadcrumbs'>
<a href='/'>Methods</a> > {{methodTitle}}
</div>
% result = json.loads(results.read('method.json'))
% if result==None:
<h2>Submit your method</h2>
% elif result['calculated']==False:
<h2>The method has not been calculated</h2>
<p>{{result['Message']}}</p>
% else:
<div class="summary">
<h2>Method summary</h2>
<p>Title: <strong>{{methodTitle}}</strong> [{{submitId}}]</p>
<p>Submit date: {{submitDate}}</p>
<% for k,v in method_params.items():
colValue = result['method'][k]
if v['format'] == "perc" :
value = str(round(colValue*100,2)) + " %"
elif v['type'] == "double" :
value = str(round(colValue*100,2))
else:
value = colValue
end
%>
<p>{{v['long_name']}}: <strong>{{value}}</strong></p>
% end
</div>
<div class='navigation'>
%num_pages = int(math.ceil(float(len(images)) / 20))
% if page>1:
<a class="pure-button button-secondary" href='?m={{submitId}}&p={{page-1}}'>< previous</a>
% end
<span class='current'>Page {{page}} of {{num_pages}}</span>
% if page<num_pages:
<a class="pure-button button-secondary" href='?m={{submitId}}&p={{page+1}}'>next ></a>
% end
</div>
<div class="samples_list">
<%
for index, name in enumerate(images[(page-1)*20:page*20]):
sampleId = web.image_name_to_id(name)
values = json.loads(results.read( sampleId + '.json'))
sample = (page-1)*20+index+1
%>
<div class='sample'>
<a href='/sample/?m={{submitId}}&sample={{str(sample)}}'><img src='/image_thumb/?c={{acronym}}&sample={{str(sample)}}' alt='{{name}}'></a>
<p><a href='/sample/?m={{submitId}}&sample={{str(sample)}}'>Sample: {{ str(sample)}}</a></p>
<p><a href='/sample/?m={{submitId}}&sample={{str(sample)}}'>ID: {{sampleId}}</a></p>
<% for k,v in sample_params.items():
colValue = values[k]
if v['format'] == "perc" :
value = str(round(colValue*100,2)) + " %"
elif v['type'] == "double" :
value = str(round(colValue*100,2))
else:
value = colValue
end
%>
<p>{{v['long_name']}}: <strong>{{value}}</strong></p>
% end
</div>
<%
end
results.close()
%>
</div>
% end
</body>
</html>
================================================
FILE: views/sample.tpl
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
<meta charset="utf-8" />
<script type="text/javascript" src="{{ url('static', path='jquery-1.8.2.min.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url('static', path='jquery-ui.min.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url('static', path='jquery-mousewheel.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url('static', path='visualization_default.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url('static', path='funcs.js') }}" charset="utf-8"></script>
<link rel='stylesheet' href='{{ url('static', path='style.css') }}' />
<link rel='stylesheet' href='{{ url('static', path='jquery-ui.min.css') }}' />
<link rel='stylesheet' href='{{ url('static', path='visualization.css') }}' />
<script type="text/javascript" src="{{ url('static_custom', path='visualization_TL_iou.js') }}" charset="utf-8"></script>
<link rel='stylesheet' href='{{ url('static_custom', path='visualization_TL_iou.css') }}' />
</head>
<body>
<h1><a href="http://rrc.cvc.uab.es/" target="_blank"><img id='logo' src='/static/CVC.png'></a>{{title}}</h1>
% submitId, methodTitle, submitDate, methodResultJson = subm_data
% import math
% page = 1
% if int(sample)>1:
%page = (0 if sample % 20 == 0 else 1) + int(math.ceil(sample/20))
% end
<div class="breadcrumbs">
<a href='/'>Methods</a> >
<a href="/method/?m={{submitId}}&p={{page}}" style='margin-right: 40px;'>{{methodTitle}}</a>
% if int(sample)>1:
<a class="pure-button button-secondary" href="/sample/?m={{submitId}}&sample={{int(sample)-1}}">< previous</a>
% end
Sample {{sample}} of {{num_samples}}
% if int(sample) < int(num_samples):
<a class="pure-button button-secondary" href="/sample/?m={{submitId}}&sample={{int(sample)+1}}">next ></a>
% end
</div>
<div id='div_comparation'>
<table class='sample_methods'>
<thead>
<th>Method</th>
<%
num_column = -1
num_column_order = -1
for k,v in sample_params.items():
num_column+=1
if v['order'] != "":
num_column_order = num_column
sort_order = v['order']
end
%>
<th>{{v['long_name']}}</th>
% end
</thead>
<tbody>
<%
samplesData = []
for row in samplesValues:
sampleData = [row['id'],row['title']]
for k,v in sample_params.items():
sampleData.append(row[k])
end
samplesData.append(sampleData)
end
samplesData = sorted(samplesData, key=lambda sample: sample[num_column_order],reverse=sort_order=="desc")
for row in samplesData:
methodClass = "current" if row[0]==submitId else "other"
%>
<tr class="{{methodClass}}">
<td>{{row[1]}}</td>
<% index = 2 #omit fields id,title
for k,v in sample_params.items():
colValue = row[index]
if v['format'] == "perc" :
value = str(round(colValue*100,2)) + " %"
elif v['type'] == "double" :
value = str(round(colValue*100,2))
else:
value = colValue
end
index = index+1
%>
<td>{{value}}</td>
% end
</tr>
% end
</tbody>
</table>
</div>
<div id='div_sample'></div>
</body>
</html>
================================================
FILE: views/upload.tpl
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{title}}</title>
<link rel='stylesheet' href='{{ url('static', path='style.css') }}' />
</head>
<body>
<h1><a href="http://rrc.cvc.uab.es/" target="_blank"><img id='logo' src='/static/CVC.png'></a>{{title}}</h1>
<div class='breadcrumbs'>
<a href='/'>Methods</a>
</div>
% if resDict['calculated']==False:
<h2>The method has not been calculated</h2>
<p>{{resDict['Message']}}</p>
%else:
<h2>The method has been calculated</h2>
<p>See the <a href='/method/?m={{id}}'>method results</a></p>
% end
</body>
</html>
================================================
FILE: web.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
sys.path.append('./')
import json
from io import BytesIO
import zipfile
import re
from datetime import datetime
import importlib
import sqlite3
import rrc_evaluation_funcs
from config.config import *
try:
from bottle import route, run, request, static_file, url, template, TEMPLATE_PATH,HTTPResponse,redirect
except ImportError:
print("""Required module not found: Bottle. Installation: pip install --user bottle""")
sys.exit(-1)
try:
from PIL import Image
except ImportError:
print("""Required module not found: Pillow. Installation: pip install --user Pillow""")
sys.exit(-1)
TEMPLATE_PATH.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "views")))
def image_name_to_id(name):
# m = re.match(image_name_to_id_str,name)
# if m == None:
# return False
# id = m.group(1)
id = name.replace('.jpg', '').replace('.png', '').replace('.gif', '').replace('.bmp', '')
return id
def get_sample_id_from_num(num):
imagesFilePath = os.path.dirname(os.path.abspath(__file__)) + "/gt/images.zip"
archive = zipfile.ZipFile(imagesFilePath,'r')
current = 0
for image in archive.namelist():
if image_name_to_id(image) != False:
current += 1
if (current == num):
return image_name_to_id(image)
return False
def get_sample_from_num(num):
imagesFilePath = os.path.dirname(os.path.abspath(__file__)) + "/gt/images.zip"
archive = zipfile.ZipFile(imagesFilePath,'r')
current = 0
for image in archive.namelist():
if image_name_to_id(image) != False:
current += 1
if (current == num):
return image,archive.read(image)
return False
def get_samples():
imagesFilePath = os.path.dirname(os.path.abspath(__file__)) + "/gt/images.zip"
archive = zipfile.ZipFile(imagesFilePath,'r')
num_samples = 0
samples_list = []
for image in archive.namelist():
if image_name_to_id(image) != False:
num_samples += 1
samples_list.append(image)
return num_samples,samples_list
@route('/static/:path#.+#', name='static')
def static(path):
return static_file(path, root=os.path.abspath(os.path.join(os.path.dirname(__file__), "static")))
@route('/static_custom/:path#.+#', name='static_custom')
def static_custom(path):
return static_file(path, root=os.path.abspath(os.path.join(os.path.dirname(__file__), "static_custom")))
@route('/gt/:path#.+#', name='static_gt')
def static_gt(path):
return static_file(path, root=os.path.abspath(os.path.join(os.path.dirname(__file__), "gt")))
@route('/favicon.ico')
def favicon():
return static_file("cvc-ico.png", root=os.path.abspath(os.path.join(os.path.dirname(__file__), "static")))
@route('/')
def index():
_,images_list = get_samples()
page = 1
if 'p' in request.query:
page = int(request.query['p'])
subm_data = get_all_submissions()
vars = {
'url':url,
'acronym':acronym,
'title':title,
'images':images_list,
'method_params':method_params,
'page':page,
'subm_data':subm_data,
'submit_params':submit_params,
'instructions':instructions,
'extension':gt_ext
}
return template('index',vars)
@route('/exit')
def exit():
sys.stderr.close()
@route('/method/', methods=['GET'])
def method():
_,images_list = get_samples()
results = None
page = 1
subm_data = {}
if 'm' in request.query:
id = request.query['m']
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + id + ".zip"
if os.path.isfile(submFilePath):
results = zipfile.ZipFile(submFilePath,'r')
if 'p' in request.query:
page = int(request.query['p'])
subm_data = get_submission(id)
if results is None or subm_data is None:
redirect('/')
else:
redirect('/')
vars = {
'url':url,
'acronym':acronym,
'title':title,
'images':images_list,
'method_params':method_params,
'sample_params':sample_params,
'results':results,
'page':page,
'subm_data':subm_data
}
return template('method',vars)
@route('/sample/')
def sample():
num_samples,images_list = get_samples()
sample = int(request.query['sample'])
methodId = request.query['m']
subm_data = get_submission(methodId)
samplesValues = []
id = get_sample_id_from_num(int(sample))
sampleId = id + ".json"
subms = get_all_submissions()
for methodId,methodTitle,_,_ in subms:
zipFolderPath = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + str(methodId)
sampleFilePath = zipFolderPath + "/" + sampleId
if os.path.isfile(sampleFilePath) == False:
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + str(methodId) + ".zip"
archive = zipfile.ZipFile(submFilePath,'r')
if os.path.exists(zipFolderPath) == False:
os.makedirs(zipFolderPath)
archive.extract(sampleId, zipFolderPath)
file = open(sampleFilePath,"r")
results = json.loads(file.read())
file.close()
#results = json.loads(archive.read(id + ".json"))
sampleResults = {"id":methodId, "title":methodTitle}
for k,v in sample_params.items():
sampleResults[k] = results[k]
samplesValues.append( sampleResults )
vars = {
'url':url,
'acronym':acronym,
'title':title + ' - Sample ' + str(sample) + ' : ' + images_list[sample-1],
'sample':sample,
'num_samples':num_samples,
'subm_data':subm_data,
'samplesValues':samplesValues,
'sample_params':sample_params,
'customJS':customJS,
'customCSS':customCSS
}
return template('sample',vars)
@route('/sampleInfo/', methods=['GET'])
def get_sample_info():
methodId = request.query['m']
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + methodId + ".zip"
archive = zipfile.ZipFile(submFilePath,'r')
id = get_sample_id_from_num(int(request.query['sample']))
results = json.loads(archive.read(id + ".json"))
return json.dumps(results)
@route('/image_thumb/', methods=['GET'])
def image_thumb():
sample = int(request.query['sample'])
fileName,data = get_sample_from_num(sample)
ext = fileName.split('.')[-1]
f = BytesIO(data)
image = Image.open(f)
maxsize = (205, 130)
image.thumbnail(maxsize)
output = BytesIO()
if ext=="jpg":
im_format = "JPEG"
header = "image/jpeg"
image.save(output,im_format, quality=80, optimize=True, progressive=True)
elif ext == "gif":
im_format = "GIF"
header = "image/gif"
image.save(output,im_format)
elif ext == "png":
im_format = "PNG"
header = "image/png"
image.save(output,im_format, optimize=True)
contents = output.getvalue()
output.close()
body = contents
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/image/', methods=['GET'])
def image():
sample = int(request.query['sample'])
fileName,data = get_sample_from_num(sample)
ext = fileName.split('.')[-1]
if ext=="jpg":
header = "image/jpeg"
elif ext == "gif":
header = "image/gif"
elif ext == "png":
header = "image/png"
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/gt_image/', methods=['GET'])
def gt_image():
imagesFilePath = os.path.dirname(os.path.abspath(__file__)) + "/gt/gt.zip"
archive = zipfile.ZipFile(imagesFilePath,'r')
fileName = request.query['sample']
ext = fileName.split('.')[-1]
if ext=="jpg":
header = "image/jpeg"
elif ext == "gif":
header = "image/gif"
elif ext == "png":
header = "image/png"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/gt_file/', methods=['GET'])
def gt_file():
imagesFilePath = os.path.dirname(os.path.abspath(__file__)) + "/gt/gt.zip"
archive = zipfile.ZipFile(imagesFilePath,'r')
fileName = request.query['sample']
ext = fileName.split('.')[-1]
if ext=="xml":
header = "text/xml"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/gt_video/', methods=['GET'])
def gt_video():
imagesFilePath = os.path.dirname(os.path.abspath(__file__)) + "/gt/images.zip"
archive = zipfile.ZipFile(imagesFilePath,'r')
fileName = request.query['sample']
ext = fileName.split('.')[-1]
header = "video/mp4"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/subm_image/', methods=['GET'])
def subm_image():
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/subm_" + str(request.query['m']) + ".zip"
archive = zipfile.ZipFile(submFilePath,'r')
fileName = request.query['sample']
ext = fileName.split('.')[-1]
if ext=="jpg":
header = "image/jpeg"
elif ext == "gif":
header = "image/gif"
elif ext == "png":
header = "image/png"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/subm_xml/', methods=['GET'])
def subm_xml():
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/subm_" + str(request.query['m']) + ".zip"
archive = zipfile.ZipFile(submFilePath,'r')
fileName = request.query['sample']
header = "text/xml"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/result_image/', methods=['GET'])
def result_image():
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + str(request.query['m']) + ".zip"
archive = zipfile.ZipFile(submFilePath,'r')
fileName = request.query['name']
ext = fileName.split('.')[-1]
if ext=="jpg":
header = "image/jpeg"
elif ext == "gif":
header = "image/gif"
elif ext == "png":
header = "image/png"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/result_xml/', methods=['GET'])
def result_xml():
submFilePath = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + str(request.query['m']) + ".zip"
archive = zipfile.ZipFile(submFilePath,'r')
fileName = request.query['name']
header = "text/xml"
data = archive.read(fileName)
body = data
headers = dict()
headers['Content-Type'] = header
if 'c' in request.query:
headers['Cache-Control'] = "public, max-age=3600"
return HTTPResponse(body, **headers)
@route('/evaluate', method=['POST','GET'])
def evaluate():
id=0
submFile = request.files.get('submissionFile')
if submFile is None:
resDict = {"calculated":False,"Message":"No file selected"}
if request.query['json']=="1":
return json.dumps(resDict)
else:
vars = {'url':url, 'title':'Method Upload ' + title,'resDict':resDict}
return template('upload',vars)
else:
name, ext = os.path.splitext(submFile.filename)
if ext not in ('.' + gt_ext):
resDict = {"calculated":False,"Message":"File not valid. A " + gt_ext.upper() + " file is required."}
if request.query['json']=="1":
return json.dumps(resDict)
else:
vars = {'url':url, 'title':'Method Upload ' + title,'resDict':resDict}
return template('upload',vars)
p = {
'g': os.path.dirname(os.path.abspath(__file__)) + "/gt/gt." + gt_ext,
's': os.path.dirname(os.path.abspath(__file__)) + "/output/subm." + gt_ext,
'o': os.path.dirname(os.path.abspath(__file__)) + "/output"
}
for k,_ in submit_params.items():
p['p'][k] = request.forms.get(k)
if os.path.isfile(p['s']):
os.remove(p['s'])
submFile.save(p['s'])
module = importlib.import_module(evaluation_script )
resDict = rrc_evaluation_funcs.main_evaluation(p,module.default_evaluation_params,module.validate_data,module.evaluate_method)
if resDict['calculated']==True:
dbPath = os.path.dirname(os.path.abspath(__file__)) + "/output/submits"
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
submTitle = request.forms.get('title')
if submTitle=="":
submTitle = "unnamed"
cursor.execute('INSERT INTO submission(title,sumbit_date,results) VALUES(?,?,?)',(submTitle ,datetime.now().strftime("%Y-%m-%d %H:%M"),json.dumps(resDict['method'])))
conn.commit()
id = cursor.lastrowid
os.rename(p['s'], p['s'].replace("subm." + gt_ext,"subm_" + str(id) + "." + gt_ext) )
os.rename(p['o'] + "/results.zip", p['o'] + "/results_" + str(id) + ".zip" )
conn.close()
if request.query['json']=="1":
return json.dumps( {"calculated": resDict['calculated'],"Message": resDict['Message'],'id':id} )
else:
vars = {'url':url, 'title':'Method Upload ' + title,'resDict':resDict,'id':id}
return template('upload',vars)
@route('/delete_all', method='POST')
def delete_all():
output_folder = os.path.dirname(os.path.abspath(__file__)) + "/output"
try:
for root, dirs, files in os.walk(output_folder, topdown=False):
for f in files:
os.remove(os.path.join(root, f))
for d in dirs:
os.rmdir(os.path.join(root, d))
except:
print("Unexpected error:", sys.exc_info()[0])
@route('/delete_method', method='POST')
def delete_method():
id = request.forms.get('id')
try:
output_folder = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + id
if os.path.isdir(output_folder):
for root, dirs, files in os.walk(output_folder, topdown=False):
for f in files:
os.remove(os.path.join(root, f))
for d in dirs:
os.rmdir(os.path.join(root, d))
os.rmdir(output_folder)
subm_file = os.path.dirname(os.path.abspath(__file__)) + "/output/results_" + id + "." + gt_ext
results_file = os.path.dirname(os.path.abspath(__file__)) + "/output/subm_" + id + ".zip"
os.remove(subm_file)
os.remove(results_file)
except:
print("Unexpected error:", sys.exc_info()[0])
dbPath = os.path.dirname(os.path.abspath(__file__)) + "/output/submits"
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
cursor.execute('DELETE FROM submission WHERE id=?',(id))
conn.commit()
conn.close()
@route('/edit_method', method='POST')
def edit_method():
id = request.forms.get('id')
name = request.forms.get('name')
dbPath = os.path.dirname(os.path.abspath(__file__)) + "/output/submits"
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
cursor.execute('UPDATE submission SET title=? WHERE id=?',(name,id))
conn.commit()
conn.close()
def get_all_submissions():
dbPath = os.path.dirname(os.path.abspath(__file__)) + "/output/submits"
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
cursor.execute("""CREATE TABLE IF NOT EXISTS submission(id integer primary key autoincrement, title varchar(50), sumbit_date varchar(12),results TEXT)""")
conn.commit()
cursor.execute('SELECT id,title,sumbit_date,results FROM submission')
sumbData = cursor.fetchall()
conn.close()
return sumbData
def get_submission(id):
dbPath = os.path.dirname(os.path.abspath(__file__)) + "/output/submits"
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
cursor.execute("""CREATE TABLE IF NOT EXISTS submission(id integer primary key autoincrement, title varchar(50), sumbit_date varchar(12),results TEXT)""")
conn.commit()
cursor.execute('SELECT id,title,sumbit_date,results FROM submission WHERE id=?',(id,))
sumbData = cursor.fetchone()
conn.close()
return sumbData
if __name__=='__main__':
evalModule = importlib.import_module(evaluation_script)
try:
for module,alias in evalModule.evaluation_imports().items():
importlib.import_module(module)
except ImportError:
print("Script " + evaluation_script + ". Required module (" + module + ") not found.")
if module=="Polygon":
print("Install it with: pip3 install Polygon3")
else:
print("Install it with: pip install """ + module)
sys.exit(101)
print("***********************************************")
print("RRC Standalone Task")
print("-----------------------------------------------")
print('Command line client:\ncurl -F "submissionFile=submit.zip" http://127.0.0.1:8080/evaluate')
print("\nGUI client:firefox http://127.0.0.1:8080")
print("-----------------------------------------------")
run(host='0.0.0.0', port=8080, debug=True)
gitextract_ex9hoqm4/ ├── .gitignore ├── LICENSE ├── NOTICE ├── README.MD ├── config/ │ └── config.py ├── rrc_evaluation_funcs.py ├── script.py ├── static/ │ ├── funcs.js │ ├── jquery-mousewheel.js │ ├── jquery.form-3.51.js │ ├── ranking.js │ ├── style.css │ ├── visualization.css │ └── visualization_default.js ├── static_custom/ │ ├── contents.txt │ ├── visualization_TL_iou.css │ └── visualization_TL_iou.js ├── views/ │ ├── index.tpl │ ├── method.tpl │ ├── sample.tpl │ └── upload.tpl └── web.py
SYMBOL INDEX (62 symbols across 5 files)
FILE: rrc_evaluation_funcs.py
function print_help (line 13) | def print_help():
function load_zip_file_keys (line 18) | def load_zip_file_keys(file,fileNameRegExp=''):
function load_zip_file (line 47) | def load_zip_file(file,fileNameRegExp='',allEntries=False):
function decode_utf8 (line 79) | def decode_utf8(raw):
function validate_lines_in_file (line 93) | def validate_lines_in_file(fileName,file_contents,CRLF=True,LTRB=True,wi...
function validate_tl_line (line 112) | def validate_tl_line(line,LTRB=True,withTranscription=True,withConfidenc...
function get_tl_line_values (line 123) | def get_tl_line_values(line,LTRB=True,withTranscription=False,withConfid...
function validate_point_inside_bounds (line 224) | def validate_point_inside_bounds(x,y,imWidth,imHeight):
function validate_clockwise_points (line 230) | def validate_clockwise_points(points):
function get_tl_line_values_from_file_contents (line 255) | def get_tl_line_values_from_file_contents(content,CRLF=True,LTRB=True,wi...
function main_evaluation (line 283) | def main_evaluation(p,default_evaluation_params_fn,validate_data_fn,eval...
function main_validation (line 345) | def main_validation(default_evaluation_params_fn,validate_data_fn):
FILE: script.py
function evaluation_imports (line 8) | def evaluation_imports():
function default_evaluation_params (line 17) | def default_evaluation_params():
function validate_data (line 36) | def validate_data(gtFilePath, submFilePath,evaluationParams):
function evaluate_method (line 58) | def evaluate_method(gtFilePath, submFilePath, evaluationParams):
FILE: static/jquery.form-3.51.js
function t (line 11) | function t(t){var r=t.data;t.isDefaultPrevented()||(t.preventDefault(),e...
function r (line 11) | function r(t){var r=t.target,a=e(r);if(!a.is("[type=submit],[type=image]...
function a (line 11) | function a(){if(e.fn.ajaxSubmit.debug){var t="[jquery.form] "+Array.prot...
function r (line 11) | function r(r){var a,n,i=e.param(r,t.traditional).split("&"),o=i.length,s...
function o (line 11) | function o(a){for(var n=new FormData,i=0;i<a.length;i++)n.append(a[i].na...
function s (line 11) | function s(r){function n(e){var t=null;try{e.contentWindow&&(t=e.content...
FILE: static/ranking.js
function delete_methods (line 25) | function delete_methods(){
function delete_method (line 36) | function delete_method(id){
function edit_method (line 46) | function edit_method(id,el){
function upload_subm (line 58) | function upload_subm(){
function wait_screen (line 62) | function wait_screen(msg){
function close_overlay (line 66) | function close_overlay(){
function show_error (line 70) | function show_error(msg){
function show_info (line 76) | function show_info(msg){
function carregar_api_grafics (line 110) | function carregar_api_grafics(callback){
function init_graphic (line 119) | function init_graphic(){
function show_current_graphic (line 134) | function show_current_graphic(){
function instructions (line 202) | function instructions(){
FILE: web.py
function image_name_to_id (line 30) | def image_name_to_id(name):
function get_sample_id_from_num (line 39) | def get_sample_id_from_num(num):
function get_sample_from_num (line 51) | def get_sample_from_num(num):
function get_samples (line 63) | def get_samples():
function static (line 76) | def static(path):
function static_custom (line 80) | def static_custom(path):
function static_gt (line 84) | def static_gt(path):
function favicon (line 88) | def favicon():
function index (line 93) | def index():
function exit (line 118) | def exit():
function method (line 122) | def method():
function sample (line 162) | def sample():
function get_sample_info (line 220) | def get_sample_info():
function image_thumb (line 230) | def image_thumb():
function image (line 268) | def image():
function gt_image (line 287) | def gt_image():
function gt_file (line 308) | def gt_file():
function gt_video (line 325) | def gt_video():
function subm_image (line 341) | def subm_image():
function subm_xml (line 362) | def subm_xml():
function result_image (line 377) | def result_image():
function result_xml (line 398) | def result_xml():
function evaluate (line 412) | def evaluate():
function delete_all (line 478) | def delete_all():
function delete_method (line 490) | def delete_method():
function edit_method (line 517) | def edit_method():
function get_all_submissions (line 528) | def get_all_submissions():
function get_submission (line 541) | def get_submission(id):
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (177K chars).
[
{
"path": ".gitignore",
"chars": 61,
"preview": "__pycache__*/\n.idea/\noutput/*\n._*\n*/._*\n.pyc\n.DS_Store\n*.swp\n"
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "Copyright (c) 2019-present NAVER Corp.\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "NOTICE",
"chars": 5880,
"preview": "TedEval\nCopyright (c) 2019-present NAVER Corp.\n\nThis project contains subcomponents with separate copyright notices and "
},
{
"path": "README.MD",
"chars": 9627,
"preview": "# TedEval: A Fair Evaluation Metric for Scene Text Detectors\n\nOfficial Python 3 implementation of TedEval | [paper](http"
},
{
"path": "config/config.py",
"chars": 3061,
"preview": "#!/usr/bin/env python3\r\n#encoding: UTF-8\r\nimport json\r\n#Name of the script used for the evalution\r\nevaluation_script = '"
},
{
"path": "rrc_evaluation_funcs.py",
"chars": 15256,
"preview": "#!/usr/bin/env python3\n#encoding: UTF-8\nimport json\nimport sys;sys.path.append('./')\nimport zipfile\nimport re\nimport sys"
},
{
"path": "script.py",
"chars": 26094,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom collections import namedtuple\nimport rrc_evaluation_funcs\nimport impo"
},
{
"path": "static/funcs.js",
"chars": 460,
"preview": "\nvar getUrlParameter = function getUrlParameter(sParam) {\n var sPageURL = decodeURIComponent(window.location.search.s"
},
{
"path": "static/jquery-mousewheel.js",
"chars": 2146,
"preview": "/**\n *\n * credits for this plugin go to brandonaaron.net\n *\n * unfortunately his site is down\n *\n * @param {Object} up\n "
},
{
"path": "static/jquery.form-3.51.js",
"chars": 15248,
"preview": "/*!\n * jQuery Form Plugin\n * version: 3.51.0-2014.06.20\n * Requires jQuery v1.5 or later\n * Copyright (c) 2014 M. Alsup\n"
},
{
"path": "static/ranking.js",
"chars": 8073,
"preview": "/* global web, google */\nvar ranking_task_graphic_options_default = {\n height: 240,\n fontSize:12,\n animation:{'"
},
{
"path": "static/style.css",
"chars": 7020,
"preview": "body{\n font-family: 'Open Sans',helvetica,arial,sans-serif;\n font-size: 1.0em;\n margin: 10px;\n}\n\na{\n color:#"
},
{
"path": "static/visualization.css",
"chars": 3009,
"preview": "#div_sample{\n position: relative;\n}\ndiv.filter {\n display: block;\n vertical-align: middle;\n position: relati"
},
{
"path": "static/visualization_default.js",
"chars": 12789,
"preview": "/* global web */\nvar ClassVisualization = function(){\n this.sampleData = null;\n \n //Parameters for GT/Det Canva"
},
{
"path": "static_custom/contents.txt",
"chars": 78,
"preview": "This folder are designed to store CSS and JS files for a custom visualization."
},
{
"path": "static_custom/visualization_TL_iou.css",
"chars": 1588,
"preview": "#div_canvas_gt{\n position: absolute;\n left: 0px;\n right: 0px;\n top: 30px;\n bottom: 0px; \n}\n#div_canvas"
},
{
"path": "static_custom/visualization_TL_iou.js",
"chars": 19745,
"preview": "/* global web, visualization, ClassVisualization */\n\nClassVisualization.prototype.load_visualization = function(){\n\n "
},
{
"path": "views/index.tpl",
"chars": 8564,
"preview": "% import json\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n <head>\r\n <title>{{title}}</title>\r\n <meta charset=\"u"
},
{
"path": "views/method.tpl",
"chars": 3815,
"preview": "<%\r\nimport json\r\nimport math\r\nimport web\r\n%>\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n <head>\r\n <title>{{title}}</t"
},
{
"path": "views/sample.tpl",
"chars": 4798,
"preview": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n <head>\r\n <title>{{title}}</title> \r\n <meta charset=\"utf-8\" /"
},
{
"path": "views/upload.tpl",
"chars": 801,
"preview": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n <head>\r\n <meta charset=\"utf-8\" />\r\n <title>{{title}}</title>\r\n "
},
{
"path": "web.py",
"chars": 19181,
"preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nimport sys\nimport os\nsys.path.append('./')\nimport json\nfrom io import Byt"
}
]
About this extraction
This page contains the full source code of the clovaai/TedEval GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (164.4 KB), approximately 44.0k tokens, and a symbol index with 62 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.