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.

### 2. Scoring Policy We compute Pseudo Character Center (PCC) from word-level bounding boxes and penalize matches when PCCs are missing or overlapping.

### Sample Evaluation ## 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 ## 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 | 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 └── ... ``` ## 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 = """

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:

See here an example of the expected submission file

""" #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= -s= [-o= -p=]' %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(xmax0 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("" + str(len(np.where(sum(gtCharCounts[gtNum]) == 1)[0])) + "/" + str(len(gtCharPoints[gtNum])) + "") 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("" + str(detContain) + "/" + str(detTotal) + "") 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 += "Recall = " + str(round(recallAccum,2)) + " / " + str(numGtCare) + " = " + str(round(recall,2)) + "\n" evaluationLog += "Precision = " + str(round(precisionAccum,2)) + " / " + str(numDetCare) + " = "+ str(round(precision,2)) + "\n" 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("").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').val(m.extraData[d].value).appendTo(w)[0]:e('').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'),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;Ec;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;li;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("
" + (msg!=undefined? "
" + msg + "
" : "") + "
"); } function close_overlay(){ $("div.overlay").remove(); } function show_error(msg){ if(!$("div.overlay").length){ $("body").append("
"); } $("div.overlay").html("
" + msg + "
"); } function show_info(msg){ if(!$("div.overlay").length){ $("body").append("
"); } $("div.overlay").html("
" + msg + "
"); } $(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>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>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;n127&&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(n191&&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;i0)? 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.ywidth && 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"); for (var key in visualization.sampleData){ $("#div_sample").append("
" + 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 = "
"+ "
" + "

Ground Truth

" + "
" + "
"+ "
" + "

Detection

" + "
" + "
"+ ""+ "
"+ "

Recall

loading..
"+ "

Precision

loading..
"+ "

Character score

loading..
"+ "

Log

loading..
"; $("#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-1; sampleData.gtTypes.push( gtDontCare? 'DC' : 'NM' ); } for ( var j=0;j-1; sampleData.detTypes.push( detDontCare? 'DC' : 'NM' ); } if (sampleData.pairs!=undefined){ for ( var k=0;k100){ html_recall = "

The algorithm has detected more than 100 bounding boxes, the visualization are not posible

"; }else{ var html_recall = ""; for ( var i=0;i-1; html_recall += ""; } html_recall += ""; for ( var j=0;j-1; html_recall += ""; html_recall += ""; for ( var i=0;i=sampleData.evaluationParams.AREA_RECALL_CONSTRAINT ? ' green' : ' red' ); html_recall += ""; } html_recall += ""; } html_recall += "
GT / Det#" + i + "
#" + j + "" + Math.round(sampleData.recallMat[j][i]*10000)/100 + "
"; } $("#div_recall").html("

Recall

" + html_recall + "
"); if(numDet>100){ html_precision = "

The algorithm has detected more than 100 bounding boxes, the visualization are not posible

"; }else{ var html_precision = ""; for ( var i=0;i-1; html_precision += ""; } html_precision += ""; for ( var j=0;j-1; html_precision += ""; html_precision += ""; for ( var i=0;i=sampleData.evaluationParams.AREA_PRECISION_CONSTRAINT ? ' green' : ' red' ); html_precision += ""; } html_precision += ""; } html_precision += "
GT / Det#" + i + "
#" + j + "" + Math.round(sampleData.precisionMat[j][i]*10000)/100 + "
"; } $("#div_precision").html("

Precision

" + html_precision + "
"); if(numDet>100){ html_char = "

The algorithm has detected more than 100 bounding boxes, the visualization are not posible

"; }else{ var html_char = ""; for ( var i=0;i-1; html_char += ""; } html_char += ""; html_char += ""; for ( var j=0;j-1; html_char += ""; html_char += ""; for ( var i=0;i" + sampleData.charCounts[j][i] + ""; } html_char += ""; html_char += ""; } html_char += ""; html_char += ""; for ( var i=0;i" + sampleData.precisionScore[i] + ""; } html_char += ""; html_char += ""; html_char += "
GT / Det#" + i + "" + "Rec Score" + "
#" + j + "" + sampleData.recallScore[j] + "
" + "Prec Score" + "" + "
"; } $("#div_char").html("

Character score

" + html_char + "
"); var evalLog = sampleData.evaluationLog; if (evalLog==undefined){ evalLog = ""; }else{ evalLog = evalLog.replace(/\n/g, "
") } $("#div_logs").html("

Log

" + evalLog + "
"); 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-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 {{title}}

{{title}}

Upload your method: File: % for k,v in submit_params.items(): % end

Dataset files of this Standalone: Images - Ground Truth

<% if len(subm_data)>0: graphicRows = [] graphic2Rows = [] %> <% 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 %> % end % graphicRows.append("[" + ','.join(row) + "]") % graphic2Rows.append("[" + ','.join(row2) + "]") <% 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] %> <% 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 %> <% index += 1 end %> % graphicRows.append("[" + ','.join(row) + "]") % graphic2Rows.append("[" + ','.join(row2) + "]") <% end graphicData = "[" + ','.join(graphicRows) + "]" base64Data = graphicData.encode('utf-8') graphic2Data = "[" + ','.join(graphic2Rows) + "]" base64Data2 = graphic2Data.encode('utf-8') %>
Method Submit date{{v['long_name']}}
{{id}}: {{title}} {{date}}{{value}}
% if show2ndGraphic:
% end
% else:

Upload your methods to see the method's ranking.

% end ================================================ FILE: views/method.tpl ================================================ <% import json import math import web %> {{title}} % submitId, methodTitle, submitDate, methodResultJson = subm_data

{{title}}

% result = json.loads(results.read('method.json')) % if result==None:

Submit your method

% elif result['calculated']==False:

The method has not been calculated

{{result['Message']}}

% else:

Method summary

Title: {{methodTitle}} [{{submitId}}]

Submit date: {{submitDate}}

<% 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 %>

{{v['long_name']}}: {{value}}

% end
<% 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 %>
{{name}}

Sample: {{ str(sample)}}

ID: {{sampleId}}

<% 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 %>

{{v['long_name']}}: {{value}}

% end
<% end results.close() %>
% end ================================================ FILE: views/sample.tpl ================================================ {{title}}

{{title}}

% 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
<% 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 %> % end <% 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" %> <% 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 %> % end % end
Method{{v['long_name']}}
{{row[1]}}{{value}}
================================================ FILE: views/upload.tpl ================================================ {{title}}

{{title}}

% if resDict['calculated']==False:

The method has not been calculated

{{resDict['Message']}}

%else:

The method has been calculated

See the method results

% end ================================================ 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)