├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── cli ├── .dockerignore ├── 91316_leica_at2_40x.svs.38552.50251.624.488.jpg ├── Dockerfile ├── FasterNuclieDetectionCPU │ ├── Cluster-Dask-topology.png │ ├── FasterNuclieDetectionCPU.py │ ├── FasterNuclieDetectionCPU.xml │ ├── pipeline-workflow.png │ └── utils.py ├── FasterNuclieDetectionGPU │ ├── FasterNuclieDetectionGPU.py │ ├── FasterNuclieDetectionGPU.xml │ └── utils.py ├── LICENSE ├── README.md ├── requirements.txt ├── sample_config.yml └── slicer_cli_list.json ├── evaluation ├── IoU_based_mAP_evaluation.py ├── conf-score_based_AUC_evaluation.py ├── ex1-overlay_TCGA-G9-6362-01Z-00-DX1_3.png ├── ex2-overlay_TCGA-HE-7130-01Z-00-DX1_2.png ├── mAP_eval_jsondata_conversion.py ├── plot_precision-recall_confidence-interval_curve.py └── plot_precision-recall_curve.py ├── train ├── config_resnet101_ff12ep4_default.yml ├── config_resnet101_ff12ep4_n-units18.yml ├── config_resnet50_ff12ep4.yml ├── config_resnet50_ff1ep2.yml └── train_model_variants.txt └── utils ├── cli_utils.py ├── nodenames.txt └── overlapping_tiles_detection ├── IoU_based_decision ├── 91315_leica_at2_40x.svs.98728.44031.1649.892.jpg ├── detection_objects.json ├── final_ROI_with_output.png └── run_bbox_mapping.py └── point_based_decision ├── another.png ├── detection.json └── point_mapping.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # =================== 7 | # Folders to be removed 8 | utils/overlapping_tiles_detection/ 9 | inference/ 10 | 11 | 12 | 13 | 14 | 15 | # =================== 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # celery beat schedule file 90 | celerybeat-schedule 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Environments 96 | .env 97 | .venv 98 | env/ 99 | venv/ 100 | ENV/ 101 | env.bak/ 102 | venv.bak/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "luminoth"] 2 | path = luminoth 3 | url = https://github.com/cramraj8/luminoth.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CNNCellDetection 2 | This repository contains adaptations of the state-of-the-art deep learning based object detection models for the detection of cell nuclei in histology images 3 | 4 | 5 | 6 | 7 | First you have to clone the Luminoth repo either from its official repo or the forked repo, which has some modifications made internally. 8 | 9 | 1. Official repo: https://github.com/tryolabs/luminoth.git 10 | 2. Forked Modified repo: https://github.com/cramraj8/luminoth.git 11 | 12 | Forked repo has, 13 | 14 | * ResNet block 3 **num_units** hyper-parameter exposed in base_config.yml for all ResNet variants 15 | * provided ```img = input_img[:, :, :num_channels]``` in dataset loading function 16 | to facilitate gray image loading and unncessary TensorFlow reshaping exceptions. 17 | * provided end-points in feature extraction from R-CNN layers 18 | 19 | 20 | 21 | # Data Generation 22 | 23 | 24 | Raw data can be found in different formats. Either in csv file or **PascalVoc** format in order to train the model. 25 | You can check the documentation for more info at https://luminoth.readthedocs.io/en/latest/usage/dataset.html 26 | 27 | 1. csv file only needs to contain the following columns, and the columns names can be overidden by input argument. 28 | ``` 29 | a. image_id 30 | b. bounding box coordinates in either convention 31 | - x_min, y_min, x_max, y_max 32 | - x_center, y_center, width, height 33 | c. class label(for class-agnostic model, represent by objectness class) 34 | ``` 35 | 36 | 2. PascalVoc data folder should look like below, 37 | 38 | 39 | 40 | ``` 41 | Data 42 | ├── annotations - Folder contains XML ground truth annotations. 43 | │ 44 | │ 45 | ├── ImageSets 46 | │ └──Main 47 | │ ├── objectness_train.txt - contains the image_ids that has this particular 'objectness' class. 48 | │ └── train.py - contains the image_ids that is going to be used for training. 49 | │ 50 | │ 51 | └── JPEGImages - Folder contains JPEG/PNG images. 52 | ``` 53 | 54 | 55 | 56 | 57 | 58 | ## To create tfrecord data 59 | 60 | Either place **image**, **annotations**, **train.txt** in appropriate arrangements inside the '**pascalvoc_format_data**' folder or 61 | have a csv file in appropriate format. 62 | 63 | 64 | 65 | ## Luminoth CLI: 66 | 67 | from PascalVoc format to tfrecord generation 68 | ``` 69 | $ lumi dataset transform \ 70 | --type pascal \ 71 | --data-dir ./data/pascalvoc_format_data \ 72 | --output-dir ./data/tf_dataset \ 73 | --split train 74 | ``` 75 | 76 | 77 | from csv format to tfrecord generation 78 | ``` 79 | $ lumi dataset transform \ 80 | --type csv \ 81 | --data-dir ./data/csv_file \ 82 | --output-dir ./data/tf_dataset \ 83 | --split train 84 | ``` 85 | 86 | 87 | you may want to execute this command by standing inside **luminoth/** folder. 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | # Training 97 | ``` 98 | $ lumi train -c train/config_resnet101_ff12ep4_default.yml 99 | ``` 100 | 101 | To monitor the real-time performance of the training model, we can enable tensorboard. 102 | ``` 103 | $ tensorboard --logdir= 104 | ``` 105 | 106 | If you want to modify the parameters of the model or training, you can do in the config file. There is a config file(**base_config.yml**) inside the path 107 | '**luminoth/models/fasterrcnn/**'. Also there is another very short config file(**sample_config.yml**) inside the path '**examples/**'. Therefore, you can copy the entire 108 | base_config.yml, make changes to it, and provide its path with **-c** flag in the CLI so that the parameters will be overidden while initiating the training. 109 | 110 | Frequently changeable parameters can be found here: https://github.com/cramraj8/luminoth/blob/nuclei-detection/luminoth/README.md 111 | 112 | 113 | 114 | 115 | # Prediction 116 | ``` 117 | $ lumi predict -c train/config_resnet101_ff12ep4_default.yml --min-prob 0.1 --max-detections 800 -d 118 | ``` 119 | This CLI command is to generate prediction overlays visually in .png file and prediction coordinates together with labels in .json file. 120 | The only safely tunable parameters at inference stage are min-prob and max-detections, which are used in filtering out the final predictions. 121 | 122 | **Remember**: When you are increasing the max-detections, the R-CNN part of the network will throw out more detection proposals that consumes more GPU memory. It 123 | can result in memory overflow exception. 124 | 125 | 126 | 127 | 128 | # Evaluation 129 | 130 | Two methods of evaluation conducted, 131 | 132 | 1. IoU based mAP evaluation method 133 | 2. Objectness/classification confidence score based AUC evaluation method 134 | using Hungarian Algorithm for mapping GT with Predictions 135 | 136 | Generally, in the PascalVOC and COCO object detection challenges people often use method #1; however, this method 137 | is better fit for the problem of detecting small number of objects per image. But in the nuclei-detection problem domain, 138 | we usually face more than 100 nuclei(objects) in each image. In this case, without mapping best prediction bndbox with ground-truth, 139 | it is hard to identify the redundant detection bndboxes based on method #1. 140 | 141 | The example of evaluation results overlayed over the input image using method #2 because of the main reason - crowded bndbox detections. 142 | Green boxes are TPs, blue boxes are FP, and red boxes are FNs respectively. 143 | 144 | 145 | 146 | 147 | 149 | 150 | 151 | 152 | Evaluation overlays of prediction example-1 | Evaluation overlays of prediction example-2 153 | :-------------------------:|:-------------------------: 154 | ![](evaluation/ex1-overlay_TCGA-G9-6362-01Z-00-DX1_3.png) | ![](evaluation/ex2-overlay_TCGA-HE-7130-01Z-00-DX1_2.png) 155 | 156 | 157 | 158 | 159 | 160 | # Nuclei Detection Web CLI Plugin 161 | 162 | An extended plugin for [girder/slicer_cli_web](https://github.com/girder/slicer_cli_web) 163 | 164 | The complete workflow of the Dask-TensorFlow enabled pipeline is below ... 165 | ![Alt text](cli/FasterNuclieDetectionCPU/pipeline-workflow.png?raw=true "Complete Nuclei Detection Pipeline") 166 | 167 | 168 | To build a Docker Image from this CLI Plugin, 169 | 170 | First pull Nuclei-Detection TensorFlow pre-trained model files and place them inside the 'cli/jobs/' folder because these files 171 | are going to be placed inside the Docker Image. Or place a `wget ` line in Dockerfile after line #13. 172 | 173 | 174 | 175 | then run, 176 | 177 | ``` 178 | $ docker build -t : 179 | ``` 180 | 181 | To check the Docker Image is completely running, 182 | 183 | 1. First create a Docker Container of this Docker Image and navigate into /bin/bash 184 | ``` 185 | $ docker run -ti -v : --rm --entrypoint=/bin/bash : 186 | ``` 187 | Note : -v : used for mouting local and Docker folders so that the changes to the folder will be mirrored immediately. 188 | 189 | 2. Your default working directory inside the Container bash is '/Applications/', so run a sample test run. 190 | ``` 191 | $ python FasterNuclieDetection/FasterNuclieDetection.py ../91316_leica_at2_40x.svs.38552.50251.624.488.jpg annot.anot timeprofile.csv 192 | ``` 193 | 3. Check the annotation and timeprofile files in the local folder. 194 | 195 | 196 | # Dask Execution 197 | 198 | The Dask-CPU enabled CLI application folder is **cli/FasterNuclieDetectionCPU/**. The script consists complete pipeline. If we run this cli on a local 199 | machine, then Dask will create default number of workers for action run. 200 | 201 | 202 | 203 | 204 | ![Alt text](cli/FasterNuclieDetectionCPU/Cluster-Dask-topology.png?raw=true "Dask Cluster Topology") 205 | 206 | If we want to run this pipeline on **cluster** environment, first we need to intialize **Dask-scheduler** and then run the script. Dask-scheduler will connect, split, configure the workers 207 | to run the tasks in parallel. 208 | 209 | ``` 210 | $ dask-ssh --hostfile=nodenames.txt --remote-python= --remote-dask-worker=distributed.cli.dask_worker --nprocs=6 211 | ``` 212 | 213 | **nodenames.txt** is just a text file contains the node names at its each line. **virtualenv_path_python_folder** is usually be `~/.virtualenvs//bin/python`. **nprocs** flag is 214 | used to specify the number of processes that we want to run on each node. 215 | 216 | Once we run the above CLI, the Dask-scheduler will be created in a virtual terminal session. Now you can run your script in another terminal session. 217 | 218 | **Note** : Any paths that you define inside the script should be declared in a file so that the all the nodes connected with the head node knows where to look for. 219 | For this purpose, we usually create a `.pth` inside **site-packages** folder(usally located at `~/.local/lib/python2.7/site-packages/`), and write down the 220 | paths at each line. The paths that we usally consider are, 221 | 222 | 1. any libraries that we installed outside the site-packages. 223 | 2. any 'import file' location(like utils.py). 224 | 3. input file location. 225 | 4. ouput dir location. 226 | 5. ckpt(pretrained files) dir location. 227 | 6. sample_config.yml file location. 228 | 229 | Now when we run the CLI 230 | ``` 231 | $ python FasterNuclieDetectionCPU/FasterNuclieDetectionCPU.py ../91316_leica_at2_40x.svs.38552.50251.624.488.jpg annot.anot timeprofile.csv 232 | ``` 233 | the task will be running in multi-worker nodes parallelly. 234 | 235 | You can monitor the real time Dask performance using **Bokeh** tool. If you already installed this library in the virtualenv, the `port: 8787` will be listenining always to 236 | Bokeh. 237 | -------------------------------------------------------------------------------- /cli/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore Applications build directory 2 | Applications/build 3 | 4 | # Created by .ignore support plugin (hsz.mobi) 5 | ### Vim template 6 | # swap 7 | [._]*.s[a-w][a-z] 8 | [._]s[a-w][a-z] 9 | # session 10 | Session.vim 11 | # temporary 12 | .netrwhist 13 | *~ 14 | # auto-generated tag files 15 | tags 16 | 17 | ### Cpp template 18 | # Compiled Object files 19 | *.slo 20 | *.lo 21 | *.o 22 | *.obj 23 | 24 | # Precompiled Headers 25 | *.gch 26 | *.pch 27 | 28 | # Compiled Dynamic libraries 29 | *.so 30 | *.dylib 31 | *.dll 32 | 33 | # Fortran module files 34 | *.mod 35 | *.smod 36 | 37 | # Compiled Static libraries 38 | *.lai 39 | *.la 40 | *.a 41 | *.lib 42 | 43 | # Executables 44 | *.exe 45 | *.out 46 | *.app 47 | 48 | ### CMake template 49 | CMakeCache.txt 50 | CMakeFiles 51 | CMakeScripts 52 | Makefile 53 | cmake_install.cmake 54 | install_manifest.txt 55 | CTestTestfile.cmake 56 | 57 | ### Emacs template 58 | # -*- mode: gitignore; -*- 59 | *~ 60 | \#*\# 61 | /.emacs.desktop 62 | /.emacs.desktop.lock 63 | *.elc 64 | auto-save-list 65 | tramp 66 | .\#* 67 | 68 | # Org-mode 69 | .org-id-locations 70 | *_archive 71 | 72 | # flymake-mode 73 | *_flymake.* 74 | 75 | # eshell files 76 | /eshell/history 77 | /eshell/lastdir 78 | 79 | # elpa packages 80 | /elpa/ 81 | 82 | # reftex files 83 | *.rel 84 | 85 | # AUCTeX auto folder 86 | /auto/ 87 | 88 | # cask packages 89 | .cask/ 90 | dist/ 91 | 92 | # Flycheck 93 | flycheck_*.el 94 | 95 | # server auth directory 96 | /server/ 97 | 98 | # projectiles files 99 | .projectile### VirtualEnv template 100 | # Virtualenv 101 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 102 | .Python 103 | [Bb]in 104 | [Ii]nclude 105 | [Ll]ib 106 | [Ll]ib64 107 | [Ll]ocal 108 | [Ss]cripts 109 | pyvenv.cfg 110 | .venv 111 | pip-selfcheck.json 112 | 113 | ### Linux template 114 | *~ 115 | 116 | # temporary files which can be created if a process still has a handle open of a deleted file 117 | .fuse_hidden* 118 | 119 | # KDE directory preferences 120 | .directory 121 | 122 | # Linux trash folder which might appear on any partition or disk 123 | .Trash-* 124 | 125 | ### C template 126 | # Object files 127 | *.o 128 | *.ko 129 | *.obj 130 | *.elf 131 | 132 | # Precompiled Headers 133 | *.gch 134 | *.pch 135 | 136 | # Libraries 137 | *.lib 138 | *.a 139 | *.la 140 | *.lo 141 | 142 | # Shared objects (inc. Windows DLLs) 143 | *.dll 144 | *.so 145 | *.so.* 146 | *.dylib 147 | 148 | # Executables 149 | *.exe 150 | *.out 151 | *.app 152 | *.i*86 153 | *.x86_64 154 | *.hex 155 | 156 | # Debug files 157 | *.dSYM/ 158 | *.su 159 | 160 | ### Windows template 161 | # Windows image file caches 162 | Thumbs.db 163 | ehthumbs.db 164 | 165 | # Folder config file 166 | Desktop.ini 167 | 168 | # Recycle Bin used on file shares 169 | $RECYCLE.BIN/ 170 | 171 | # Windows Installer files 172 | *.cab 173 | *.msi 174 | *.msm 175 | *.msp 176 | 177 | # Windows shortcuts 178 | *.lnk 179 | 180 | ### KDevelop4 template 181 | *.kdev4 182 | .kdev4/ 183 | 184 | ### Python template 185 | # Byte-compiled / optimized / DLL files 186 | __pycache__/ 187 | *.py[cod] 188 | *$py.class 189 | 190 | # C extensions 191 | *.so 192 | 193 | # Distribution / packaging 194 | .Python 195 | env/ 196 | build/ 197 | develop-eggs/ 198 | dist/ 199 | downloads/ 200 | eggs/ 201 | .eggs/ 202 | lib/ 203 | lib64/ 204 | parts/ 205 | sdist/ 206 | var/ 207 | *.egg-info/ 208 | .installed.cfg 209 | *.egg 210 | 211 | # PyInstaller 212 | # Usually these files are written by a python script from a template 213 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 214 | *.manifest 215 | *.spec 216 | 217 | # Installer logs 218 | pip-log.txt 219 | pip-delete-this-directory.txt 220 | 221 | # Unit test / coverage reports 222 | htmlcov/ 223 | .tox/ 224 | .coverage 225 | .coverage.* 226 | .cache 227 | nosetests.xml 228 | coverage.xml 229 | *,cover 230 | .hypothesis/ 231 | 232 | # Translations 233 | *.mo 234 | *.pot 235 | 236 | # Django stuff: 237 | *.log 238 | local_settings.py 239 | 240 | # Flask stuff: 241 | instance/ 242 | .webassets-cache 243 | 244 | # Scrapy stuff: 245 | .scrapy 246 | 247 | # Sphinx documentation 248 | docs/_build/ 249 | 250 | # PyBuilder 251 | target/ 252 | 253 | # IPython Notebook 254 | .ipynb_checkpoints 255 | 256 | # pyenv 257 | .python-version 258 | 259 | # celery beat schedule file 260 | celerybeat-schedule 261 | 262 | # dotenv 263 | .env 264 | 265 | # virtualenv 266 | venv/ 267 | ENV/ 268 | 269 | # Spyder project settings 270 | .spyderproject 271 | 272 | # Rope project settings 273 | .ropeproject 274 | ### Xcode template 275 | # Xcode 276 | # 277 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 278 | 279 | ## Build generated 280 | build/ 281 | DerivedData/ 282 | 283 | ## Various settings 284 | *.pbxuser 285 | !default.pbxuser 286 | *.mode1v3 287 | !default.mode1v3 288 | *.mode2v3 289 | !default.mode2v3 290 | *.perspectivev3 291 | !default.perspectivev3 292 | xcuserdata/ 293 | 294 | ## Other 295 | *.moved-aside 296 | *.xccheckout 297 | *.xcscmblueprint 298 | 299 | -------------------------------------------------------------------------------- /cli/91316_leica_at2_40x.svs.38552.50251.624.488.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/cli/91316_leica_at2_40x.svs.38552.50251.624.488.jpg -------------------------------------------------------------------------------- /cli/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM cramraj8/base_docker_cuda:1.0 3 | # FROM dsarchive/histomicstk:latest 4 | MAINTAINER Lee Cooper 5 | 6 | 7 | # Copy plugin files and install any requirements 8 | ENV my_cwd_path=$htk_path/../my_cwd 9 | RUN mkdir -p $my_cwd_path && \ 10 | apt-get update 11 | 12 | 13 | COPY . $my_cwd_path 14 | 15 | # ================ Above are basics ====================== 16 | 17 | RUN pip install -e git+https://github.com/cramraj8/luminoth.git@nuclei-detection#egg=luminoth --ignore-installed PyYAML 18 | 19 | RUN pip install -r $htk_path/requirements.txt 20 | 21 | RUN pip install tensorflow==1.5.0 22 | 23 | # ================ Above are installation requirements file ====================== 24 | 25 | # use entrypoint provided by slicer_cli_web 26 | WORKDIR $my_cwd_path 27 | ENTRYPOINT ["/build/miniconda/bin/python" ,"/build/slicer_cli_web/server/cli_list_entrypoint.py"] 28 | -------------------------------------------------------------------------------- /cli/FasterNuclieDetectionCPU/Cluster-Dask-topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/cli/FasterNuclieDetectionCPU/Cluster-Dask-topology.png -------------------------------------------------------------------------------- /cli/FasterNuclieDetectionCPU/FasterNuclieDetectionCPU.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HistomicsTK 4 | Detect General Nuclie 5 | Detect wide rage of nuclie on Hematoxylin channel using deep learning based algorithm 6 | 0.1.0 7 | This work is part of the HistomicsTK project. 8 | 9 | 10 | Input/output parameters 11 | 12 | inputImageFile 13 | 14 | Input image 15 | input 16 | 0 17 | 18 | 19 | analysis_roi 20 | 21 | Region of interest within which the analysis should be done. Must be a four element vector in the format "left, top, width, height" in the space of the base layer. Default value of "-1, -1, -1, -1" indicates that the whole image should be processed. 22 | analysis_roi 23 | -1,-1,-1,-1 24 | 25 | 26 | nuclei_annotation_format 27 | 28 | Format of the output nuclei annotations 29 | nuclei_annotation_format 30 | bbox 31 | boundary 32 | bbox 33 | 34 | 35 | outputNucleiAnnotationFile 36 | 37 | Output Nuclei annotation file (*.anot) 38 | output 39 | 1 40 | 41 | 42 | outputNucleiDetectionTimeProfilingFile 43 | 44 | Output Nuclei Detection Time Profiling file (*.csv) 45 | output 46 | 2 47 | 48 | 49 | 50 | 51 | preprocessing each tile images 52 | 53 | deconv_method 54 | 55 | Name of the Color Deconvolving Process 56 | deconv_method 57 | ruifrok 58 | macenko 59 | ruifrok 60 | 61 | 62 | stain_1 63 | 64 | Name of the Particular Color Deconvolving Process 65 | stain_1 66 | hematoxylin 67 | dab 68 | eosin 69 | null 70 | hematoxylin 71 | 72 | 73 | stain_2 74 | 75 | Name of the Particular Color Deconvolving Process 76 | stain_2 77 | hematoxylin 78 | dab 79 | eosin 80 | null 81 | dab 82 | 83 | 84 | stain_3 85 | 86 | Name of the Particular Color Deconvolving Process 87 | stain_3 88 | hematoxylin 89 | dab 90 | eosin 91 | null 92 | null 93 | 94 | 95 | stain_1_vector 96 | 97 | input 98 | s1_v 99 | -1, -1, -1 100 | somthing1 101 | 102 | 103 | stain_2_vector 104 | 105 | input 106 | s2_v 107 | -1, -1, -1 108 | somthing2 109 | 110 | 111 | stain_3_vector 112 | 113 | input 114 | s3_v 115 | -1, -1, -1 116 | somthing3 117 | 118 | 132 | 133 | 134 | 135 | Nuclei detection parameters 136 | 137 | min_prob 138 | 139 | Minimum cut-off probability value to filter out robust detections 140 | min_prob 141 | 0.1 142 | 143 | 144 | max_det 145 | 146 | Maximum Number of Nuclei Detections 147 | max_det 148 | 1000 149 | 150 | 151 | ignore_border_nuclei 152 | 153 | Ignore/drop nuclei touching the image/tile border 154 | ignore_border_nuclei 155 | false 156 | 157 | 158 | 159 | 160 | Whole-slide image analysis (WSI) parameters 161 | 162 | analysis_tile_size 163 | 164 | Tile size for blockwise analysis 165 | analysis_tile_size 166 | 1024 167 | 168 | 169 | analysis_mag 170 | 171 | The magnification at which the analysis should be performed. 172 | analysis_mag 173 | 40 174 | 175 | 176 | min_fgnd_frac 177 | 178 | The minimum amount of foreground that must be present in a tile for it to be analyzed 179 | min_fgnd_frac 180 | 0.25 181 | 182 | 183 | 184 | 185 | Dask parameters 186 | 187 | scheduler 188 | 189 | Address of a dask scheduler in the format '127.0.0.1:8786'. Not passing this parameter sets up a dask cluster on the local machine. 'multiprocessing' uses Python multiprocessing. 'multithreading' uses Python multiprocessing in threaded mode. 190 | scheduler 191 | 192 | 193 | 194 | num_workers 195 | 196 | Number of dask workers to start while setting up a local cluster internally. If a negative value is specified then the number of workers is set to number of cpu cores on the machine minus the number of workers specified. 197 | num_workers 198 | -1 199 | 200 | 201 | num_threads_per_worker 202 | 203 | Number of threads to use per worker while setting up a local cluster internally. Must be a positive integer >= 1. 204 | num_threads_per_worker 205 | 1 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /cli/FasterNuclieDetectionCPU/pipeline-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/cli/FasterNuclieDetectionCPU/pipeline-workflow.png -------------------------------------------------------------------------------- /cli/FasterNuclieDetectionCPU/utils.py: -------------------------------------------------------------------------------- 1 | from argparse import Namespace 2 | from datetime import timedelta 3 | import psutil 4 | import numpy as np 5 | import skimage.measure 6 | import skimage.morphology 7 | 8 | import histomicstk.preprocessing.color_deconvolution as htk_cdeconv 9 | import histomicstk.segmentation as htk_seg 10 | import histomicstk.utils as htk_utils 11 | 12 | import large_image 13 | 14 | 15 | # These defaults are only used if girder is not present 16 | # Use memcached by default. 17 | large_image.cache_util.cachefactory.defaultConfig[ 18 | 'cache_backend'] = 'memcached' 19 | # If memcached is unavilable, specify the fraction of memory that python 20 | # caching is allowed to use. This is deliberately small. 21 | large_image.cache_util.cachefactory.defaultConfig[ 22 | 'cache_python_memory_portion'] = 32 23 | 24 | 25 | def get_stain_vector(args, index): 26 | """Get the stain corresponding to args.stain_$index and 27 | args.stain_$index_vector. If the former is not "custom", all the 28 | latter's elements must be -1. 29 | """ 30 | args = args._asdict() if hasattr(args, '_asdict') else vars(args) 31 | stain = args['stain_' + str(index)] 32 | stain_vector = args['stain_' + str(index) + '_vector'] 33 | if all(x == -1 for x in stain_vector): # Magic default value 34 | if stain == 'custom': 35 | raise ValueError('If "custom" is chosen for a stain, ' 36 | 'a stain vector must be provided.') 37 | return htk_cdeconv.stain_color_map[stain] 38 | else: 39 | if stain == 'custom': 40 | return stain_vector 41 | raise ValueError('Unless "custom" is chosen for a stain, ' 42 | 'no stain vector may be provided.') 43 | 44 | 45 | def get_stain_matrix(args, count=3): 46 | """Get the stain matrix corresponding to the args.stain_$index and 47 | args.stain_$index_vector arguments for values of index 1 to count. 48 | Return a numpy array of column vectors. 49 | """ 50 | return np.array([get_stain_vector(args, i + 1) for i in range(count)]).T 51 | 52 | 53 | def segment_wsi_foreground_at_low_res(ts, lres_size=2048): 54 | 55 | ts_metadata = ts.getMetadata() 56 | 57 | # get image at low-res 58 | maxSize = max(ts_metadata['sizeX'], ts_metadata['sizeY']) 59 | maxSize = float(max(maxSize, lres_size)) 60 | 61 | downsample_factor = 2.0 ** np.floor(np.log2(maxSize / lres_size)) 62 | 63 | fgnd_seg_mag = ts_metadata['magnification'] / downsample_factor 64 | 65 | fgnd_seg_scale = {'magnification': fgnd_seg_mag} 66 | 67 | im_lres, _ = ts.getRegion( 68 | scale=fgnd_seg_scale, 69 | format=large_image.tilesource.TILE_FORMAT_NUMPY 70 | ) 71 | 72 | im_lres = im_lres[:, :, :3] 73 | 74 | # compute foreground mask at low-res 75 | im_fgnd_mask_lres = htk_utils.simple_mask(im_lres) 76 | 77 | return im_fgnd_mask_lres, fgnd_seg_scale 78 | 79 | 80 | def convert_preds_to_utilformat(nuclei_obj_props, pred_probs, 81 | ignore_border_nuclei, 82 | im_tile_size): 83 | 84 | annot_list = [] 85 | analysis_list = [] 86 | for i in range(nuclei_obj_props.shape[0]): 87 | obj_coords = nuclei_obj_props.loc[i] 88 | 89 | xmin = int(obj_coords[0]) 90 | ymin = int(obj_coords[1]) 91 | xmax = int(obj_coords[2]) 92 | ymax = int(obj_coords[3]) 93 | 94 | # ===================================================================== 95 | 96 | if ignore_border_nuclei and \ 97 | (xmin == 0 or xmax == im_tile_size or ymin == 0 or ymax == im_tile_size): 98 | continue 99 | 100 | # ===================================================================== 101 | 102 | cx = (xmin + xmax) / 2 103 | cy = (ymin + ymax) / 2 104 | 105 | height = ymax - ymin 106 | width = xmax - xmin 107 | 108 | tmp_prob = float(pred_probs[i]) 109 | 110 | color = 'rgba(0,255,0)' 111 | 112 | # ========================================== 113 | 114 | tmp_annot_dict = {} 115 | tmp_annot_dict['type'] = "rectangle" 116 | tmp_annot_dict['center'] = [cx, cy, 0] 117 | tmp_annot_dict['width'] = width 118 | tmp_annot_dict['height'] = height 119 | tmp_annot_dict['rotation'] = 0 120 | tmp_annot_dict['fillColor'] = "rgba(0,0,0,0)" 121 | tmp_annot_dict['lineColor'] = color 122 | 123 | # =========================================== 124 | 125 | tmp_analysis_dict = {} 126 | 127 | tmp_analysis_dict['center'] = [cx, cy] 128 | tmp_analysis_dict['height'] = height 129 | tmp_analysis_dict['width'] = width 130 | tmp_analysis_dict['lineColor'] = color 131 | tmp_analysis_dict["pred_prob"] = tmp_prob 132 | 133 | annot_list.append(tmp_annot_dict) 134 | analysis_list.append(tmp_analysis_dict) 135 | 136 | return annot_list, analysis_list 137 | 138 | 139 | def create_tile_nuclei_bbox_annotations(nuclei_obj_props, tile_info): 140 | 141 | cell_annot_list = [] 142 | 143 | gx = tile_info['gx'] 144 | gy = tile_info['gy'] 145 | wfrac = tile_info['gwidth'] / np.double(tile_info['width']) 146 | hfrac = tile_info['gheight'] / np.double(tile_info['height']) 147 | 148 | for i in range(len(nuclei_obj_props)): 149 | 150 | cx = nuclei_obj_props[i]['center'][0] 151 | cy = nuclei_obj_props[i]['center'][1] 152 | width = nuclei_obj_props[i]['width'] + 1 153 | height = nuclei_obj_props[i]['height'] + 1 154 | 155 | class_color = nuclei_obj_props[i]['lineColor'] 156 | 157 | # convert to base pixel coords 158 | cx = np.round(gx + cx * wfrac, 2) 159 | cy = np.round(gy + cy * hfrac, 2) 160 | width = np.round(width * wfrac, 2) 161 | height = np.round(height * hfrac, 2) 162 | 163 | # create annotation json 164 | cur_bbox = { 165 | "type": "rectangle", 166 | "center": [cx, cy, 0], 167 | "width": width, 168 | "height": height, 169 | "rotation": 0, 170 | "fillColor": "rgba(0,0,0,0)", 171 | "lineColor": class_color 172 | } 173 | 174 | cell_annot_list.append(cur_bbox) 175 | 176 | return cell_annot_list 177 | 178 | 179 | def create_tile_nuclei_boundary_annotations(im_nuclei_seg_mask, tile_info): 180 | 181 | nuclei_annot_list = [] 182 | 183 | gx = tile_info['gx'] 184 | gy = tile_info['gy'] 185 | wfrac = tile_info['gwidth'] / np.double(tile_info['width']) 186 | hfrac = tile_info['gheight'] / np.double(tile_info['height']) 187 | 188 | by, bx = htk_seg.label.trace_object_boundaries(im_nuclei_seg_mask, 189 | trace_all=True) 190 | 191 | for i in range(len(bx)): 192 | 193 | # get boundary points and convert to base pixel space 194 | num_points = len(bx[i]) 195 | 196 | if num_points < 3: 197 | continue 198 | 199 | cur_points = np.zeros((num_points, 3)) 200 | cur_points[:, 0] = np.round(gx + bx[i] * wfrac, 2) 201 | cur_points[:, 1] = np.round(gy + by[i] * hfrac, 2) 202 | cur_points = cur_points.tolist() 203 | 204 | # create annotation json 205 | cur_annot = { 206 | "type": "polyline", 207 | "points": cur_points, 208 | "closed": True, 209 | "fillColor": "rgba(0,0,0,0)", 210 | "lineColor": "rgb(0,255,0)" 211 | } 212 | 213 | nuclei_annot_list.append(cur_annot) 214 | 215 | return nuclei_annot_list 216 | 217 | 218 | def create_tile_nuclei_annotations(im_nuclei_det_dict, tile_info, format): 219 | 220 | if format == 'bbox': 221 | 222 | return create_tile_nuclei_bbox_annotations(im_nuclei_det_dict, 223 | tile_info) 224 | 225 | elif format == 'boundary': 226 | 227 | return create_tile_nuclei_boundary_annotations(im_nuclei_det_dict, 228 | tile_info) 229 | else: 230 | 231 | raise ValueError('Invalid value passed for nuclei_annotation_format') 232 | 233 | 234 | def create_dask_client(args): 235 | """Create and install a Dask distributed client using args from a 236 | Namespace, supporting the following attributes: 237 | - .scheduler: Address of the distributed scheduler, or the 238 | empty string to start one locally 239 | """ 240 | import dask 241 | scheduler = args.scheduler 242 | 243 | if scheduler == 'multithreading': 244 | import dask.threaded 245 | from multiprocessing.pool import ThreadPool 246 | 247 | if args.num_threads_per_worker <= 0: 248 | num_workers = max( 249 | 1, psutil.cpu_count(logical=False) + args.num_threads_per_worker) 250 | else: 251 | num_workers = args.num_threads_per_worker 252 | 253 | # num_workers = 1 254 | 255 | print('Starting dask thread pool with %d thread(s)' % num_workers) 256 | dask.config.set(pool=ThreadPool(num_workers)) 257 | dask.config.set(scheduler='threads') 258 | return 259 | 260 | if scheduler == 'multiprocessing': 261 | import dask.multiprocessing 262 | import multiprocessing 263 | 264 | dask.config.set(scheduler='processes') 265 | if args.num_workers <= 0: 266 | num_workers = max( 267 | 1, psutil.cpu_count(logical=False) + args.num_workers) 268 | else: 269 | num_workers = args.num_workers 270 | 271 | print('Starting dask multiprocessing pool with %d worker(s)' % num_workers) 272 | dask.config.set(pool=multiprocessing.Pool( 273 | num_workers, initializer=dask.multiprocessing.initialize_worker_process)) 274 | return 275 | 276 | import dask.distributed 277 | if not scheduler: 278 | 279 | if args.num_workers <= 0: 280 | num_workers = max( 281 | 1, psutil.cpu_count(logical=False) + args.num_workers) 282 | else: 283 | num_workers = args.num_workers 284 | num_threads_per_worker = ( 285 | args.num_threads_per_worker if args.num_threads_per_worker >= 1 else None) 286 | 287 | print('Creating dask LocalCluster with %d worker(s), %d thread(s) per ' 288 | 'worker' % (num_workers, args.num_threads_per_worker)) 289 | scheduler = dask.distributed.LocalCluster( 290 | ip='0.0.0.0', # Allow reaching the diagnostics port externally 291 | scheduler_port=0, # Don't expose the scheduler port 292 | n_workers=num_workers, 293 | memory_limit=0, 294 | threads_per_worker=num_threads_per_worker, 295 | silence_logs=False 296 | ) 297 | 298 | return dask.distributed.Client(scheduler) 299 | 300 | 301 | def get_region_dict(region, maxRegionSize=None, tilesource=None): 302 | """Return a dict corresponding to region, checking the region size if 303 | maxRegionSize is provided. 304 | The intended use is to be passed via **kwargs, and so either {} is 305 | returned (for the special region -1,-1,-1,-1) or {'region': 306 | region_dict}. 307 | Params 308 | ------ 309 | region: list 310 | 4 elements -- left, top, width, height -- or all -1, meaning the whole 311 | slide. 312 | maxRegionSize: int, optional 313 | Maximum size permitted of any single dimension 314 | tilesource: tilesource, optional 315 | A `large_image` tilesource (or anything with `.sizeX` and `.sizeY` 316 | properties) that is used to determine the size of the whole slide if 317 | necessary. Must be provided if `maxRegionSize` is. 318 | Returns 319 | ------- 320 | region_dict: dict 321 | Either {} (for the special region -1,-1,-1,-1) or 322 | {'region': region_subdict} 323 | """ 324 | 325 | if len(region) != 4: 326 | raise ValueError('Exactly four values required for --region') 327 | 328 | useWholeImage = region == [-1] * 4 329 | 330 | if maxRegionSize is not None: 331 | if tilesource is None: 332 | raise ValueError('tilesource must be provided if maxRegionSize is') 333 | if maxRegionSize != -1: 334 | if useWholeImage: 335 | size = max(tilesource.sizeX, tilesource.sizeY) 336 | else: 337 | size = max(region[-2:]) 338 | if size > maxRegionSize: 339 | raise ValueError('Requested region is too large! ' 340 | 'Please see --maxRegionSize') 341 | 342 | return {} if useWholeImage else dict( 343 | region=dict(zip(['left', 'top', 'width', 'height'], 344 | region))) 345 | 346 | 347 | def disp_time_hms(seconds): 348 | """Converts time from seconds to a string of the form hours:minutes:seconds 349 | """ 350 | 351 | return str(timedelta(seconds=seconds)) 352 | 353 | 354 | def splitArgs(args, split='_'): 355 | """Split a Namespace into a Namespace of Namespaces based on shared 356 | prefixes. The string separating the prefix from the rest of the 357 | argument is determined by the optional "split" parameter. 358 | Parameters not containing the splitting string are kept as-is. 359 | """ 360 | def splitKey(k): 361 | s = k.split(split, 1) 362 | return (None, s[0]) if len(s) == 1 else s 363 | 364 | args = args._asdict() if hasattr(args, '_asdict') else vars(args) 365 | firstKeys = {splitKey(k)[0] for k in args} 366 | result = Namespace() 367 | for k in firstKeys - {None}: 368 | setattr(result, k, Namespace()) 369 | for k, v in args.items(): 370 | f, s = splitKey(k) 371 | if f is None: 372 | setattr(result, s, v) 373 | else: 374 | setattr(getattr(result, f), s, v) 375 | return result 376 | 377 | 378 | def sample_pixels(args): 379 | """Version of histomicstk.utils.sample_pixels that takes a Namespace 380 | and handles the special default values. 381 | """ 382 | args = (args._asdict() if hasattr(args, '_asdict') else vars(args)).copy() 383 | for k in 'magnification', 'sample_fraction', 'sample_approximate_total': 384 | if args[k] == -1: 385 | del args[k] 386 | return htk_utils.sample_pixels(**args) 387 | 388 | 389 | __all__ = ( 390 | 'create_dask_client', 391 | 'create_tile_nuclei_annotations', 392 | 'create_tile_nuclei_bbox_annotations', 393 | 'create_tile_nuclei_boundary_annotations', 394 | 'disp_time_hms', 395 | 'get_region_dict', 396 | 'get_stain_matrix', 397 | 'get_stain_vector', 398 | 'sample_pixels', 399 | 'segment_wsi_foreground_at_low_res', 400 | 'splitArgs', 401 | ) 402 | -------------------------------------------------------------------------------- /cli/FasterNuclieDetectionGPU/FasterNuclieDetectionGPU.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HistomicsTK 4 | Detect General Nuclie 5 | Detect wide rage of nuclie on Hematoxylin channel using deep learning based algorithm 6 | 0.1.0 7 | This work is part of the HistomicsTK project. 8 | 9 | 10 | Input/output parameters 11 | 12 | inputImageFile 13 | 14 | Input image 15 | input 16 | 0 17 | 18 | 19 | analysis_roi 20 | 21 | Region of interest within which the analysis should be done. Must be a four element vector in the format "left, top, width, height" in the space of the base layer. Default value of "-1, -1, -1, -1" indicates that the whole image should be processed. 22 | analysis_roi 23 | -1,-1,-1,-1 24 | 25 | 26 | nuclei_annotation_format 27 | 28 | Format of the output nuclei annotations 29 | nuclei_annotation_format 30 | bbox 31 | boundary 32 | bbox 33 | 34 | 35 | outputNuclieAnnotationFile 36 | 37 | Output Nuclei annotation file (*.anot) 38 | output 39 | 1 40 | 41 | 42 | outputNucleiDetectionTimeProfilingFile 43 | 44 | Output Nuclei Detection Time Profiling file (*.csv) 45 | output 46 | 2 47 | 48 | 49 | 50 | 51 | preprocessing each tile images 52 | 53 | deconv_method 54 | 55 | Name of the Color Deconvolving Process 56 | deconv_method 57 | ruifrok 58 | macenko 59 | ruifrok 60 | 61 | 62 | stain_1 63 | 64 | Name of the Particular Color Deconvolving Process 65 | stain_1 66 | hematoxylin 67 | dab 68 | eosin 69 | null 70 | hematoxylin 71 | 72 | 73 | stain_2 74 | 75 | Name of the Particular Color Deconvolving Process 76 | stain_2 77 | hematoxylin 78 | dab 79 | eosin 80 | null 81 | dab 82 | 83 | 84 | stain_3 85 | 86 | Name of the Particular Color Deconvolving Process 87 | stain_3 88 | hematoxylin 89 | dab 90 | eosin 91 | null 92 | null 93 | 94 | 95 | stain_1_vector 96 | 97 | input 98 | s1_v 99 | -1, -1, -1 100 | somthing1 101 | 102 | 103 | stain_2_vector 104 | 105 | input 106 | s2_v 107 | -1, -1, -1 108 | somthing2 109 | 110 | 111 | stain_3_vector 112 | 113 | input 114 | s3_v 115 | -1, -1, -1 116 | somthing3 117 | 118 | 132 | 133 | 134 | 135 | Nuclei detection parameters 136 | 137 | min_prob 138 | 139 | Minimum cut-off probability value to filter out robust detections 140 | min_prob 141 | 0.1 142 | 143 | 144 | max_det 145 | 146 | Maximum Number of Nuclei Detections 147 | max_det 148 | 1000 149 | 150 | 151 | ignore_border_nuclei 152 | 153 | Ignore/drop nuclei touching the image/tile border 154 | ignore_border_nuclei 155 | false 156 | 157 | 158 | 159 | 160 | Whole-slide image analysis (WSI) parameters 161 | 162 | analysis_tile_size 163 | 164 | Tile size for blockwise analysis 165 | analysis_tile_size 166 | 1024 167 | 168 | 169 | analysis_mag 170 | 171 | The magnification at which the analysis should be performed. 172 | analysis_mag 173 | 40 174 | 175 | 176 | min_fgnd_frac 177 | 178 | The minimum amount of foreground that must be present in a tile for it to be analyzed 179 | min_fgnd_frac 180 | 0.25 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /cli/FasterNuclieDetectionGPU/utils.py: -------------------------------------------------------------------------------- 1 | from argparse import Namespace 2 | from datetime import timedelta 3 | import psutil 4 | import numpy as np 5 | import skimage.measure 6 | import skimage.morphology 7 | 8 | import histomicstk.preprocessing.color_deconvolution as htk_cdeconv 9 | import histomicstk.segmentation as htk_seg 10 | import histomicstk.utils as htk_utils 11 | 12 | import large_image 13 | 14 | 15 | # These defaults are only used if girder is not present 16 | # Use memcached by default. 17 | large_image.cache_util.cachefactory.defaultConfig[ 18 | 'cache_backend'] = 'memcached' 19 | # If memcached is unavilable, specify the fraction of memory that python 20 | # caching is allowed to use. This is deliberately small. 21 | large_image.cache_util.cachefactory.defaultConfig[ 22 | 'cache_python_memory_portion'] = 32 23 | 24 | 25 | def get_stain_vector(args, index): 26 | """Get the stain corresponding to args.stain_$index and 27 | args.stain_$index_vector. If the former is not "custom", all the 28 | latter's elements must be -1. 29 | """ 30 | args = args._asdict() if hasattr(args, '_asdict') else vars(args) 31 | stain = args['stain_' + str(index)] 32 | stain_vector = args['stain_' + str(index) + '_vector'] 33 | if all(x == -1 for x in stain_vector): # Magic default value 34 | if stain == 'custom': 35 | raise ValueError('If "custom" is chosen for a stain, ' 36 | 'a stain vector must be provided.') 37 | return htk_cdeconv.stain_color_map[stain] 38 | else: 39 | if stain == 'custom': 40 | return stain_vector 41 | raise ValueError('Unless "custom" is chosen for a stain, ' 42 | 'no stain vector may be provided.') 43 | 44 | 45 | def get_stain_matrix(args, count=3): 46 | """Get the stain matrix corresponding to the args.stain_$index and 47 | args.stain_$index_vector arguments for values of index 1 to count. 48 | Return a numpy array of column vectors. 49 | """ 50 | return np.array([get_stain_vector(args, i + 1) for i in range(count)]).T 51 | 52 | 53 | def segment_wsi_foreground_at_low_res(ts, lres_size=2048): 54 | 55 | ts_metadata = ts.getMetadata() 56 | 57 | # get image at low-res 58 | maxSize = max(ts_metadata['sizeX'], ts_metadata['sizeY']) 59 | maxSize = float(max(maxSize, lres_size)) 60 | 61 | downsample_factor = 2.0 ** np.floor(np.log2(maxSize / lres_size)) 62 | 63 | fgnd_seg_mag = ts_metadata['magnification'] / downsample_factor 64 | 65 | fgnd_seg_scale = {'magnification': fgnd_seg_mag} 66 | 67 | im_lres, _ = ts.getRegion( 68 | scale=fgnd_seg_scale, 69 | format=large_image.tilesource.TILE_FORMAT_NUMPY 70 | ) 71 | 72 | im_lres = im_lres[:, :, :3] 73 | 74 | # compute foreground mask at low-res 75 | im_fgnd_mask_lres = htk_utils.simple_mask(im_lres) 76 | 77 | return im_fgnd_mask_lres, fgnd_seg_scale 78 | 79 | 80 | def convert_preds_to_utilformat(nuclei_obj_props, pred_probs, 81 | ignore_border_nuclei, 82 | im_tile_size): 83 | 84 | annot_list = [] 85 | analysis_list = [] 86 | for i in range(nuclei_obj_props.shape[0]): 87 | obj_coords = nuclei_obj_props.loc[i] 88 | 89 | xmin = int(obj_coords[0]) 90 | ymin = int(obj_coords[1]) 91 | xmax = int(obj_coords[2]) 92 | ymax = int(obj_coords[3]) 93 | 94 | # ===================================================================== 95 | 96 | if ignore_border_nuclei and \ 97 | (xmin == 0 or xmax == im_tile_size or ymin == 0 or ymax == im_tile_size): 98 | continue 99 | 100 | # ===================================================================== 101 | 102 | cx = (xmin + xmax) / 2 103 | cy = (ymin + ymax) / 2 104 | 105 | height = ymax - ymin 106 | width = xmax - xmin 107 | 108 | tmp_prob = float(pred_probs[i]) 109 | 110 | color = 'rgba(0,255,0)' 111 | 112 | # ========================================== 113 | 114 | tmp_annot_dict = {} 115 | tmp_annot_dict['type'] = "rectangle" 116 | tmp_annot_dict['center'] = [cx, cy, 0] 117 | tmp_annot_dict['width'] = width 118 | tmp_annot_dict['height'] = height 119 | tmp_annot_dict['rotation'] = 0 120 | tmp_annot_dict['fillColor'] = "rgba(0,0,0,0)" 121 | tmp_annot_dict['lineColor'] = color 122 | 123 | # =========================================== 124 | 125 | tmp_analysis_dict = {} 126 | 127 | tmp_analysis_dict['center'] = [cx, cy] 128 | tmp_analysis_dict['height'] = height 129 | tmp_analysis_dict['width'] = width 130 | tmp_analysis_dict['lineColor'] = color 131 | tmp_analysis_dict["pred_prob"] = tmp_prob 132 | 133 | annot_list.append(tmp_annot_dict) 134 | analysis_list.append(tmp_analysis_dict) 135 | 136 | return annot_list, analysis_list 137 | 138 | 139 | def create_tile_nuclei_bbox_annotations(nuclei_obj_props, tile_info): 140 | 141 | cell_annot_list = [] 142 | 143 | gx = tile_info['gx'] 144 | gy = tile_info['gy'] 145 | wfrac = tile_info['gwidth'] / np.double(tile_info['width']) 146 | hfrac = tile_info['gheight'] / np.double(tile_info['height']) 147 | 148 | for i in range(len(nuclei_obj_props)): 149 | 150 | cx = nuclei_obj_props[i]['center'][0] 151 | cy = nuclei_obj_props[i]['center'][1] 152 | width = nuclei_obj_props[i]['width'] + 1 153 | height = nuclei_obj_props[i]['height'] + 1 154 | 155 | class_color = nuclei_obj_props[i]['lineColor'] 156 | 157 | # convert to base pixel coords 158 | cx = np.round(gx + cx * wfrac, 2) 159 | cy = np.round(gy + cy * hfrac, 2) 160 | width = np.round(width * wfrac, 2) 161 | height = np.round(height * hfrac, 2) 162 | 163 | # create annotation json 164 | cur_bbox = { 165 | "type": "rectangle", 166 | "center": [cx, cy, 0], 167 | "width": width, 168 | "height": height, 169 | "rotation": 0, 170 | "fillColor": "rgba(0,0,0,0)", 171 | "lineColor": class_color 172 | } 173 | 174 | cell_annot_list.append(cur_bbox) 175 | 176 | return cell_annot_list 177 | 178 | 179 | def create_tile_nuclei_boundary_annotations(im_nuclei_seg_mask, tile_info): 180 | 181 | nuclei_annot_list = [] 182 | 183 | gx = tile_info['gx'] 184 | gy = tile_info['gy'] 185 | wfrac = tile_info['gwidth'] / np.double(tile_info['width']) 186 | hfrac = tile_info['gheight'] / np.double(tile_info['height']) 187 | 188 | by, bx = htk_seg.label.trace_object_boundaries(im_nuclei_seg_mask, 189 | trace_all=True) 190 | 191 | for i in range(len(bx)): 192 | 193 | # get boundary points and convert to base pixel space 194 | num_points = len(bx[i]) 195 | 196 | if num_points < 3: 197 | continue 198 | 199 | cur_points = np.zeros((num_points, 3)) 200 | cur_points[:, 0] = np.round(gx + bx[i] * wfrac, 2) 201 | cur_points[:, 1] = np.round(gy + by[i] * hfrac, 2) 202 | cur_points = cur_points.tolist() 203 | 204 | # create annotation json 205 | cur_annot = { 206 | "type": "polyline", 207 | "points": cur_points, 208 | "closed": True, 209 | "fillColor": "rgba(0,0,0,0)", 210 | "lineColor": "rgb(0,255,0)" 211 | } 212 | 213 | nuclei_annot_list.append(cur_annot) 214 | 215 | return nuclei_annot_list 216 | 217 | 218 | def create_tile_nuclei_annotations(im_nuclei_det_dict, tile_info, format): 219 | 220 | if format == 'bbox': 221 | 222 | return create_tile_nuclei_bbox_annotations(im_nuclei_det_dict, 223 | tile_info) 224 | 225 | elif format == 'boundary': 226 | 227 | return create_tile_nuclei_boundary_annotations(im_nuclei_det_dict, 228 | tile_info) 229 | else: 230 | 231 | raise ValueError('Invalid value passed for nuclei_annotation_format') 232 | 233 | 234 | def create_dask_client(args): 235 | """Create and install a Dask distributed client using args from a 236 | Namespace, supporting the following attributes: 237 | - .scheduler: Address of the distributed scheduler, or the 238 | empty string to start one locally 239 | """ 240 | import dask 241 | scheduler = args.scheduler 242 | 243 | if scheduler == 'multithreading': 244 | import dask.threaded 245 | from multiprocessing.pool import ThreadPool 246 | 247 | if args.num_threads_per_worker <= 0: 248 | num_workers = max( 249 | 1, psutil.cpu_count(logical=False) + args.num_threads_per_worker) 250 | else: 251 | num_workers = args.num_threads_per_worker 252 | 253 | # num_workers = 1 254 | 255 | print('Starting dask thread pool with %d thread(s)' % num_workers) 256 | dask.config.set(pool=ThreadPool(num_workers)) 257 | dask.config.set(scheduler='threads') 258 | return 259 | 260 | if scheduler == 'multiprocessing': 261 | import dask.multiprocessing 262 | import multiprocessing 263 | 264 | dask.config.set(scheduler='processes') 265 | if args.num_workers <= 0: 266 | num_workers = max( 267 | 1, psutil.cpu_count(logical=False) + args.num_workers) 268 | else: 269 | num_workers = args.num_workers 270 | 271 | print('Starting dask multiprocessing pool with %d worker(s)' % num_workers) 272 | dask.config.set(pool=multiprocessing.Pool( 273 | num_workers, initializer=dask.multiprocessing.initialize_worker_process)) 274 | return 275 | 276 | import dask.distributed 277 | if not scheduler: 278 | 279 | if args.num_workers <= 0: 280 | num_workers = max( 281 | 1, psutil.cpu_count(logical=False) + args.num_workers) 282 | else: 283 | num_workers = args.num_workers 284 | num_threads_per_worker = ( 285 | args.num_threads_per_worker if args.num_threads_per_worker >= 1 else None) 286 | 287 | print('Creating dask LocalCluster with %d worker(s), %d thread(s) per ' 288 | 'worker' % (num_workers, args.num_threads_per_worker)) 289 | scheduler = dask.distributed.LocalCluster( 290 | ip='0.0.0.0', # Allow reaching the diagnostics port externally 291 | scheduler_port=0, # Don't expose the scheduler port 292 | n_workers=num_workers, 293 | memory_limit=0, 294 | threads_per_worker=num_threads_per_worker, 295 | silence_logs=False 296 | ) 297 | 298 | return dask.distributed.Client(scheduler) 299 | 300 | 301 | def get_region_dict(region, maxRegionSize=None, tilesource=None): 302 | """Return a dict corresponding to region, checking the region size if 303 | maxRegionSize is provided. 304 | The intended use is to be passed via **kwargs, and so either {} is 305 | returned (for the special region -1,-1,-1,-1) or {'region': 306 | region_dict}. 307 | Params 308 | ------ 309 | region: list 310 | 4 elements -- left, top, width, height -- or all -1, meaning the whole 311 | slide. 312 | maxRegionSize: int, optional 313 | Maximum size permitted of any single dimension 314 | tilesource: tilesource, optional 315 | A `large_image` tilesource (or anything with `.sizeX` and `.sizeY` 316 | properties) that is used to determine the size of the whole slide if 317 | necessary. Must be provided if `maxRegionSize` is. 318 | Returns 319 | ------- 320 | region_dict: dict 321 | Either {} (for the special region -1,-1,-1,-1) or 322 | {'region': region_subdict} 323 | """ 324 | 325 | if len(region) != 4: 326 | raise ValueError('Exactly four values required for --region') 327 | 328 | useWholeImage = region == [-1] * 4 329 | 330 | if maxRegionSize is not None: 331 | if tilesource is None: 332 | raise ValueError('tilesource must be provided if maxRegionSize is') 333 | if maxRegionSize != -1: 334 | if useWholeImage: 335 | size = max(tilesource.sizeX, tilesource.sizeY) 336 | else: 337 | size = max(region[-2:]) 338 | if size > maxRegionSize: 339 | raise ValueError('Requested region is too large! ' 340 | 'Please see --maxRegionSize') 341 | 342 | return {} if useWholeImage else dict( 343 | region=dict(zip(['left', 'top', 'width', 'height'], 344 | region))) 345 | 346 | 347 | def disp_time_hms(seconds): 348 | """Converts time from seconds to a string of the form hours:minutes:seconds 349 | """ 350 | 351 | return str(timedelta(seconds=seconds)) 352 | 353 | 354 | def splitArgs(args, split='_'): 355 | """Split a Namespace into a Namespace of Namespaces based on shared 356 | prefixes. The string separating the prefix from the rest of the 357 | argument is determined by the optional "split" parameter. 358 | Parameters not containing the splitting string are kept as-is. 359 | """ 360 | def splitKey(k): 361 | s = k.split(split, 1) 362 | return (None, s[0]) if len(s) == 1 else s 363 | 364 | args = args._asdict() if hasattr(args, '_asdict') else vars(args) 365 | firstKeys = {splitKey(k)[0] for k in args} 366 | result = Namespace() 367 | for k in firstKeys - {None}: 368 | setattr(result, k, Namespace()) 369 | for k, v in args.items(): 370 | f, s = splitKey(k) 371 | if f is None: 372 | setattr(result, s, v) 373 | else: 374 | setattr(getattr(result, f), s, v) 375 | return result 376 | 377 | 378 | def sample_pixels(args): 379 | """Version of histomicstk.utils.sample_pixels that takes a Namespace 380 | and handles the special default values. 381 | """ 382 | args = (args._asdict() if hasattr(args, '_asdict') else vars(args)).copy() 383 | for k in 'magnification', 'sample_fraction', 'sample_approximate_total': 384 | if args[k] == -1: 385 | del args[k] 386 | return htk_utils.sample_pixels(**args) 387 | 388 | 389 | __all__ = ( 390 | 'create_dask_client', 391 | 'create_tile_nuclei_annotations', 392 | 'create_tile_nuclei_bbox_annotations', 393 | 'create_tile_nuclei_boundary_annotations', 394 | 'disp_time_hms', 395 | 'get_region_dict', 396 | 'get_stain_matrix', 397 | 'get_stain_vector', 398 | 'sample_pixels', 399 | 'segment_wsi_foreground_at_low_res', 400 | 'splitArgs', 401 | ) 402 | -------------------------------------------------------------------------------- /cli/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Nuclei Detection Web CLI Plugin 3 | 4 | An extended plugin for [girder/slicer_cli_web](https://github.com/girder/slicer_cli_web) 5 | 6 | To build a Docker Image from this CLI Plugin, 7 | 8 | First pull Nuclei-Detection TensorFlow pre-trained model files and place them inside the 'cli' folder because these files 9 | are going to be placed inside the Docker Image. 10 | 11 | wget 12 | 13 | 14 | then run, 15 | 16 | ``` 17 | $ docker build -t : 18 | ``` 19 | 20 | To check the Docker Image is completely running, 21 | 22 | 1. First create a Docker Container of this Docker Image and navigate into /bin/bash 23 | ``` 24 | $ docker run -ti -v : --rm --entrypoint=/bin/bash : 25 | ``` 26 | Note : -v : used for mouting local and Docker folders so that the changes to the folder will be mirrored immediately. 27 | 28 | 2. Your default working directory inside the Container bash is '/Applications/', so run a sample test run. 29 | ``` 30 | $ python FasterNuclieDetection/FasterNuclieDetection.py ../91316_leica_at2_40x.svs.38552.50251.624.488.jpg annot.anot timeprofile.csv 31 | ``` 32 | 3. Check the annotation and timeprofile files in the local folder. 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /cli/requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.7.1 2 | asn1crypto==0.24.0 3 | astor==0.7.1 4 | attrs==19.1.0 5 | backcall==0.1.0 6 | bcrypt==3.1.6 7 | bleach==1.5.0 8 | bokeh==1.1.0 9 | cachetools==3.1.0 10 | certifi==2019.3.9 11 | cffi==1.12.3 12 | chardet==3.0.4 13 | click==6.7 14 | cloudpickle==0.8.1 15 | cmake==3.13.3 16 | cryptography==2.6.1 17 | cycler==0.10.0 18 | Cython==0.29.7 19 | dask==1.2.1 20 | decorator==4.4.0 21 | defusedxml==0.6.0 22 | distributed==1.27.1 23 | dm-sonnet==1.23 24 | easydict==1.8 25 | entrypoints==0.3 26 | enum34==1.1.6 27 | Flask==1.0.2 28 | gast==0.2.2 29 | grpcio==1.20.1 30 | h5py==2.9.0 31 | HeapDict==1.0.0 32 | html5lib==0.9999999 33 | idna==2.8 34 | imageio==2.5.0 35 | ipykernel==5.1.0 36 | ipython==7.5.0 37 | ipython-genutils==0.2.0 38 | ipywidgets==7.4.2 39 | itsdangerous==1.1.0 40 | jedi==0.13.3 41 | Jinja2==2.10.1 42 | jsonschema==3.0.1 43 | jupyter==1.0.0 44 | jupyter-client==5.2.4 45 | jupyter-console==6.0.0 46 | jupyter-core==4.4.0 47 | Keras==2.2.4 48 | Keras-Applications==1.0.7 49 | Keras-Preprocessing==1.0.9 50 | kiwisolver==1.1.0 51 | large-image==0.3.0 52 | libtiff==0.5.0 53 | line-profiler==2.1.2 54 | lxml==4.3.3 55 | Markdown==3.1 56 | MarkupSafe==1.1.1 57 | matplotlib==3.0.3 58 | mistune==0.8.4 59 | mock==2.0.0 60 | msgpack==0.6.1 61 | nbconvert==5.5.0 62 | nbformat==4.4.0 63 | networkx==2.3 64 | nimfa==1.3.4 65 | notebook==5.7.8 66 | numexpr==2.6.9 67 | numpy==1.16.3 68 | openslide-python==1.1.1 69 | packaging==19.0 70 | pandas==0.24.2 71 | pandocfilters==1.4.2 72 | paramiko==2.4.2 73 | parso==0.4.0 74 | pbr==5.2.0 75 | pexpect==4.7.0 76 | pickleshare==0.7.5 77 | Pillow==6.0.0 78 | prometheus-client==0.6.0 79 | prompt-toolkit==2.0.9 80 | protobuf==3.7.1 81 | psutil==5.6.2 82 | ptyprocess==0.6.0 83 | pyasn1==0.4.5 84 | pycparser==2.19 85 | Pygments==2.3.1 86 | pylibmc==1.6.0 87 | PyNaCl==1.3.0 88 | pyparsing==2.4.0 89 | pyrsistent==0.15.1 90 | python-dateutil==2.8.0 91 | pytz==2019.1 92 | PyWavelets==1.0.3 93 | PyYAML==3.13 94 | pyzmq==18.0.1 95 | qtconsole==4.4.3 96 | requests==2.21.0 97 | scikit-build==0.9.0 98 | scikit-image==0.15.0 99 | scikit-learn==0.20.3 100 | scikit-video==1.1.11 101 | scipy==1.2.1 102 | Send2Trash==1.5.0 103 | six==1.12.0 104 | sortedcontainers==2.1.0 105 | tables==3.5.1 106 | tblib==1.3.2 107 | tensorboard==1.13.1 108 | tensorflow==1.5.0 109 | tensorflow-estimator==1.13.0 110 | tensorflow-tensorboard==1.5.1 111 | termcolor==1.1.0 112 | terminado==0.8.2 113 | testpath==0.4.2 114 | toolz==0.9.0 115 | tornado==6.0.2 116 | traitlets==4.3.2 117 | ujson==1.35 118 | urllib3==1.24.2 119 | wcwidth==0.1.7 120 | webencodings==0.5.1 121 | Werkzeug==0.15.2 122 | widgetsnbextension==3.4.2 123 | zict==0.1.4 124 | -------------------------------------------------------------------------------- /cli/sample_config.yml: -------------------------------------------------------------------------------- 1 | train: 2 | # Run name for the training session. 3 | run_name: my-run 4 | # Directory in which model checkpoints & summaries (for Tensorboard) will be saved. 5 | job_dir: jobs/ 6 | 7 | dataset: 8 | type: object_detection 9 | # From which directory to read the dataset. 10 | dir: tf_dataset 11 | 12 | model: 13 | type: fasterrcnn 14 | network: 15 | # Total number of classes to predict. 16 | num_classes: 1 17 | -------------------------------------------------------------------------------- /cli/slicer_cli_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "FasterNuclieDetectionCPU": { 3 | "type" : "python" 4 | }, 5 | "FasterNuclieDetectionGPU": { 6 | "type" : "python" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /evaluation/ex1-overlay_TCGA-G9-6362-01Z-00-DX1_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/evaluation/ex1-overlay_TCGA-G9-6362-01Z-00-DX1_3.png -------------------------------------------------------------------------------- /evaluation/ex2-overlay_TCGA-HE-7130-01Z-00-DX1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/evaluation/ex2-overlay_TCGA-HE-7130-01Z-00-DX1_2.png -------------------------------------------------------------------------------- /evaluation/mAP_eval_jsondata_conversion.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @__ramraj__ 3 | 4 | 5 | import json 6 | import os 7 | import xml.etree.ElementTree as ET 8 | import numpy as np 9 | import argparse 10 | 11 | 12 | """ 13 | GT JSON specs 14 | {"img_id": [[ ... ]], 15 | ... 16 | } 17 | 18 | 19 | PRED JSON specs 20 | 21 | {"img_id": {"boxes": [[ ... ]], 22 | "scores": [...] 23 | }, 24 | ... 25 | } 26 | """ 27 | 28 | 29 | def load_pred_json(name, JSON_dict, pad_size): 30 | """ 31 | This function will load each predicted JSON files. 32 | Read each predicted object's 4 coordinates and its labels with confidence score/probability to be an object. 33 | Return them as a 2-dim list. 34 | """ 35 | 36 | print 'Entering into Pred Loading ....' 37 | 38 | # img_name = '%s.png' % name.split('/')[-1][5:-5] 39 | # W = img_size[0] 40 | # H = img_size[1] 41 | 42 | _, jsonname = os.path.split(name) 43 | img_name = '%s.png' % jsonname[5:-5] 44 | 45 | preds = json.loads(open(name).read()).get('objects') 46 | 47 | pred_list = [] 48 | for obj in preds: 49 | value = [ 50 | obj['label'], # object class - objectness 51 | obj['bbox'][0], # x1 coordinate 52 | obj['bbox'][1], # y1 coordinate 53 | obj['bbox'][2], # x2 coordinate 54 | obj['bbox'][3], # y2 coordinate 55 | obj['prob'] # Confidence scor of this detected objectness 56 | ] 57 | pred_list.append(value) 58 | 59 | print('Total Number of Un-Filtered Label Counts per ROI : ', len(pred_list)) 60 | 61 | # ++++++++++++++++++++++++++ fILTERING ++++++++++++++++++++++++++ 62 | # Renove the cells, which are close to borders within the pad_size pixels 63 | pred_filtered_list = [] 64 | for obj in preds: 65 | 66 | x1 = obj['bbox'][0] 67 | y1 = obj['bbox'][1] 68 | x2 = obj['bbox'][2] 69 | y2 = obj['bbox'][3] 70 | 71 | x_cent = int((x1 + x2) / 2) 72 | y_cent = int((y1 + y2) / 2) 73 | 74 | # Check if bbox center is inside the valid error measurable region of 75 | # ROI. 76 | # if (x_cent >= pad_size) and (x_cent <= W - pad_size)\ 77 | # and (y_cent >= pad_size) and (y_cent <= H - pad_size): 78 | if True: 79 | 80 | value = [obj['label'], # object class - objectness 81 | x1, # x1 coordinate 82 | y1, # y1 coordinate 83 | x2, # x2 coordinate 84 | y2, # y2 coordinate 85 | obj['prob']] # Confidence scor of this detected objectness 86 | pred_filtered_list.append(value) 87 | 88 | print('Total Number of Filtered Prediction Counts per ROI : ', 89 | len(pred_filtered_list)) 90 | 91 | json_coords = np.asarray(np.asarray(pred_filtered_list)[:, 1:5], 92 | dtype=np.int32).\ 93 | tolist() 94 | json_scores = np.asarray(np.asarray(pred_filtered_list)[:, 5], 95 | dtype=np.float32).\ 96 | tolist() 97 | tmp_dict = {} 98 | tmp_dict['boxes'] = json_coords 99 | tmp_dict['scores'] = json_scores 100 | JSON_dict['%s' % img_name] = tmp_dict 101 | 102 | return pred_filtered_list, JSON_dict 103 | 104 | 105 | def load_gt_xml(name, JSON_dict, pad_size=0): 106 | """ 107 | This function will load each XML ground truth files. 108 | Read each objects 4 coordinates and their labels on a ROI. 109 | Return them as a 2-dim list. 110 | """ 111 | 112 | print 'Entering into GT Loading ....' 113 | 114 | img_name = '%s.png' % name.split('/')[-1][:-4] 115 | 116 | xml_list = [] 117 | tree = ET.parse(name) 118 | root = tree.getroot() 119 | 120 | W = int(root.findall('size/width')[0].text) 121 | H = int(root.findall('size/height')[0].text) 122 | D = int(root.findall('size/depth')[0].text) 123 | 124 | for member in root.findall('object'): 125 | value = [member[0].text, # object class - objectness 126 | int(member[4][0].text), # x1 coordinate 127 | int(member[4][1].text), # y1 coordinate 128 | int(member[4][2].text), # x2 coordinate 129 | int(member[4][3].text), # y2 coordinate 130 | 1.0 # object's confidence - ofcourse its 1.0 131 | ] 132 | xml_list.append(value) 133 | 134 | print('Total Number of Un-Filtered Label Counts per ROI : ', len(xml_list)) 135 | 136 | size = root.findall('size')[0] 137 | size = [int(si.text) for si in size] 138 | 139 | # ++++++++++++++++++++++++++ fILTERING ++++++++++++++++++++++++++ 140 | # Renove the cells, which are close to borders within the pad_size pixels 141 | xml_FILTERED_list = [] 142 | for member in root.findall('object'): 143 | x1 = int(member[4][0].text) 144 | y1 = int(member[4][1].text) 145 | x2 = int(member[4][2].text) 146 | y2 = int(member[4][3].text) 147 | 148 | x_cent = int((x1 + x2) / 2) 149 | y_cent = int((y1 + y2) / 2) 150 | 151 | # Check if bbox center is inside the valid error measurable region of 152 | # ROI. 153 | if (x_cent >= pad_size) and (x_cent <= W - pad_size)\ 154 | and (y_cent >= pad_size) and (y_cent <= H - pad_size): 155 | # if True: 156 | 157 | value = [member[0].text, # object class - objectness 158 | x1, # x1 coordinate 159 | y1, # y1 coordinate 160 | x2, # x2 coordinate 161 | y2, # y2 coordinate 162 | 1.0] # object's confidence - ofcourse its 1.0 163 | xml_FILTERED_list.append(value) 164 | 165 | print('Total Number of Fitlered Label Counts per ROI : ', len(xml_FILTERED_list)) 166 | xml_coords = np.asarray(np.asarray(xml_FILTERED_list)[:, 1:5], 167 | dtype=np.int32).\ 168 | tolist() 169 | JSON_dict['%s' % img_name] = xml_coords 170 | 171 | return xml_FILTERED_list, JSON_dict, size 172 | 173 | 174 | def convert(args): 175 | 176 | gt_path = args["groundtruth_path"] 177 | pred_path = args["prediction_path"] 178 | dest_path = args["dest_path"] 179 | mode = args["mode"] 180 | pad_size = args["pad_size"] 181 | 182 | if mode == 'Local': 183 | if not os.path.exists(os.path.join(dest_path, 'GT')): 184 | os.makedirs(os.path.join(dest_path, 'GT')) 185 | if not os.path.exists(os.path.join(dest_path, 'Pred')): 186 | os.makedirs(os.path.join(dest_path, 'Pred')) 187 | 188 | # ========================================================================== 189 | # ==================== Ground Truth XML 2 JSON conversion ================ 190 | # ========================================================================== 191 | 192 | gt_files = os.listdir(gt_path) 193 | gt_files = [gt_file for gt_file in gt_files if gt_file != '.DS_Store'] 194 | 195 | if MODE != 'Local': 196 | json_dict = {} 197 | 198 | for xmlfile in gt_files: 199 | if MODE == 'Local': 200 | json_dict = {} 201 | 202 | xmlfilename = os.path.join(gt_path, xmlfile) 203 | _, json_gt_dict, _ = load_gt_xml(xmlfilename, json_dict, pad_size) 204 | 205 | if MODE == 'Local': 206 | print json_gt_dict 207 | with open(os.path.join(dest_path, 'GT', 'formatted-%s.json' % xmlfile[:-4], 'w')) as gt_json: 208 | gt_json.write(json.dumps(json_gt_dict)) 209 | 210 | if MODE != 'Local': 211 | print json_gt_dict 212 | with open(os.path.join(dest_path, 'GT', 'collective-formatted-GT.json', 'w')) as gt_json: 213 | gt_json.write(json.dumps(json_gt_dict)) 214 | 215 | # ==================================================================== 216 | # ==================== Predictions JSON 2 JSON conversion ============ 217 | # ==================================================================== 218 | 219 | pred_files = os.listdir(pred_path) 220 | pred_files = [ 221 | pred_file for pred_file in pred_files if pred_file != '.DS_Store'] 222 | 223 | if mode != 'Local': 224 | json_dict = {} 225 | for jsonfile in pred_files: 226 | if mode == 'Local': 227 | json_dict = {} 228 | 229 | jsonfilename = os.path.join(pred_path, jsonfile) 230 | print(jsonfilename) 231 | _, json_pred_dict = load_pred_json(jsonfilename, json_dict, pad_size) 232 | 233 | if mode == 'Local': 234 | print json_pred_dict 235 | with open(os.path.join(dest_path, 'Pred', 'formatted-%s.json' % jsonfile[5:-5], 'w')) as pred_json: 236 | pred_json.write(json.dumps(json_pred_dict)) 237 | 238 | if mode != 'Local': 239 | print json_pred_dict 240 | with open(os.path.join(dest_path, 'Pred', 'collective-formatted-Pred.json', 'w')) as pred_json: 241 | pred_json.write(json.dumps(json_pred_dict)) 242 | 243 | 244 | if __name__ == '__main__': 245 | 246 | parser = argparse.ArgumentParser( 247 | description='Eval the predictions based on confidence score.') 248 | 249 | parser.add_argument( 250 | '--gt', '--groundtruth_path', help='ground truth files path in xml format', required=True) 251 | parser.add_argument( 252 | '--p', '--prediction_path', help='prediction files path in json format', required=True) 253 | parser.add_argument( 254 | '--d', '--dest_path', help='destination path to save results and overlays', required=True) 255 | 256 | parser.add_argument( 257 | '--m', '--mode', default='Global', choices=['Global', 'Local'], help='mode in which the csv files are run, either as individual experiments or single experiment') 258 | parser.add_argument( 259 | '--ps', '--pad_size', type=int, default=0, help='pixels left from all 4 borders') 260 | 261 | args = vars(parser.parse_args()) 262 | 263 | convert(args) 264 | -------------------------------------------------------------------------------- /evaluation/plot_precision-recall_confidence-interval_curve.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @__ramraj__ 3 | 4 | 5 | import matplotlib.pyplot as plt 6 | import numpy as np 7 | import sklearn.metrics as metrics 8 | import pandas as pd 9 | from sklearn.metrics import auc 10 | import os 11 | import argparse 12 | 13 | 14 | def get_PR_values(score_file, auc_dict, args): 15 | 16 | csv_path = args["csv_path"] 17 | csv_path = os.path.join(csv_path, 'Img-wise-Tables') 18 | 19 | # Results thresholded at 0.5 20 | df = pd.read_csv(csv_path + score_file) 21 | probabilities = df['PredScore'] 22 | classes = df['GTLabel'] 23 | 24 | # calculate precision-recall curve 25 | thresholds = np.sort(probabilities) 26 | precision = [] 27 | recall = [] 28 | 29 | for t in thresholds: 30 | precision.append(metrics.precision_score(classes, probabilities >= t)) 31 | recall.append(metrics.recall_score(classes, probabilities >= t)) 32 | 33 | recall.append(0) 34 | precision.append(1) 35 | 36 | print[recall[0], precision[0]] 37 | print[recall[-1], precision[-1]] 38 | 39 | auc_val = auc(recall, precision) 40 | print 'AUC score : ', auc_val 41 | 42 | auc_dict['AUC'].append(auc_val) 43 | auc_dict['ImageID'].append('%s.png' % score_file[9:-4]) 44 | 45 | tmp_PR = list(set(zip(precision, recall))) 46 | dff = pd.DataFrame(tmp_PR, columns=['Precision', 'Recall']) 47 | dff = dff.sort_values(by=['Recall']) 48 | 49 | # plt.step(dff['Recall'], dff['Precision'], where='post', color='b') 50 | # plt.xlim(0.0, 1.0) 51 | # plt.ylim(0.0, 1.0) 52 | # plt.show() 53 | 54 | return recall, precision, auc_dict 55 | 56 | 57 | def plot_conf_region_curve(args): 58 | 59 | csv_path = args["csv_path"] 60 | dst_path = args["dst_path"] 61 | 62 | csv_path = os.path.join(csv_path, 'Img-wise-Tables') 63 | 64 | if not os.path.exists(dst_path): 65 | os.makedirs(dst_path) 66 | 67 | auc_dict = {} 68 | auc_dict['AUC'] = [] 69 | auc_dict['ImageID'] = [] 70 | 71 | score_files = os.listdir(csv_path) 72 | score_files = [ 73 | score_file for score_file in score_files if score_file != '.DS_Store'] 74 | 75 | all_recalls = pd.DataFrame() 76 | 77 | for score_file in score_files: 78 | csv_dict = {} 79 | recall, precision, auc_dict = get_PR_values(score_file, auc_dict, args) 80 | 81 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 82 | # ++++++++++++++++++++++++ Rounding Mechanism +++++++++++++++++++++++++ 83 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 84 | recall = np.around(recall, decimals=2) 85 | precision = np.around(precision, decimals=2) 86 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 87 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 88 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 89 | 90 | PR = list(set(zip(precision, recall))) 91 | dff = pd.DataFrame(PR, columns=['Precision', 'Recall']) 92 | dff = dff.sort_values(by=['Recall']) 93 | dff.to_csv(os.path.join( 94 | dst_path, 'PRvalues_img_%s.csv' % score_file)) 95 | # plt.step(dff['Recall'], dff['Precision'], where='post', color='b') 96 | # plt.show() 97 | 98 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 99 | all_recalls = pd.concat([all_recalls, dff['Recall']]) 100 | all_recalls.index = pd.RangeIndex(len(all_recalls.index)) 101 | 102 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 103 | 104 | df = pd.DataFrame(auc_dict, columns=['ImageID', 'AUC']) 105 | df.to_csv(os.path.join(dst_path, 'AUC_values.csv')) 106 | 107 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 108 | # +++++++++++++++++++++ Concatanating the Results ++++++++++++++++++++++ 109 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 110 | 111 | all_recalls['SampleNumber'] = pd.RangeIndex(len(all_recalls.index)) 112 | all_recalls.columns = ['Recall', 'SampleNumber'] 113 | all_recalls.set_index('Recall', inplace=True) 114 | all_recalls = all_recalls[~all_recalls.index.duplicated(keep='first')] 115 | 116 | whole_PR_Table = pd.DataFrame() 117 | for score_file in score_files: 118 | df = pd.read_csv(os.path.join( 119 | dst_path, 'PRvalues_img_%s.csv' % score_file)) 120 | 121 | precision = df['Precision'] 122 | recall = df['Recall'] 123 | 124 | tmp_csv = pd.DataFrame({'Recall': recall, 'Precision': precision}) 125 | tmp_csv.set_index('Recall', inplace=True) 126 | tmp_csv = tmp_csv[~tmp_csv.index.duplicated(keep='first')] 127 | 128 | whole_PR_Table = pd.concat( 129 | [whole_PR_Table, tmp_csv], axis=1, join='outer') 130 | 131 | df = pd.DataFrame(whole_PR_Table) 132 | df.to_csv(os.path.join(dst_path, 'WithoutNaNFilling0_0.csv')) 133 | df = df.fillna(method='ffill') 134 | df.to_csv(os.path.join(dst_path, 'FilledPRValues0_0.csv')) 135 | 136 | sample_std = df.std(axis=1) 137 | sample_mean = df.mean(axis=1) 138 | 139 | plt.step(recall, precision, where='post', color='b') 140 | plt.show() 141 | 142 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 143 | # +++++++++++++++++++++ Plotting Section +++++++++++++++++++++++++++++++ 144 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 145 | 146 | FigSize = (10, 10) 147 | fig = plt.figure(figsize=FigSize) 148 | ax = fig.add_subplot(111, aspect='equal') 149 | ax.set_aspect('equal') 150 | 151 | lw = 0 152 | x_axis = df.index.values 153 | 154 | plt.plot(x_axis, sample_mean, 'b') 155 | plt.fill_between(x_axis, sample_mean - sample_std, 156 | sample_mean + sample_std, 157 | alpha=0.3, 158 | color="navy", lw=lw, 159 | step='mid', 160 | facecolor='green', 161 | interpolate=True, 162 | ) 163 | 164 | plt.xlabel("Recall") 165 | plt.ylabel("Precision") 166 | plt.xlim(0.0, 1.0) 167 | plt.ylim(0.0, 1.0) 168 | plt.show() 169 | fig.savefig(os.path.join(dst_path, 'conf-reg_curve.svg'), 170 | bbox_inches='tight') 171 | 172 | 173 | if __name__ == '__main__': 174 | 175 | parser = argparse.ArgumentParser( 176 | description='Eval the predictions based on confidence score.') 177 | 178 | parser.add_argument( 179 | '--c', '--csv_path', help='path which has csv files prepared with confidence scores', required=True) 180 | parser.add_argument( 181 | '--d', '--dst_path', help='desination path which saves the results', required=True) 182 | 183 | args = vars(parser.parse_args()) 184 | 185 | run(args) 186 | -------------------------------------------------------------------------------- /evaluation/plot_precision-recall_curve.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @__ramraj__ 3 | 4 | 5 | import matplotlib.pyplot as plt 6 | import numpy as np 7 | import sklearn.metrics as metrics 8 | import pandas as pd 9 | from sklearn.metrics import auc 10 | import os 11 | import argparse 12 | 13 | 14 | def get_stats_and_plot(filename, args): 15 | 16 | mode = args["mode"] 17 | dst_path = args["dst_path"] 18 | do_plot = args["do_plot"] 19 | 20 | df = pd.read_csv(filename) 21 | 22 | probabilities = df['PredScore'] 23 | classes = df['GTLabel'] 24 | 25 | # calculate precision-recall curve 26 | thresholds = np.sort(probabilities) 27 | precision = [] 28 | recall = [] 29 | for t in thresholds: 30 | precision.append(metrics.precision_score(classes, probabilities >= t)) 31 | recall.append(metrics.recall_score(classes, probabilities >= t)) 32 | 33 | recall.append(0) 34 | precision.append(1) 35 | 36 | print[recall[0], precision[0]] 37 | print[recall[-1], precision[-1]] 38 | 39 | tmp_PR = list(set(zip(precision, recall))) 40 | dff = pd.DataFrame(tmp_PR, columns=['Precision', 'Recall']) 41 | dff = dff.sort_values(by=['Recall']) 42 | 43 | auc_val = auc(dff['Recall'], dff['Precision']) 44 | print 'AUC score : ', auc_val 45 | 46 | fig_size = (9, 9) 47 | fig = plt.figure(figsize=fig_size) 48 | ax = fig.add_subplot(111, aspect='equal') 49 | ax.set_aspect('equal') 50 | plt.step(dff['Recall'], dff['Precision'], where='post', color='b') 51 | # plt.step(recall, precision, where='post', color='b') 52 | plt.xlim(0.0, 1.0) 53 | plt.ylim(0.0, 1.0) 54 | plt.xlabel('Recall') 55 | plt.ylabel('Precision') 56 | if do_plot: 57 | plt.show() 58 | 59 | # ========================================================================= 60 | # ============================ Saving SVG Files =========================== 61 | # ========================================================================= 62 | 63 | if mode == 'Local': 64 | fig.savefig(os.path.join(dst_path, 65 | 'Plots', 66 | '%s - PRCurve-AUC=%.2f.png' % ( 67 | filename[:-4], auc_val)), 68 | bbox_inches='tight') 69 | else: 70 | fig.savefig(os.path.join(dst_path, 71 | 'Plots', 72 | 'Overall_PRCurve-AUC=%.2f.png'.format(auc_val)), 73 | bbox_inches='tight') 74 | 75 | return recall, precision 76 | 77 | 78 | def run(args): 79 | 80 | mode = args["mode"] 81 | csv_path = args["csv_path"] 82 | dst_path = args["dst_path"] 83 | 84 | if mode == 'Local': 85 | csv_path = os.path.join(csv_path, 'Img-wise-Tables') 86 | 87 | if not os.path.exists(os.path.join(dst_path, 'Plots')): 88 | os.makedirs(os.path.join(dst_path, 'Plots')) 89 | 90 | if MODE == 'Local': 91 | files = os.listdir(csv_path) 92 | files = [os.path.join(csv_path, file) 93 | for file in files if file != '.DS_Store'] 94 | else: 95 | files = [os.path.join(csv_path, 'mapped_table.csv')] 96 | 97 | files.sort() 98 | for file in files: 99 | P, R = get_stats_and_plot(file, args) 100 | 101 | 102 | if __name__ == '__main__': 103 | 104 | parser = argparse.ArgumentParser( 105 | description='Eval the predictions based on confidence score.') 106 | 107 | parser.add_argument( 108 | '--c', '--csv_path', help='path which has csv files prepared with confidence scores', required=True) 109 | parser.add_argument( 110 | '--d', '--dst_path', help='desination path which saves the results', required=True) 111 | 112 | parser.add_argument( 113 | '--p', '--do_plot', default=False, help='boolean to either plot or not the results') 114 | 115 | parser.add_argument( 116 | '--m', '--mode', default='Global', choices=['Global', 'Local'], help='mode in which the csv files are run, either as individual experiments or single experiment') 117 | 118 | args = vars(parser.parse_args()) 119 | 120 | run(args) 121 | -------------------------------------------------------------------------------- /train/config_resnet101_ff12ep4_default.yml: -------------------------------------------------------------------------------- 1 | train: 2 | # Run on debug mode (which enables more logging). 3 | debug: False 4 | # Seed for random operations. 5 | seed: 6 | # Training batch size for images. FasterRCNN currently only supports 1. 7 | batch_size: 1 8 | # Base directory in which model checkpoints & summaries (for Tensorboard) will 9 | # be saved. 10 | job_dir: jobs_resnet101_ff2ep4_default/ 11 | # Ignore scope when loading from checkpoint (useful when training RPN first 12 | # and then RPN + RCNN). 13 | ignore_scope: 14 | # Enables TensorFlow debug mode, which stops and lets you analyze Tensors 15 | # after each `Session.run`. 16 | tf_debug: False 17 | # Name used to identify the run. Data inside `job_dir` will be stored under 18 | # `run_name`. 19 | run_name: 20 | # Disables logging and saving checkpoints. 21 | no_log: False 22 | # Displays debugging images with results every N steps. Debug mode must be 23 | # enabled. 24 | display_every_steps: 25 | # Display debugging images every N seconds. 26 | display_every_secs: 300 27 | # Shuffle the dataset. It should only be disabled when trying to reproduce 28 | # some problem on some sample. 29 | random_shuffle: True 30 | # Save Tensorboard timeline. 31 | save_timeline: False 32 | # The frequency, in seconds, that a checkpoint is saved. 33 | save_checkpoint_secs: 100 34 | # The maximum number of checkpoints to keep 35 | checkpoints_max_keep: 1 36 | # The frequency, in number of global steps, that the summaries are written to 37 | # disk. 38 | save_summaries_steps: 39 | # The frequency, in seconds, that the summaries are written to disk. If both 40 | # save_summaries_steps and save_summaries_secs are set to empty, then the 41 | # default summary saver isn't used. 42 | save_summaries_secs: 30 43 | # Run TensorFlow using full_trace mode for memory and running time logging 44 | # Debug mode must be enabled. 45 | full_trace: False 46 | # Clip gradients by norm, making sure the maximum value is 10. 47 | clip_by_norm: False 48 | # Learning rate config. 49 | learning_rate: 50 | # Because we're using kwargs, we want the learning_rate dict to be replaced 51 | # as a whole. 52 | _replace: True 53 | # Learning rate decay method; can be: ((empty), 'none', piecewise_constant, 54 | # exponential_decay, polynomial_decay) You can define different decay 55 | # methods using `decay_method` and defining all the necessary arguments. 56 | decay_method: 57 | learning_rate: 0.0003 58 | 59 | # Optimizer configuration. 60 | optimizer: 61 | # Because we're using kwargs, we want the optimizer dict to be replaced as a 62 | # whole. 63 | _replace: True 64 | # Type of optimizer to use (momentum, adam, gradient_descent, rmsprop). 65 | type: momentum 66 | # Any options are passed directly to the optimizer as kwarg. 67 | momentum: 0.9 68 | 69 | # Number of epochs (complete dataset batches) to run. 70 | num_epochs: 1000 71 | 72 | # Image visualization mode, options = train, eval, debug, (empty). 73 | # Default=(empty). 74 | image_vis: train 75 | # Variable summary visualization mode, options = full, reduced, (empty). 76 | var_vis: 77 | 78 | eval: 79 | # Image visualization mode, options = train, eval, debug, 80 | # (empty). Default=(empty). 81 | image_vis: eval 82 | 83 | dataset: 84 | type: object_detection 85 | # From which directory to read the dataset. 86 | dir: 'tf_dataset' 87 | # Which split of tfrecords to look for. 88 | split: train 89 | # Resize image according to min_size and max_size. 90 | image_preprocessing: 91 | min_size: 92 | max_size: 93 | # Data augmentation techniques. 94 | data_augmentation: 95 | - flip: 96 | left_right: True 97 | up_down: False 98 | prob: 0.5 99 | # Also available: 100 | # # If you resize to too small images, you may end up not having any anchors 101 | # # that aren't partially outside the image. 102 | # - resize: 103 | # min_size: 600 104 | # max_size: 1024 105 | # prob: 0.2 106 | # - patch: 107 | # min_height: 600 108 | # min_width: 600 109 | # prob: 0.2 110 | # - distortion: 111 | # brightness: 112 | # max_delta: 0.2 113 | # hue: 114 | # max_delta: 0.2 115 | # saturation: 116 | # lower: 0.5 117 | # upper: 1.5 118 | # prob: 0.3 119 | 120 | model: 121 | type: fasterrcnn 122 | network: 123 | # Total number of classes to predict. 124 | num_classes: 1 125 | # Use RCNN or just RPN. 126 | with_rcnn: True 127 | 128 | # Whether to use batch normalization in the model. 129 | batch_norm: False 130 | 131 | base_network: 132 | # Which type of pretrained network to use. 133 | architecture: resnet_v1_101 134 | # Number of units used at ResNet_v1 block2. This block makes the model deeper while going 135 | # from resnet50 to resnet200 136 | block2_num_units: 4 137 | # Number of units used at ResNet_v1 block3 138 | block3_num_units: 23 139 | # Should we train the pretrained network. 140 | trainable: True 141 | # From which file to load the weights. 142 | weights: 143 | # Should we download weights if not available. 144 | download: True 145 | # Which endpoint layer to use as feature map for network. 146 | endpoint: 147 | # Starting point after which all the variables in the base network will be 148 | # trainable. If not specified, then all the variables in the network will be 149 | # trainable. 150 | fine_tune_from: block2 151 | # Whether to train the ResNet's batch norm layers. 152 | train_batch_norm: False 153 | # Whether to use the base network's tail in the RCNN. 154 | use_tail: True 155 | # Whether to freeze the base network's tail. 156 | freeze_tail: False 157 | # Output stride for ResNet. 158 | output_stride: 16 159 | arg_scope: 160 | # Regularization. 161 | weight_decay: 0.0005 162 | 163 | loss: 164 | # Loss weights for calculating the total loss. 165 | rpn_cls_loss_weight: 1.0 166 | rpn_reg_loss_weights: 1.0 167 | rcnn_cls_loss_weight: 1.0 168 | rcnn_reg_loss_weights: 1.0 169 | 170 | anchors: 171 | # Base size to use for anchors. 172 | base_size: 256 173 | # Scale used for generating anchor sizes. 174 | scales: [0.25, 0.5, 1, 2] 175 | # Aspect ratios used for generating anchors. 176 | ratios: [0.5, 1, 2] 177 | # Stride depending on feature map size (of pretrained). 178 | stride: 16 179 | 180 | rpn: 181 | activation_function: relu6 182 | l2_regularization_scale: 0.0005 # Disable using 0. 183 | # Sigma for the smooth L1 regression loss. 184 | l1_sigma: 3.0 185 | # Number of filters for the RPN conv layer. 186 | num_channels: 512 187 | # Kernel shape for the RPN conv layer. 188 | kernel_shape: [3, 3] 189 | # Initializers for RPN weights. 190 | rpn_initializer: 191 | _replace: True 192 | type: random_normal_initializer 193 | mean: 0.0 194 | stddev: 0.01 195 | cls_initializer: 196 | _replace: True 197 | type: random_normal_initializer 198 | mean: 0.0 199 | stddev: 0.01 200 | bbox_initializer: 201 | _replace: True 202 | type: random_normal_initializer 203 | mean: 0.0 204 | stddev: 0.001 205 | 206 | proposals: 207 | # Total proposals to use before running NMS (sorted by score). 208 | pre_nms_top_n: 24000 209 | # Total proposals to use after NMS (sorted by score). 210 | post_nms_top_n: 4000 211 | # Option to apply NMS. 212 | apply_nms: True 213 | # NMS threshold used when removing "almost duplicates". 214 | nms_threshold: 0.7 215 | min_size: 0 # Disable using 0. 216 | # Run clipping of proposals after running NMS. 217 | clip_after_nms: False 218 | # Filter proposals from anchors partially outside the image. 219 | filter_outside_anchors: False 220 | # Minimum probability to be used as proposed object. 221 | min_prob_threshold: 0.0 222 | 223 | target: 224 | # Margin to crop proposals to close to the border. 225 | allowed_border: 0 226 | # Overwrite positives with negative if threshold is too low. 227 | clobber_positives: False 228 | # How much IoU with GT proposals must have to be marked as positive. 229 | foreground_threshold: 0.7 230 | # High and low thresholds with GT to be considered background. 231 | background_threshold_high: 0.3 232 | background_threshold_low: 0.0 233 | foreground_fraction: 0.5 234 | # Ration between background and foreground in minibatch. 235 | minibatch_size: 256 236 | # Assign to get consistent "random" selection in batch. 237 | random_seed: # Only to be used for debugging. 238 | 239 | rcnn: 240 | layer_sizes: [] # Could be e.g. `[4096, 4096]`. 241 | dropout_keep_prob: 1.0 242 | activation_function: relu6 243 | l2_regularization_scale: 0.0005 244 | # Sigma for the smooth L1 regression loss. 245 | l1_sigma: 1.0 246 | # Use average pooling before the last fully-connected layer. 247 | use_mean: True 248 | # Variances to normalize encoded targets with. 249 | target_normalization_variances: [0.1, 0.2] 250 | 251 | rcnn_initializer: 252 | _replace: True 253 | type: variance_scaling_initializer 254 | factor: 1.0 255 | uniform: True 256 | mode: FAN_AVG 257 | cls_initializer: 258 | _replace: True 259 | type: random_normal_initializer 260 | mean: 0.0 261 | stddev: 0.01 262 | bbox_initializer: 263 | _replace: True 264 | type: random_normal_initializer 265 | mean: 0.0 266 | stddev: 0.001 267 | 268 | roi: 269 | pooling_mode: crop 270 | pooled_width: 7 271 | pooled_height: 7 272 | padding: VALID 273 | 274 | proposals: 275 | # Maximum number of detections for each class. 276 | class_max_detections: 800 277 | # NMS threshold used to remove "almost duplicate" of the same class. 278 | class_nms_threshold: 0.3 279 | # Maximum total detections for an image (sorted by score). 280 | total_max_detections: 800 281 | # Minimum prob to be used as proposed object. 282 | min_prob_threshold: 0.5 283 | 284 | target: 285 | # Ratio between foreground and background samples in minibatch. 286 | foreground_fraction: 0.25 287 | minibatch_size: 256 288 | # Threshold with GT to be considered positive. 289 | foreground_threshold: 0.5 290 | # High and low threshold with GT to be considered negative. 291 | background_threshold_high: 0.5 292 | background_threshold_low: 0.0 293 | -------------------------------------------------------------------------------- /train/config_resnet101_ff12ep4_n-units18.yml: -------------------------------------------------------------------------------- 1 | train: 2 | # Run on debug mode (which enables more logging). 3 | debug: False 4 | # Seed for random operations. 5 | seed: 6 | # Training batch size for images. FasterRCNN currently only supports 1. 7 | batch_size: 1 8 | # Base directory in which model checkpoints & summaries (for Tensorboard) will 9 | # be saved. 10 | job_dir: jobs_resnet101_ff2ep4_n-units18/ 11 | # Ignore scope when loading from checkpoint (useful when training RPN first 12 | # and then RPN + RCNN). 13 | ignore_scope: 14 | # Enables TensorFlow debug mode, which stops and lets you analyze Tensors 15 | # after each `Session.run`. 16 | tf_debug: False 17 | # Name used to identify the run. Data inside `job_dir` will be stored under 18 | # `run_name`. 19 | run_name: 20 | # Disables logging and saving checkpoints. 21 | no_log: False 22 | # Displays debugging images with results every N steps. Debug mode must be 23 | # enabled. 24 | display_every_steps: 25 | # Display debugging images every N seconds. 26 | display_every_secs: 300 27 | # Shuffle the dataset. It should only be disabled when trying to reproduce 28 | # some problem on some sample. 29 | random_shuffle: True 30 | # Save Tensorboard timeline. 31 | save_timeline: False 32 | # The frequency, in seconds, that a checkpoint is saved. 33 | save_checkpoint_secs: 100 34 | # The maximum number of checkpoints to keep 35 | checkpoints_max_keep: 1 36 | # The frequency, in number of global steps, that the summaries are written to 37 | # disk. 38 | save_summaries_steps: 39 | # The frequency, in seconds, that the summaries are written to disk. If both 40 | # save_summaries_steps and save_summaries_secs are set to empty, then the 41 | # default summary saver isn't used. 42 | save_summaries_secs: 30 43 | # Run TensorFlow using full_trace mode for memory and running time logging 44 | # Debug mode must be enabled. 45 | full_trace: False 46 | # Clip gradients by norm, making sure the maximum value is 10. 47 | clip_by_norm: False 48 | # Learning rate config. 49 | learning_rate: 50 | # Because we're using kwargs, we want the learning_rate dict to be replaced 51 | # as a whole. 52 | _replace: True 53 | # Learning rate decay method; can be: ((empty), 'none', piecewise_constant, 54 | # exponential_decay, polynomial_decay) You can define different decay 55 | # methods using `decay_method` and defining all the necessary arguments. 56 | decay_method: 57 | learning_rate: 0.0003 58 | 59 | # Optimizer configuration. 60 | optimizer: 61 | # Because we're using kwargs, we want the optimizer dict to be replaced as a 62 | # whole. 63 | _replace: True 64 | # Type of optimizer to use (momentum, adam, gradient_descent, rmsprop). 65 | type: momentum 66 | # Any options are passed directly to the optimizer as kwarg. 67 | momentum: 0.9 68 | 69 | # Number of epochs (complete dataset batches) to run. 70 | num_epochs: 1000 71 | 72 | # Image visualization mode, options = train, eval, debug, (empty). 73 | # Default=(empty). 74 | image_vis: train 75 | # Variable summary visualization mode, options = full, reduced, (empty). 76 | var_vis: 77 | 78 | eval: 79 | # Image visualization mode, options = train, eval, debug, 80 | # (empty). Default=(empty). 81 | image_vis: eval 82 | 83 | dataset: 84 | type: object_detection 85 | # From which directory to read the dataset. 86 | dir: 'tf_dataset' 87 | # Which split of tfrecords to look for. 88 | split: train 89 | # Resize image according to min_size and max_size. 90 | image_preprocessing: 91 | min_size: 92 | max_size: 93 | # Data augmentation techniques. 94 | data_augmentation: 95 | - flip: 96 | left_right: True 97 | up_down: False 98 | prob: 0.5 99 | # Also available: 100 | # # If you resize to too small images, you may end up not having any anchors 101 | # # that aren't partially outside the image. 102 | # - resize: 103 | # min_size: 600 104 | # max_size: 1024 105 | # prob: 0.2 106 | # - patch: 107 | # min_height: 600 108 | # min_width: 600 109 | # prob: 0.2 110 | # - distortion: 111 | # brightness: 112 | # max_delta: 0.2 113 | # hue: 114 | # max_delta: 0.2 115 | # saturation: 116 | # lower: 0.5 117 | # upper: 1.5 118 | # prob: 0.3 119 | 120 | model: 121 | type: fasterrcnn 122 | network: 123 | # Total number of classes to predict. 124 | num_classes: 1 125 | # Use RCNN or just RPN. 126 | with_rcnn: True 127 | 128 | # Whether to use batch normalization in the model. 129 | batch_norm: False 130 | 131 | base_network: 132 | # Which type of pretrained network to use. 133 | architecture: resnet_v1_101 134 | # Number of units used at ResNet_v1 block2. This block makes the model deeper while going 135 | # from resnet50 to resnet200 136 | block2_num_units: 4 137 | # Number of units used at ResNet_v1 block3 138 | block3_num_units: 18 139 | # Should we train the pretrained network. 140 | trainable: True 141 | # From which file to load the weights. 142 | weights: 143 | # Should we download weights if not available. 144 | download: True 145 | # Which endpoint layer to use as feature map for network. 146 | endpoint: 147 | # Starting point after which all the variables in the base network will be 148 | # trainable. If not specified, then all the variables in the network will be 149 | # trainable. 150 | fine_tune_from: block2 151 | # Whether to train the ResNet's batch norm layers. 152 | train_batch_norm: False 153 | # Whether to use the base network's tail in the RCNN. 154 | use_tail: True 155 | # Whether to freeze the base network's tail. 156 | freeze_tail: False 157 | # Output stride for ResNet. 158 | output_stride: 16 159 | arg_scope: 160 | # Regularization. 161 | weight_decay: 0.0005 162 | 163 | loss: 164 | # Loss weights for calculating the total loss. 165 | rpn_cls_loss_weight: 1.0 166 | rpn_reg_loss_weights: 1.0 167 | rcnn_cls_loss_weight: 1.0 168 | rcnn_reg_loss_weights: 1.0 169 | 170 | anchors: 171 | # Base size to use for anchors. 172 | base_size: 256 173 | # Scale used for generating anchor sizes. 174 | scales: [0.25, 0.5, 1, 2] 175 | # Aspect ratios used for generating anchors. 176 | ratios: [0.5, 1, 2] 177 | # Stride depending on feature map size (of pretrained). 178 | stride: 16 179 | 180 | rpn: 181 | activation_function: relu6 182 | l2_regularization_scale: 0.0005 # Disable using 0. 183 | # Sigma for the smooth L1 regression loss. 184 | l1_sigma: 3.0 185 | # Number of filters for the RPN conv layer. 186 | num_channels: 512 187 | # Kernel shape for the RPN conv layer. 188 | kernel_shape: [3, 3] 189 | # Initializers for RPN weights. 190 | rpn_initializer: 191 | _replace: True 192 | type: random_normal_initializer 193 | mean: 0.0 194 | stddev: 0.01 195 | cls_initializer: 196 | _replace: True 197 | type: random_normal_initializer 198 | mean: 0.0 199 | stddev: 0.01 200 | bbox_initializer: 201 | _replace: True 202 | type: random_normal_initializer 203 | mean: 0.0 204 | stddev: 0.001 205 | 206 | proposals: 207 | # Total proposals to use before running NMS (sorted by score). 208 | pre_nms_top_n: 24000 209 | # Total proposals to use after NMS (sorted by score). 210 | post_nms_top_n: 4000 211 | # Option to apply NMS. 212 | apply_nms: True 213 | # NMS threshold used when removing "almost duplicates". 214 | nms_threshold: 0.7 215 | min_size: 0 # Disable using 0. 216 | # Run clipping of proposals after running NMS. 217 | clip_after_nms: False 218 | # Filter proposals from anchors partially outside the image. 219 | filter_outside_anchors: False 220 | # Minimum probability to be used as proposed object. 221 | min_prob_threshold: 0.0 222 | 223 | target: 224 | # Margin to crop proposals to close to the border. 225 | allowed_border: 0 226 | # Overwrite positives with negative if threshold is too low. 227 | clobber_positives: False 228 | # How much IoU with GT proposals must have to be marked as positive. 229 | foreground_threshold: 0.7 230 | # High and low thresholds with GT to be considered background. 231 | background_threshold_high: 0.3 232 | background_threshold_low: 0.0 233 | foreground_fraction: 0.5 234 | # Ration between background and foreground in minibatch. 235 | minibatch_size: 256 236 | # Assign to get consistent "random" selection in batch. 237 | random_seed: # Only to be used for debugging. 238 | 239 | rcnn: 240 | layer_sizes: [] # Could be e.g. `[4096, 4096]`. 241 | dropout_keep_prob: 1.0 242 | activation_function: relu6 243 | l2_regularization_scale: 0.0005 244 | # Sigma for the smooth L1 regression loss. 245 | l1_sigma: 1.0 246 | # Use average pooling before the last fully-connected layer. 247 | use_mean: True 248 | # Variances to normalize encoded targets with. 249 | target_normalization_variances: [0.1, 0.2] 250 | 251 | rcnn_initializer: 252 | _replace: True 253 | type: variance_scaling_initializer 254 | factor: 1.0 255 | uniform: True 256 | mode: FAN_AVG 257 | cls_initializer: 258 | _replace: True 259 | type: random_normal_initializer 260 | mean: 0.0 261 | stddev: 0.01 262 | bbox_initializer: 263 | _replace: True 264 | type: random_normal_initializer 265 | mean: 0.0 266 | stddev: 0.001 267 | 268 | roi: 269 | pooling_mode: crop 270 | pooled_width: 7 271 | pooled_height: 7 272 | padding: VALID 273 | 274 | proposals: 275 | # Maximum number of detections for each class. 276 | class_max_detections: 800 277 | # NMS threshold used to remove "almost duplicate" of the same class. 278 | class_nms_threshold: 0.3 279 | # Maximum total detections for an image (sorted by score). 280 | total_max_detections: 800 281 | # Minimum prob to be used as proposed object. 282 | min_prob_threshold: 0.5 283 | 284 | target: 285 | # Ratio between foreground and background samples in minibatch. 286 | foreground_fraction: 0.25 287 | minibatch_size: 256 288 | # Threshold with GT to be considered positive. 289 | foreground_threshold: 0.5 290 | # High and low threshold with GT to be considered negative. 291 | background_threshold_high: 0.5 292 | background_threshold_low: 0.0 293 | -------------------------------------------------------------------------------- /train/config_resnet50_ff12ep4.yml: -------------------------------------------------------------------------------- 1 | train: 2 | # Run on debug mode (which enables more logging). 3 | debug: False 4 | # Seed for random operations. 5 | seed: 6 | # Training batch size for images. FasterRCNN currently only supports 1. 7 | batch_size: 1 8 | # Base directory in which model checkpoints & summaries (for Tensorboard) will 9 | # be saved. 10 | job_dir: jobs_resnet50_ff2ep4/ 11 | # Ignore scope when loading from checkpoint (useful when training RPN first 12 | # and then RPN + RCNN). 13 | ignore_scope: 14 | # Enables TensorFlow debug mode, which stops and lets you analyze Tensors 15 | # after each `Session.run`. 16 | tf_debug: False 17 | # Name used to identify the run. Data inside `job_dir` will be stored under 18 | # `run_name`. 19 | run_name: 20 | # Disables logging and saving checkpoints. 21 | no_log: False 22 | # Displays debugging images with results every N steps. Debug mode must be 23 | # enabled. 24 | display_every_steps: 25 | # Display debugging images every N seconds. 26 | display_every_secs: 300 27 | # Shuffle the dataset. It should only be disabled when trying to reproduce 28 | # some problem on some sample. 29 | random_shuffle: True 30 | # Save Tensorboard timeline. 31 | save_timeline: False 32 | # The frequency, in seconds, that a checkpoint is saved. 33 | save_checkpoint_secs: 100 34 | # The maximum number of checkpoints to keep 35 | checkpoints_max_keep: 1 36 | # The frequency, in number of global steps, that the summaries are written to 37 | # disk. 38 | save_summaries_steps: 39 | # The frequency, in seconds, that the summaries are written to disk. If both 40 | # save_summaries_steps and save_summaries_secs are set to empty, then the 41 | # default summary saver isn't used. 42 | save_summaries_secs: 30 43 | # Run TensorFlow using full_trace mode for memory and running time logging 44 | # Debug mode must be enabled. 45 | full_trace: False 46 | # Clip gradients by norm, making sure the maximum value is 10. 47 | clip_by_norm: False 48 | # Learning rate config. 49 | learning_rate: 50 | # Because we're using kwargs, we want the learning_rate dict to be replaced 51 | # as a whole. 52 | _replace: True 53 | # Learning rate decay method; can be: ((empty), 'none', piecewise_constant, 54 | # exponential_decay, polynomial_decay) You can define different decay 55 | # methods using `decay_method` and defining all the necessary arguments. 56 | decay_method: 57 | learning_rate: 0.0003 58 | 59 | # Optimizer configuration. 60 | optimizer: 61 | # Because we're using kwargs, we want the optimizer dict to be replaced as a 62 | # whole. 63 | _replace: True 64 | # Type of optimizer to use (momentum, adam, gradient_descent, rmsprop). 65 | type: momentum 66 | # Any options are passed directly to the optimizer as kwarg. 67 | momentum: 0.9 68 | 69 | # Number of epochs (complete dataset batches) to run. 70 | num_epochs: 1000 71 | 72 | # Image visualization mode, options = train, eval, debug, (empty). 73 | # Default=(empty). 74 | image_vis: train 75 | # Variable summary visualization mode, options = full, reduced, (empty). 76 | var_vis: 77 | 78 | eval: 79 | # Image visualization mode, options = train, eval, debug, 80 | # (empty). Default=(empty). 81 | image_vis: eval 82 | 83 | dataset: 84 | type: object_detection 85 | # From which directory to read the dataset. 86 | dir: 'tf_dataset' 87 | # Which split of tfrecords to look for. 88 | split: train 89 | # Resize image according to min_size and max_size. 90 | image_preprocessing: 91 | min_size: 92 | max_size: 93 | # Data augmentation techniques. 94 | data_augmentation: 95 | - flip: 96 | left_right: True 97 | up_down: False 98 | prob: 0.5 99 | # Also available: 100 | # # If you resize to too small images, you may end up not having any anchors 101 | # # that aren't partially outside the image. 102 | # - resize: 103 | # min_size: 600 104 | # max_size: 1024 105 | # prob: 0.2 106 | # - patch: 107 | # min_height: 600 108 | # min_width: 600 109 | # prob: 0.2 110 | # - distortion: 111 | # brightness: 112 | # max_delta: 0.2 113 | # hue: 114 | # max_delta: 0.2 115 | # saturation: 116 | # lower: 0.5 117 | # upper: 1.5 118 | # prob: 0.3 119 | 120 | model: 121 | type: fasterrcnn 122 | network: 123 | # Total number of classes to predict. 124 | num_classes: 1 125 | # Use RCNN or just RPN. 126 | with_rcnn: True 127 | 128 | # Whether to use batch normalization in the model. 129 | batch_norm: False 130 | 131 | base_network: 132 | # Which type of pretrained network to use. 133 | architecture: resnet_v1_50 134 | # Number of units used at ResNet_v1 block2. This block makes the model deeper while going 135 | # from resnet50 to resnet200 136 | block2_num_units: 4 137 | # Number of units used at ResNet_v1 block3 138 | block3_num_units: 6 139 | # Should we train the pretrained network. 140 | trainable: True 141 | # From which file to load the weights. 142 | weights: 143 | # Should we download weights if not available. 144 | download: True 145 | # Which endpoint layer to use as feature map for network. 146 | endpoint: 147 | # Starting point after which all the variables in the base network will be 148 | # trainable. If not specified, then all the variables in the network will be 149 | # trainable. 150 | fine_tune_from: block2 151 | # Whether to train the ResNet's batch norm layers. 152 | train_batch_norm: False 153 | # Whether to use the base network's tail in the RCNN. 154 | use_tail: True 155 | # Whether to freeze the base network's tail. 156 | freeze_tail: False 157 | # Output stride for ResNet. 158 | output_stride: 16 159 | arg_scope: 160 | # Regularization. 161 | weight_decay: 0.0005 162 | 163 | loss: 164 | # Loss weights for calculating the total loss. 165 | rpn_cls_loss_weight: 1.0 166 | rpn_reg_loss_weights: 1.0 167 | rcnn_cls_loss_weight: 1.0 168 | rcnn_reg_loss_weights: 1.0 169 | 170 | anchors: 171 | # Base size to use for anchors. 172 | base_size: 256 173 | # Scale used for generating anchor sizes. 174 | scales: [0.25, 0.5, 1, 2] 175 | # Aspect ratios used for generating anchors. 176 | ratios: [0.5, 1, 2] 177 | # Stride depending on feature map size (of pretrained). 178 | stride: 16 179 | 180 | rpn: 181 | activation_function: relu6 182 | l2_regularization_scale: 0.0005 # Disable using 0. 183 | # Sigma for the smooth L1 regression loss. 184 | l1_sigma: 3.0 185 | # Number of filters for the RPN conv layer. 186 | num_channels: 512 187 | # Kernel shape for the RPN conv layer. 188 | kernel_shape: [3, 3] 189 | # Initializers for RPN weights. 190 | rpn_initializer: 191 | _replace: True 192 | type: random_normal_initializer 193 | mean: 0.0 194 | stddev: 0.01 195 | cls_initializer: 196 | _replace: True 197 | type: random_normal_initializer 198 | mean: 0.0 199 | stddev: 0.01 200 | bbox_initializer: 201 | _replace: True 202 | type: random_normal_initializer 203 | mean: 0.0 204 | stddev: 0.001 205 | 206 | proposals: 207 | # Total proposals to use before running NMS (sorted by score). 208 | pre_nms_top_n: 24000 209 | # Total proposals to use after NMS (sorted by score). 210 | post_nms_top_n: 4000 211 | # Option to apply NMS. 212 | apply_nms: True 213 | # NMS threshold used when removing "almost duplicates". 214 | nms_threshold: 0.7 215 | min_size: 0 # Disable using 0. 216 | # Run clipping of proposals after running NMS. 217 | clip_after_nms: False 218 | # Filter proposals from anchors partially outside the image. 219 | filter_outside_anchors: False 220 | # Minimum probability to be used as proposed object. 221 | min_prob_threshold: 0.0 222 | 223 | target: 224 | # Margin to crop proposals to close to the border. 225 | allowed_border: 0 226 | # Overwrite positives with negative if threshold is too low. 227 | clobber_positives: False 228 | # How much IoU with GT proposals must have to be marked as positive. 229 | foreground_threshold: 0.7 230 | # High and low thresholds with GT to be considered background. 231 | background_threshold_high: 0.3 232 | background_threshold_low: 0.0 233 | foreground_fraction: 0.5 234 | # Ration between background and foreground in minibatch. 235 | minibatch_size: 256 236 | # Assign to get consistent "random" selection in batch. 237 | random_seed: # Only to be used for debugging. 238 | 239 | rcnn: 240 | layer_sizes: [] # Could be e.g. `[4096, 4096]`. 241 | dropout_keep_prob: 1.0 242 | activation_function: relu6 243 | l2_regularization_scale: 0.0005 244 | # Sigma for the smooth L1 regression loss. 245 | l1_sigma: 1.0 246 | # Use average pooling before the last fully-connected layer. 247 | use_mean: True 248 | # Variances to normalize encoded targets with. 249 | target_normalization_variances: [0.1, 0.2] 250 | 251 | rcnn_initializer: 252 | _replace: True 253 | type: variance_scaling_initializer 254 | factor: 1.0 255 | uniform: True 256 | mode: FAN_AVG 257 | cls_initializer: 258 | _replace: True 259 | type: random_normal_initializer 260 | mean: 0.0 261 | stddev: 0.01 262 | bbox_initializer: 263 | _replace: True 264 | type: random_normal_initializer 265 | mean: 0.0 266 | stddev: 0.001 267 | 268 | roi: 269 | pooling_mode: crop 270 | pooled_width: 7 271 | pooled_height: 7 272 | padding: VALID 273 | 274 | proposals: 275 | # Maximum number of detections for each class. 276 | class_max_detections: 800 277 | # NMS threshold used to remove "almost duplicate" of the same class. 278 | class_nms_threshold: 0.3 279 | # Maximum total detections for an image (sorted by score). 280 | total_max_detections: 800 281 | # Minimum prob to be used as proposed object. 282 | min_prob_threshold: 0.5 283 | 284 | target: 285 | # Ratio between foreground and background samples in minibatch. 286 | foreground_fraction: 0.25 287 | minibatch_size: 256 288 | # Threshold with GT to be considered positive. 289 | foreground_threshold: 0.5 290 | # High and low threshold with GT to be considered negative. 291 | background_threshold_high: 0.5 292 | background_threshold_low: 0.0 293 | -------------------------------------------------------------------------------- /train/config_resnet50_ff1ep2.yml: -------------------------------------------------------------------------------- 1 | train: 2 | # Run on debug mode (which enables more logging). 3 | debug: False 4 | # Seed for random operations. 5 | seed: 6 | # Training batch size for images. FasterRCNN currently only supports 1. 7 | batch_size: 1 8 | # Base directory in which model checkpoints & summaries (for Tensorboard) will 9 | # be saved. 10 | job_dir: jobs_resnet50_ff1ep2/ 11 | # Ignore scope when loading from checkpoint (useful when training RPN first 12 | # and then RPN + RCNN). 13 | ignore_scope: 14 | # Enables TensorFlow debug mode, which stops and lets you analyze Tensors 15 | # after each `Session.run`. 16 | tf_debug: False 17 | # Name used to identify the run. Data inside `job_dir` will be stored under 18 | # `run_name`. 19 | run_name: 20 | # Disables logging and saving checkpoints. 21 | no_log: False 22 | # Displays debugging images with results every N steps. Debug mode must be 23 | # enabled. 24 | display_every_steps: 25 | # Display debugging images every N seconds. 26 | display_every_secs: 300 27 | # Shuffle the dataset. It should only be disabled when trying to reproduce 28 | # some problem on some sample. 29 | random_shuffle: True 30 | # Save Tensorboard timeline. 31 | save_timeline: False 32 | # The frequency, in seconds, that a checkpoint is saved. 33 | save_checkpoint_secs: 100 34 | # The maximum number of checkpoints to keep 35 | checkpoints_max_keep: 1 36 | # The frequency, in number of global steps, that the summaries are written to 37 | # disk. 38 | save_summaries_steps: 39 | # The frequency, in seconds, that the summaries are written to disk. If both 40 | # save_summaries_steps and save_summaries_secs are set to empty, then the 41 | # default summary saver isn't used. 42 | save_summaries_secs: 30 43 | # Run TensorFlow using full_trace mode for memory and running time logging 44 | # Debug mode must be enabled. 45 | full_trace: False 46 | # Clip gradients by norm, making sure the maximum value is 10. 47 | clip_by_norm: False 48 | # Learning rate config. 49 | learning_rate: 50 | # Because we're using kwargs, we want the learning_rate dict to be replaced 51 | # as a whole. 52 | _replace: True 53 | # Learning rate decay method; can be: ((empty), 'none', piecewise_constant, 54 | # exponential_decay, polynomial_decay) You can define different decay 55 | # methods using `decay_method` and defining all the necessary arguments. 56 | decay_method: 57 | learning_rate: 0.0003 58 | 59 | # Optimizer configuration. 60 | optimizer: 61 | # Because we're using kwargs, we want the optimizer dict to be replaced as a 62 | # whole. 63 | _replace: True 64 | # Type of optimizer to use (momentum, adam, gradient_descent, rmsprop). 65 | type: momentum 66 | # Any options are passed directly to the optimizer as kwarg. 67 | momentum: 0.9 68 | 69 | # Number of epochs (complete dataset batches) to run. 70 | num_epochs: 1000 71 | 72 | # Image visualization mode, options = train, eval, debug, (empty). 73 | # Default=(empty). 74 | image_vis: train 75 | # Variable summary visualization mode, options = full, reduced, (empty). 76 | var_vis: 77 | 78 | eval: 79 | # Image visualization mode, options = train, eval, debug, 80 | # (empty). Default=(empty). 81 | image_vis: eval 82 | 83 | dataset: 84 | type: object_detection 85 | # From which directory to read the dataset. 86 | dir: 'tf_dataset' 87 | # Which split of tfrecords to look for. 88 | split: train 89 | # Resize image according to min_size and max_size. 90 | image_preprocessing: 91 | min_size: 92 | max_size: 93 | # Data augmentation techniques. 94 | data_augmentation: 95 | - flip: 96 | left_right: True 97 | up_down: False 98 | prob: 0.5 99 | # Also available: 100 | # # If you resize to too small images, you may end up not having any anchors 101 | # # that aren't partially outside the image. 102 | # - resize: 103 | # min_size: 600 104 | # max_size: 1024 105 | # prob: 0.2 106 | # - patch: 107 | # min_height: 600 108 | # min_width: 600 109 | # prob: 0.2 110 | # - distortion: 111 | # brightness: 112 | # max_delta: 0.2 113 | # hue: 114 | # max_delta: 0.2 115 | # saturation: 116 | # lower: 0.5 117 | # upper: 1.5 118 | # prob: 0.3 119 | 120 | model: 121 | type: fasterrcnn 122 | network: 123 | # Total number of classes to predict. 124 | num_classes: 1 125 | # Use RCNN or just RPN. 126 | with_rcnn: True 127 | 128 | # Whether to use batch normalization in the model. 129 | batch_norm: False 130 | 131 | base_network: 132 | # Which type of pretrained network to use. 133 | architecture: resnet_v1_50 134 | # Number of units used at ResNet_v1 block2. This block makes the model deeper while going 135 | # from resnet50 to resnet200 136 | block2_num_units: 4 137 | # Number of units used at ResNet_v1 block3 138 | block3_num_units: 6 139 | # Should we train the pretrained network. 140 | trainable: True 141 | # From which file to load the weights. 142 | weights: 143 | # Should we download weights if not available. 144 | download: True 145 | # Which endpoint layer to use as feature map for network. 146 | endpoint: block2 147 | # Starting point after which all the variables in the base network will be 148 | # trainable. If not specified, then all the variables in the network will be 149 | # trainable. 150 | fine_tune_from: block1 151 | # Whether to train the ResNet's batch norm layers. 152 | train_batch_norm: False 153 | # Whether to use the base network's tail in the RCNN. 154 | use_tail: True 155 | # Whether to freeze the base network's tail. 156 | freeze_tail: False 157 | # Output stride for ResNet. 158 | output_stride: 16 159 | arg_scope: 160 | # Regularization. 161 | weight_decay: 0.0005 162 | 163 | loss: 164 | # Loss weights for calculating the total loss. 165 | rpn_cls_loss_weight: 1.0 166 | rpn_reg_loss_weights: 1.0 167 | rcnn_cls_loss_weight: 1.0 168 | rcnn_reg_loss_weights: 1.0 169 | 170 | anchors: 171 | # Base size to use for anchors. 172 | base_size: 256 173 | # Scale used for generating anchor sizes. 174 | scales: [0.25, 0.5, 1, 2] 175 | # Aspect ratios used for generating anchors. 176 | ratios: [0.5, 1, 2] 177 | # Stride depending on feature map size (of pretrained). 178 | stride: 16 179 | 180 | rpn: 181 | activation_function: relu6 182 | l2_regularization_scale: 0.0005 # Disable using 0. 183 | # Sigma for the smooth L1 regression loss. 184 | l1_sigma: 3.0 185 | # Number of filters for the RPN conv layer. 186 | num_channels: 512 187 | # Kernel shape for the RPN conv layer. 188 | kernel_shape: [3, 3] 189 | # Initializers for RPN weights. 190 | rpn_initializer: 191 | _replace: True 192 | type: random_normal_initializer 193 | mean: 0.0 194 | stddev: 0.01 195 | cls_initializer: 196 | _replace: True 197 | type: random_normal_initializer 198 | mean: 0.0 199 | stddev: 0.01 200 | bbox_initializer: 201 | _replace: True 202 | type: random_normal_initializer 203 | mean: 0.0 204 | stddev: 0.001 205 | 206 | proposals: 207 | # Total proposals to use before running NMS (sorted by score). 208 | pre_nms_top_n: 24000 209 | # Total proposals to use after NMS (sorted by score). 210 | post_nms_top_n: 4000 211 | # Option to apply NMS. 212 | apply_nms: True 213 | # NMS threshold used when removing "almost duplicates". 214 | nms_threshold: 0.7 215 | min_size: 0 # Disable using 0. 216 | # Run clipping of proposals after running NMS. 217 | clip_after_nms: False 218 | # Filter proposals from anchors partially outside the image. 219 | filter_outside_anchors: False 220 | # Minimum probability to be used as proposed object. 221 | min_prob_threshold: 0.0 222 | 223 | target: 224 | # Margin to crop proposals to close to the border. 225 | allowed_border: 0 226 | # Overwrite positives with negative if threshold is too low. 227 | clobber_positives: False 228 | # How much IoU with GT proposals must have to be marked as positive. 229 | foreground_threshold: 0.7 230 | # High and low thresholds with GT to be considered background. 231 | background_threshold_high: 0.3 232 | background_threshold_low: 0.0 233 | foreground_fraction: 0.5 234 | # Ration between background and foreground in minibatch. 235 | minibatch_size: 256 236 | # Assign to get consistent "random" selection in batch. 237 | random_seed: # Only to be used for debugging. 238 | 239 | rcnn: 240 | layer_sizes: [] # Could be e.g. `[4096, 4096]`. 241 | dropout_keep_prob: 1.0 242 | activation_function: relu6 243 | l2_regularization_scale: 0.0005 244 | # Sigma for the smooth L1 regression loss. 245 | l1_sigma: 1.0 246 | # Use average pooling before the last fully-connected layer. 247 | use_mean: True 248 | # Variances to normalize encoded targets with. 249 | target_normalization_variances: [0.1, 0.2] 250 | 251 | rcnn_initializer: 252 | _replace: True 253 | type: variance_scaling_initializer 254 | factor: 1.0 255 | uniform: True 256 | mode: FAN_AVG 257 | cls_initializer: 258 | _replace: True 259 | type: random_normal_initializer 260 | mean: 0.0 261 | stddev: 0.01 262 | bbox_initializer: 263 | _replace: True 264 | type: random_normal_initializer 265 | mean: 0.0 266 | stddev: 0.001 267 | 268 | roi: 269 | pooling_mode: crop 270 | pooled_width: 7 271 | pooled_height: 7 272 | padding: VALID 273 | 274 | proposals: 275 | # Maximum number of detections for each class. 276 | class_max_detections: 800 277 | # NMS threshold used to remove "almost duplicate" of the same class. 278 | class_nms_threshold: 0.3 279 | # Maximum total detections for an image (sorted by score). 280 | total_max_detections: 800 281 | # Minimum prob to be used as proposed object. 282 | min_prob_threshold: 0.5 283 | 284 | target: 285 | # Ratio between foreground and background samples in minibatch. 286 | foreground_fraction: 0.25 287 | minibatch_size: 256 288 | # Threshold with GT to be considered positive. 289 | foreground_threshold: 0.5 290 | # High and low threshold with GT to be considered negative. 291 | background_threshold_high: 0.5 292 | background_threshold_low: 0.0 293 | -------------------------------------------------------------------------------- /train/train_model_variants.txt: -------------------------------------------------------------------------------- 1 | 2 | Experiments: 3 | 4 | TIME PROFILING 5 | 6 | 7 | ======================================================================================== 8 | ====================== Variants in finetune and endpoint =============================== 9 | ======================================================================================== 10 | 11 | 12 | 1. FasterRCNN 13 | BaseModel : ResNet-50 14 | Finetune-from : Block 2 15 | End-point : Block 4 16 | Data : Miccai + Warwick combined train data 17 | max_det : 1000 18 | job_dir : jobs_resnet50_ff2ep4/ 19 | 20 | 21 | 22 | 23 | 2. FasterRCNN 24 | BaseModel : ResNet-50 25 | Finetune-from : Block 1 26 | End-point : Block 2 27 | Data : Miccai + Warwick combined train data 28 | max_det : 1000 29 | job_dir : jobs_resnet50_ff1ep2/ 30 | 31 | 32 | 33 | 34 | 3. FasterRCNN 35 | BaseModel : ResNet-101 36 | Finetune-from : Block 1 37 | End-point : Block 2 38 | Data : Miccai + Warwick combined train data 39 | max_det : 1000 40 | job_dir : jobs_resnet101_ff1ep2/ 41 | 42 | 43 | 44 | 3. FasterRCNN 45 | BaseModel : ResNet-101 46 | Finetune-from : Block 2 47 | End-point : Block 4 48 | Data : Miccai + Warwick combined train data 49 | max_det : 1000 50 | job_dir : jobs_resnet101_ff2ep4/ 51 | 52 | 53 | ======================================================================================== 54 | ================================= Variants in block 3 tuning =========================== 55 | ======================================================================================== 56 | 57 | 58 | 1 . Base Network 59 | 60 | resnet_V1 61 | resnet50 62 | ff1ep2 63 | ff2ep4 64 | block3-num_units = [6] 65 | resnet101 66 | ff1ep2 67 | ff2ep4 68 | block3-num_units = [6, 12, 18, 23] 69 | 70 | 71 | resnet_V2 72 | resnet50 73 | ff1ep2 74 | ff2ep4 75 | resnet101 76 | ff1ep2 77 | ff2ep4 78 | 79 | vgg16 80 | 81 | trucated vgg16 82 | 83 | mobilenet 84 | 85 | 86 | 2 . RPN + ROIPooling + RCNN 87 | 88 | 89 | 90 | ======================================================================================== 91 | 92 | -------------------------------------------------------------------------------- /utils/cli_utils.py: -------------------------------------------------------------------------------- 1 | from argparse import Namespace 2 | from datetime import timedelta 3 | import psutil 4 | import numpy as np 5 | import skimage.measure 6 | import skimage.morphology 7 | 8 | import histomicstk.preprocessing.color_deconvolution as htk_cdeconv 9 | import histomicstk.segmentation as htk_seg 10 | import histomicstk.utils as htk_utils 11 | 12 | import large_image 13 | 14 | 15 | # These defaults are only used if girder is not present 16 | # Use memcached by default. 17 | large_image.cache_util.cachefactory.defaultConfig[ 18 | 'cache_backend'] = 'memcached' 19 | # If memcached is unavilable, specify the fraction of memory that python 20 | # caching is allowed to use. This is deliberately small. 21 | large_image.cache_util.cachefactory.defaultConfig[ 22 | 'cache_python_memory_portion'] = 32 23 | 24 | 25 | def get_stain_vector(args, index): 26 | """Get the stain corresponding to args.stain_$index and 27 | args.stain_$index_vector. If the former is not "custom", all the 28 | latter's elements must be -1. 29 | """ 30 | args = args._asdict() if hasattr(args, '_asdict') else vars(args) 31 | stain = args['stain_' + str(index)] 32 | stain_vector = args['stain_' + str(index) + '_vector'] 33 | if all(x == -1 for x in stain_vector): # Magic default value 34 | if stain == 'custom': 35 | raise ValueError('If "custom" is chosen for a stain, ' 36 | 'a stain vector must be provided.') 37 | return htk_cdeconv.stain_color_map[stain] 38 | else: 39 | if stain == 'custom': 40 | return stain_vector 41 | raise ValueError('Unless "custom" is chosen for a stain, ' 42 | 'no stain vector may be provided.') 43 | 44 | 45 | def get_stain_matrix(args, count=3): 46 | """Get the stain matrix corresponding to the args.stain_$index and 47 | args.stain_$index_vector arguments for values of index 1 to count. 48 | Return a numpy array of column vectors. 49 | """ 50 | return np.array([get_stain_vector(args, i + 1) for i in range(count)]).T 51 | 52 | 53 | def segment_wsi_foreground_at_low_res(ts, lres_size=2048): 54 | 55 | ts_metadata = ts.getMetadata() 56 | 57 | # get image at low-res 58 | maxSize = max(ts_metadata['sizeX'], ts_metadata['sizeY']) 59 | maxSize = float(max(maxSize, lres_size)) 60 | 61 | downsample_factor = 2.0 ** np.floor(np.log2(maxSize / lres_size)) 62 | 63 | fgnd_seg_mag = ts_metadata['magnification'] / downsample_factor 64 | 65 | fgnd_seg_scale = {'magnification': fgnd_seg_mag} 66 | 67 | im_lres, _ = ts.getRegion( 68 | scale=fgnd_seg_scale, 69 | format=large_image.tilesource.TILE_FORMAT_NUMPY 70 | ) 71 | 72 | im_lres = im_lres[:, :, :3] 73 | 74 | # compute foreground mask at low-res 75 | im_fgnd_mask_lres = htk_utils.simple_mask(im_lres) 76 | 77 | return im_fgnd_mask_lres, fgnd_seg_scale 78 | 79 | 80 | def convert_preds_to_utilformat(nuclei_obj_props, pred_probs, 81 | ignore_border_nuclei, 82 | im_tile_size): 83 | 84 | annot_list = [] 85 | for i in range(nuclei_obj_props.shape[0]): 86 | obj_coords = nuclei_obj_props.loc[i] 87 | 88 | xmin = int(obj_coords[0]) 89 | ymin = int(obj_coords[1]) 90 | xmax = int(obj_coords[2]) 91 | ymax = int(obj_coords[3]) 92 | 93 | # ===================================================================== 94 | 95 | if ignore_border_nuclei and \ 96 | (xmin == 0 or xmax == im_tile_size or ymin == 0 or ymax == im_tile_size): 97 | continue 98 | 99 | # ===================================================================== 100 | 101 | cx = (xmin + xmax) / 2 102 | cy = (ymin + ymax) / 2 103 | 104 | height = ymax - ymin 105 | width = xmax - xmin 106 | 107 | tmp_prob = float(pred_probs[i]) 108 | 109 | color = 'rgba(0,255,0,0)' 110 | 111 | tmp_dict = {} 112 | tmp_dict['center'] = [cx, cy] 113 | tmp_dict['height'] = height 114 | tmp_dict['width'] = width 115 | tmp_dict['lineColor'] = color 116 | tmp_dict["pred_prob"] = tmp_prob 117 | 118 | annot_list.append(tmp_dict) 119 | 120 | return annot_list 121 | 122 | 123 | def create_tile_nuclei_bbox_annotations(nuclei_obj_props, tile_info): 124 | 125 | cell_annot_list = [] 126 | 127 | gx = tile_info['gx'] 128 | gy = tile_info['gy'] 129 | wfrac = tile_info['gwidth'] / np.double(tile_info['width']) 130 | hfrac = tile_info['gheight'] / np.double(tile_info['height']) 131 | 132 | for i in range(len(nuclei_obj_props)): 133 | 134 | cx = nuclei_obj_props[i]['center'][0] 135 | cy = nuclei_obj_props[i]['center'][1] 136 | width = nuclei_obj_props[i]['width'] + 1 137 | height = nuclei_obj_props[i]['height'] + 1 138 | 139 | class_color = nuclei_obj_props[i]['lineColor'] 140 | 141 | # convert to base pixel coords 142 | cx = np.round(gx + cx * wfrac, 2) 143 | cy = np.round(gy + cy * hfrac, 2) 144 | width = np.round(width * wfrac, 2) 145 | height = np.round(height * hfrac, 2) 146 | 147 | # create annotation json 148 | cur_bbox = { 149 | "type": "rectangle", 150 | "center": [cx, cy, 0], 151 | "width": width, 152 | "height": height, 153 | "rotation": 0, 154 | "fillColor": "rgba(0,0,0,0)", 155 | "lineColor": class_color 156 | } 157 | 158 | cell_annot_list.append(cur_bbox) 159 | 160 | return cell_annot_list 161 | 162 | 163 | def create_tile_nuclei_boundary_annotations(im_nuclei_seg_mask, tile_info): 164 | 165 | nuclei_annot_list = [] 166 | 167 | gx = tile_info['gx'] 168 | gy = tile_info['gy'] 169 | wfrac = tile_info['gwidth'] / np.double(tile_info['width']) 170 | hfrac = tile_info['gheight'] / np.double(tile_info['height']) 171 | 172 | by, bx = htk_seg.label.trace_object_boundaries(im_nuclei_seg_mask, 173 | trace_all=True) 174 | 175 | for i in range(len(bx)): 176 | 177 | # get boundary points and convert to base pixel space 178 | num_points = len(bx[i]) 179 | 180 | if num_points < 3: 181 | continue 182 | 183 | cur_points = np.zeros((num_points, 3)) 184 | cur_points[:, 0] = np.round(gx + bx[i] * wfrac, 2) 185 | cur_points[:, 1] = np.round(gy + by[i] * hfrac, 2) 186 | cur_points = cur_points.tolist() 187 | 188 | # create annotation json 189 | cur_annot = { 190 | "type": "polyline", 191 | "points": cur_points, 192 | "closed": True, 193 | "fillColor": "rgba(0,0,0,0)", 194 | "lineColor": "rgb(0,255,0)" 195 | } 196 | 197 | nuclei_annot_list.append(cur_annot) 198 | 199 | return nuclei_annot_list 200 | 201 | 202 | def create_tile_nuclei_annotations(im_nuclei_det_dict, tile_info, format): 203 | 204 | if format == 'bbox': 205 | 206 | return create_tile_nuclei_bbox_annotations(im_nuclei_det_dict, 207 | tile_info) 208 | 209 | elif format == 'boundary': 210 | 211 | return create_tile_nuclei_boundary_annotations(im_nuclei_det_dict, 212 | tile_info) 213 | else: 214 | 215 | raise ValueError('Invalid value passed for nuclei_annotation_format') 216 | 217 | 218 | def create_dask_client(args): 219 | """Create and install a Dask distributed client using args from a 220 | Namespace, supporting the following attributes: 221 | - .scheduler: Address of the distributed scheduler, or the 222 | empty string to start one locally 223 | """ 224 | import dask 225 | scheduler = args.scheduler 226 | 227 | if scheduler == 'multithreading': 228 | import dask.threaded 229 | from multiprocessing.pool import ThreadPool 230 | 231 | if args.num_threads_per_worker <= 0: 232 | num_workers = max( 233 | 1, psutil.cpu_count(logical=False) + args.num_threads_per_worker) 234 | else: 235 | num_workers = args.num_threads_per_worker 236 | 237 | # num_workers = 1 238 | 239 | print('Starting dask thread pool with %d thread(s)' % num_workers) 240 | dask.config.set(pool=ThreadPool(num_workers)) 241 | dask.config.set(scheduler='threads') 242 | return 243 | 244 | if scheduler == 'multiprocessing': 245 | import dask.multiprocessing 246 | import multiprocessing 247 | 248 | dask.config.set(scheduler='processes') 249 | if args.num_workers <= 0: 250 | num_workers = max( 251 | 1, psutil.cpu_count(logical=False) + args.num_workers) 252 | else: 253 | num_workers = args.num_workers 254 | 255 | print('Starting dask multiprocessing pool with %d worker(s)' % num_workers) 256 | dask.config.set(pool=multiprocessing.Pool( 257 | num_workers, initializer=dask.multiprocessing.initialize_worker_process)) 258 | return 259 | 260 | import dask.distributed 261 | if not scheduler: 262 | 263 | if args.num_workers <= 0: 264 | num_workers = max( 265 | 1, psutil.cpu_count(logical=False) + args.num_workers) 266 | else: 267 | num_workers = args.num_workers 268 | num_threads_per_worker = ( 269 | args.num_threads_per_worker if args.num_threads_per_worker >= 1 else None) 270 | 271 | print('Creating dask LocalCluster with %d worker(s), %d thread(s) per ' 272 | 'worker' % (num_workers, args.num_threads_per_worker)) 273 | scheduler = dask.distributed.LocalCluster( 274 | ip='0.0.0.0', # Allow reaching the diagnostics port externally 275 | scheduler_port=0, # Don't expose the scheduler port 276 | n_workers=num_workers, 277 | memory_limit=0, 278 | threads_per_worker=num_threads_per_worker, 279 | silence_logs=False 280 | ) 281 | 282 | return dask.distributed.Client(scheduler) 283 | 284 | 285 | def get_region_dict(region, maxRegionSize=None, tilesource=None): 286 | """Return a dict corresponding to region, checking the region size if 287 | maxRegionSize is provided. 288 | The intended use is to be passed via **kwargs, and so either {} is 289 | returned (for the special region -1,-1,-1,-1) or {'region': 290 | region_dict}. 291 | Params 292 | ------ 293 | region: list 294 | 4 elements -- left, top, width, height -- or all -1, meaning the whole 295 | slide. 296 | maxRegionSize: int, optional 297 | Maximum size permitted of any single dimension 298 | tilesource: tilesource, optional 299 | A `large_image` tilesource (or anything with `.sizeX` and `.sizeY` 300 | properties) that is used to determine the size of the whole slide if 301 | necessary. Must be provided if `maxRegionSize` is. 302 | Returns 303 | ------- 304 | region_dict: dict 305 | Either {} (for the special region -1,-1,-1,-1) or 306 | {'region': region_subdict} 307 | """ 308 | 309 | if len(region) != 4: 310 | raise ValueError('Exactly four values required for --region') 311 | 312 | useWholeImage = region == [-1] * 4 313 | 314 | if maxRegionSize is not None: 315 | if tilesource is None: 316 | raise ValueError('tilesource must be provided if maxRegionSize is') 317 | if maxRegionSize != -1: 318 | if useWholeImage: 319 | size = max(tilesource.sizeX, tilesource.sizeY) 320 | else: 321 | size = max(region[-2:]) 322 | if size > maxRegionSize: 323 | raise ValueError('Requested region is too large! ' 324 | 'Please see --maxRegionSize') 325 | 326 | return {} if useWholeImage else dict( 327 | region=dict(zip(['left', 'top', 'width', 'height'], 328 | region))) 329 | 330 | 331 | def disp_time_hms(seconds): 332 | """Converts time from seconds to a string of the form hours:minutes:seconds 333 | """ 334 | 335 | return str(timedelta(seconds=seconds)) 336 | 337 | 338 | def splitArgs(args, split='_'): 339 | """Split a Namespace into a Namespace of Namespaces based on shared 340 | prefixes. The string separating the prefix from the rest of the 341 | argument is determined by the optional "split" parameter. 342 | Parameters not containing the splitting string are kept as-is. 343 | """ 344 | def splitKey(k): 345 | s = k.split(split, 1) 346 | return (None, s[0]) if len(s) == 1 else s 347 | 348 | args = args._asdict() if hasattr(args, '_asdict') else vars(args) 349 | firstKeys = {splitKey(k)[0] for k in args} 350 | result = Namespace() 351 | for k in firstKeys - {None}: 352 | setattr(result, k, Namespace()) 353 | for k, v in args.items(): 354 | f, s = splitKey(k) 355 | if f is None: 356 | setattr(result, s, v) 357 | else: 358 | setattr(getattr(result, f), s, v) 359 | return result 360 | 361 | 362 | def sample_pixels(args): 363 | """Version of histomicstk.utils.sample_pixels that takes a Namespace 364 | and handles the special default values. 365 | """ 366 | args = (args._asdict() if hasattr(args, '_asdict') else vars(args)).copy() 367 | for k in 'magnification', 'sample_fraction', 'sample_approximate_total': 368 | if args[k] == -1: 369 | del args[k] 370 | return htk_utils.sample_pixels(**args) 371 | 372 | 373 | __all__ = ( 374 | 'create_dask_client', 375 | 'create_tile_nuclei_annotations', 376 | 'create_tile_nuclei_bbox_annotations', 377 | 'create_tile_nuclei_boundary_annotations', 378 | 'disp_time_hms', 379 | 'get_region_dict', 380 | 'get_stain_matrix', 381 | 'get_stain_vector', 382 | 'sample_pixels', 383 | 'segment_wsi_foreground_at_low_res', 384 | 'splitArgs', 385 | ) 386 | -------------------------------------------------------------------------------- /utils/nodenames.txt: -------------------------------------------------------------------------------- 1 | hpg6-112.maas 2 | hpg6-111.maas 3 | hpg6-113.maas 4 | hpg6-114.maas 5 | hpg6-115.maas 6 | hpg6-116.maas 7 | hpg6-117.maas 8 | hpg6-118.maas 9 | -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/IoU_based_decision/91315_leica_at2_40x.svs.98728.44031.1649.892.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/utils/overlapping_tiles_detection/IoU_based_decision/91315_leica_at2_40x.svs.98728.44031.1649.892.jpg -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/IoU_based_decision/detection_objects.json: -------------------------------------------------------------------------------- 1 | {"objects": [{"prob": 0.9995, "bbox": [358, 483, 418, 536], "label": 0}, {"prob": 0.9993, "bbox": [1053, 122, 1114, 181], "label": 0}, {"prob": 0.9993, "bbox": [361, 266, 436, 345], "label": 0}, {"prob": 0.9991, "bbox": [243, 611, 305, 667], "label": 0}, {"prob": 0.9988, "bbox": [865, 310, 918, 353], "label": 0}, {"prob": 0.9987, "bbox": [329, 621, 387, 682], "label": 0}, {"prob": 0.9987, "bbox": [996, 126, 1062, 188], "label": 0}, {"prob": 0.9985, "bbox": [1525, 100, 1582, 156], "label": 0}, {"prob": 0.9984, "bbox": [1078, 323, 1131, 377], "label": 0}, {"prob": 0.9984, "bbox": [480, 556, 547, 616], "label": 0}, {"prob": 0.9983, "bbox": [331, 426, 395, 497], "label": 0}, {"prob": 0.9982, "bbox": [372, 543, 425, 605], "label": 0}, {"prob": 0.9982, "bbox": [84, 676, 152, 738], "label": 0}, {"prob": 0.9978, "bbox": [172, 566, 250, 637], "label": 0}, {"prob": 0.9977, "bbox": [866, 267, 934, 321], "label": 0}, {"prob": 0.9977, "bbox": [432, 807, 491, 859], "label": 0}, {"prob": 0.9976, "bbox": [1519, 36, 1575, 100], "label": 0}, {"prob": 0.9976, "bbox": [95, 475, 145, 511], "label": 0}, {"prob": 0.9976, "bbox": [215, 379, 272, 437], "label": 0}, {"prob": 0.9976, "bbox": [987, 451, 1048, 503], "label": 0}, {"prob": 0.9975, "bbox": [790, 793, 848, 842], "label": 0}, {"prob": 0.9973, "bbox": [112, 789, 166, 832], "label": 0}, {"prob": 0.9972, "bbox": [501, 825, 578, 885], "label": 0}, {"prob": 0.9972, "bbox": [849, 212, 909, 275], "label": 0}, {"prob": 0.9972, "bbox": [712, 751, 766, 803], "label": 0}, {"prob": 0.997, "bbox": [581, 304, 651, 358], "label": 0}, {"prob": 0.9968, "bbox": [719, 650, 766, 702], "label": 0}, {"prob": 0.9968, "bbox": [1451, 711, 1505, 761], "label": 0}, {"prob": 0.9967, "bbox": [1578, 522, 1622, 568], "label": 0}, {"prob": 0.9967, "bbox": [906, 233, 960, 279], "label": 0}, {"prob": 0.9963, "bbox": [1006, 384, 1070, 438], "label": 0}, {"prob": 0.9962, "bbox": [161, 771, 226, 822], "label": 0}, {"prob": 0.9961, "bbox": [307, 554, 373, 625], "label": 0}, {"prob": 0.9961, "bbox": [639, 754, 710, 813], "label": 0}, {"prob": 0.996, "bbox": [920, 581, 966, 624], "label": 0}, {"prob": 0.996, "bbox": [1338, 790, 1406, 860], "label": 0}, {"prob": 0.9959, "bbox": [409, 646, 475, 702], "label": 0}, {"prob": 0.9959, "bbox": [424, 576, 479, 629], "label": 0}, {"prob": 0.9956, "bbox": [14, 594, 83, 644], "label": 0}, {"prob": 0.9956, "bbox": [445, 117, 493, 175], "label": 0}, {"prob": 0.9955, "bbox": [1233, 277, 1285, 350], "label": 0}, {"prob": 0.9954, "bbox": [1379, 168, 1440, 247], "label": 0}, {"prob": 0.9954, "bbox": [968, 162, 1031, 210], "label": 0}, {"prob": 0.9954, "bbox": [687, 661, 730, 693], "label": 0}, {"prob": 0.9953, "bbox": [150, 645, 224, 701], "label": 0}, {"prob": 0.9953, "bbox": [802, 599, 869, 646], "label": 0}, {"prob": 0.9951, "bbox": [659, 249, 849, 426], "label": 0}, {"prob": 0.9951, "bbox": [733, 445, 789, 491], "label": 0}, {"prob": 0.995, "bbox": [788, 655, 862, 703], "label": 0}, {"prob": 0.9947, "bbox": [1223, 376, 1278, 419], "label": 0}, {"prob": 0.9946, "bbox": [597, 414, 658, 464], "label": 0}, {"prob": 0.9946, "bbox": [726, 810, 802, 880], "label": 0}, {"prob": 0.9945, "bbox": [514, 512, 583, 563], "label": 0}, {"prob": 0.9945, "bbox": [984, 620, 1052, 673], "label": 0}, {"prob": 0.9944, "bbox": [1263, 332, 1310, 377], "label": 0}, {"prob": 0.9944, "bbox": [28, 524, 96, 581], "label": 0}, {"prob": 0.9944, "bbox": [1093, 378, 1171, 440], "label": 0}, {"prob": 0.9942, "bbox": [548, 363, 620, 427], "label": 0}, {"prob": 0.9942, "bbox": [1438, 172, 1497, 235], "label": 0}, {"prob": 0.9942, "bbox": [853, 631, 915, 671], "label": 0}, {"prob": 0.9939, "bbox": [523, 751, 589, 798], "label": 0}, {"prob": 0.9939, "bbox": [879, 384, 940, 427], "label": 0}, {"prob": 0.9939, "bbox": [1041, 779, 1120, 834], "label": 0}, {"prob": 0.9937, "bbox": [275, 442, 334, 489], "label": 0}, {"prob": 0.9935, "bbox": [830, 476, 899, 530], "label": 0}, {"prob": 0.9934, "bbox": [371, 406, 422, 478], "label": 0}, {"prob": 0.9932, "bbox": [576, 688, 629, 745], "label": 0}, {"prob": 0.9932, "bbox": [449, 501, 500, 538], "label": 0}, {"prob": 0.993, "bbox": [613, 530, 676, 579], "label": 0}, {"prob": 0.993, "bbox": [596, 193, 650, 249], "label": 0}, {"prob": 0.993, "bbox": [1204, 58, 1257, 106], "label": 0}, {"prob": 0.9926, "bbox": [863, 696, 929, 761], "label": 0}, {"prob": 0.9926, "bbox": [936, 395, 1006, 456], "label": 0}, {"prob": 0.9925, "bbox": [1144, 318, 1201, 377], "label": 0}, {"prob": 0.9924, "bbox": [60, 626, 110, 673], "label": 0}, {"prob": 0.9922, "bbox": [756, 626, 813, 674], "label": 0}, {"prob": 0.9921, "bbox": [1154, 411, 1219, 477], "label": 0}, {"prob": 0.992, "bbox": [419, 402, 472, 478], "label": 0}, {"prob": 0.9919, "bbox": [755, 701, 817, 748], "label": 0}, {"prob": 0.9918, "bbox": [718, 640, 750, 672], "label": 0}, {"prob": 0.9917, "bbox": [1403, 783, 1461, 836], "label": 0}, {"prob": 0.9917, "bbox": [383, 215, 437, 263], "label": 0}, {"prob": 0.9915, "bbox": [319, 298, 376, 356], "label": 0}, {"prob": 0.991, "bbox": [1013, 209, 1077, 263], "label": 0}, {"prob": 0.9909, "bbox": [463, 732, 531, 784], "label": 0}, {"prob": 0.9906, "bbox": [1416, 115, 1471, 165], "label": 0}, {"prob": 0.9906, "bbox": [406, 333, 460, 376], "label": 0}, {"prob": 0.99, "bbox": [952, 214, 1011, 259], "label": 0}, {"prob": 0.9899, "bbox": [1577, 258, 1625, 309], "label": 0}, {"prob": 0.9898, "bbox": [1288, 285, 1355, 336], "label": 0}, {"prob": 0.9897, "bbox": [643, 704, 697, 746], "label": 0}, {"prob": 0.9892, "bbox": [92, 519, 155, 565], "label": 0}, {"prob": 0.9887, "bbox": [440, 610, 498, 661], "label": 0}, {"prob": 0.9885, "bbox": [796, 498, 840, 545], "label": 0}, {"prob": 0.9883, "bbox": [148, 389, 209, 443], "label": 0}, {"prob": 0.9878, "bbox": [1463, 102, 1516, 149], "label": 0}, {"prob": 0.9876, "bbox": [215, 528, 250, 571], "label": 0}, {"prob": 0.9869, "bbox": [1534, 212, 1606, 258], "label": 0}, {"prob": 0.9865, "bbox": [1466, 28, 1522, 84], "label": 0}, {"prob": 0.9863, "bbox": [929, 119, 980, 169], "label": 0}, {"prob": 0.9863, "bbox": [684, 600, 734, 643], "label": 0}, {"prob": 0.9861, "bbox": [44, 702, 94, 760], "label": 0}, {"prob": 0.986, "bbox": [142, 349, 190, 394], "label": 0}, {"prob": 0.9859, "bbox": [604, 594, 659, 650], "label": 0}, {"prob": 0.9855, "bbox": [238, 561, 301, 605], "label": 0}, {"prob": 0.985, "bbox": [928, 676, 989, 757], "label": 0}, {"prob": 0.985, "bbox": [633, 642, 684, 695], "label": 0}, {"prob": 0.9846, "bbox": [607, 107, 650, 145], "label": 0}, {"prob": 0.9845, "bbox": [571, 80, 612, 118], "label": 0}, {"prob": 0.9842, "bbox": [915, 633, 964, 670], "label": 0}, {"prob": 0.9839, "bbox": [801, 552, 862, 596], "label": 0}, {"prob": 0.9837, "bbox": [744, 585, 796, 623], "label": 0}, {"prob": 0.9836, "bbox": [237, 705, 420, 885], "label": 0}, {"prob": 0.9826, "bbox": [1219, 123, 1279, 181], "label": 0}, {"prob": 0.9817, "bbox": [982, 770, 1048, 809], "label": 0}, {"prob": 0.9814, "bbox": [1284, 154, 1352, 207], "label": 0}, {"prob": 0.9814, "bbox": [1026, 331, 1077, 380], "label": 0}, {"prob": 0.9803, "bbox": [1083, 84, 1137, 141], "label": 0}, {"prob": 0.9797, "bbox": [870, 547, 918, 596], "label": 0}, {"prob": 0.9794, "bbox": [906, 193, 968, 233], "label": 0}, {"prob": 0.9788, "bbox": [246, 492, 303, 550], "label": 0}, {"prob": 0.9787, "bbox": [1333, 197, 1380, 237], "label": 0}, {"prob": 0.9786, "bbox": [1038, 697, 1089, 745], "label": 0}, {"prob": 0.9773, "bbox": [616, 250, 680, 300], "label": 0}, {"prob": 0.9769, "bbox": [1381, 693, 1429, 735], "label": 0}, {"prob": 0.9739, "bbox": [951, 456, 994, 502], "label": 0}, {"prob": 0.9738, "bbox": [451, 765, 532, 805], "label": 0}, {"prob": 0.9735, "bbox": [1577, 143, 1624, 180], "label": 0}, {"prob": 0.9735, "bbox": [541, 640, 626, 683], "label": 0}, {"prob": 0.9725, "bbox": [614, 810, 685, 877], "label": 0}, {"prob": 0.9722, "bbox": [384, 604, 437, 644], "label": 0}, {"prob": 0.9719, "bbox": [1076, 525, 1148, 575], "label": 0}, {"prob": 0.9719, "bbox": [673, 187, 723, 248], "label": 0}, {"prob": 0.9711, "bbox": [1222, 418, 1287, 457], "label": 0}, {"prob": 0.97, "bbox": [191, 686, 231, 720], "label": 0}, {"prob": 0.9699, "bbox": [472, 438, 523, 487], "label": 0}, {"prob": 0.9695, "bbox": [306, 499, 369, 550], "label": 0}, {"prob": 0.9695, "bbox": [1126, 495, 1174, 541], "label": 0}, {"prob": 0.9694, "bbox": [69, 744, 135, 795], "label": 0}, {"prob": 0.9693, "bbox": [1048, 640, 1117, 683], "label": 0}, {"prob": 0.9687, "bbox": [1506, 166, 1569, 234], "label": 0}, {"prob": 0.9687, "bbox": [712, 681, 762, 723], "label": 0}, {"prob": 0.9682, "bbox": [505, 670, 549, 714], "label": 0}, {"prob": 0.9663, "bbox": [502, 413, 548, 461], "label": 0}, {"prob": 0.9652, "bbox": [729, 498, 781, 544], "label": 0}, {"prob": 0.9642, "bbox": [1226, 452, 1276, 491], "label": 0}, {"prob": 0.9615, "bbox": [129, 709, 183, 755], "label": 0}, {"prob": 0.9613, "bbox": [404, 715, 463, 770], "label": 0}, {"prob": 0.9596, "bbox": [1245, 243, 1305, 286], "label": 0}, {"prob": 0.9591, "bbox": [145, 712, 222, 774], "label": 0}, {"prob": 0.9582, "bbox": [902, 159, 960, 194], "label": 0}, {"prob": 0.9561, "bbox": [943, 783, 994, 823], "label": 0}, {"prob": 0.9547, "bbox": [405, 437, 450, 491], "label": 0}, {"prob": 0.9527, "bbox": [1311, 322, 1359, 365], "label": 0}, {"prob": 0.9511, "bbox": [685, 450, 724, 507], "label": 0}, {"prob": 0.9505, "bbox": [1052, 831, 1123, 884], "label": 0}, {"prob": 0.9504, "bbox": [1152, 91, 1217, 142], "label": 0}, {"prob": 0.9492, "bbox": [1032, 550, 1080, 606], "label": 0}, {"prob": 0.9491, "bbox": [895, 656, 954, 704], "label": 0}, {"prob": 0.9487, "bbox": [1028, 507, 1079, 559], "label": 0}, {"prob": 0.9479, "bbox": [1469, 209, 1503, 252], "label": 0}, {"prob": 0.9465, "bbox": [1426, 711, 1468, 748], "label": 0}, {"prob": 0.9457, "bbox": [977, 559, 1020, 607], "label": 0}, {"prob": 0.9456, "bbox": [1075, 565, 1160, 627], "label": 0}, {"prob": 0.9449, "bbox": [521, 798, 571, 829], "label": 0}, {"prob": 0.9435, "bbox": [1140, 621, 1197, 667], "label": 0}, {"prob": 0.9427, "bbox": [996, 695, 1054, 746], "label": 0}, {"prob": 0.9405, "bbox": [1245, 77, 1319, 142], "label": 0}, {"prob": 0.9347, "bbox": [704, 539, 755, 589], "label": 0}, {"prob": 0.9319, "bbox": [277, 521, 328, 576], "label": 0}, {"prob": 0.9247, "bbox": [1051, 60, 1093, 96], "label": 0}, {"prob": 0.9245, "bbox": [106, 608, 156, 667], "label": 0}, {"prob": 0.9226, "bbox": [338, 583, 393, 634], "label": 0}, {"prob": 0.9197, "bbox": [813, 703, 864, 759], "label": 0}, {"prob": 0.9192, "bbox": [544, 168, 598, 222], "label": 0}, {"prob": 0.9148, "bbox": [711, 186, 765, 232], "label": 0}, {"prob": 0.9124, "bbox": [665, 494, 707, 536], "label": 0}, {"prob": 0.9089, "bbox": [520, 708, 569, 743], "label": 0}, {"prob": 0.908, "bbox": [578, 776, 627, 815], "label": 0}, {"prob": 0.905, "bbox": [894, 771, 958, 809], "label": 0}, {"prob": 0.9014, "bbox": [478, 779, 545, 824], "label": 0}, {"prob": 0.8994, "bbox": [1338, 647, 1420, 700], "label": 0}, {"prob": 0.8929, "bbox": [229, 699, 290, 747], "label": 0}, {"prob": 0.8922, "bbox": [1173, 476, 1236, 521], "label": 0}, {"prob": 0.8859, "bbox": [205, 719, 261, 763], "label": 0}, {"prob": 0.8846, "bbox": [1169, 525, 1223, 559], "label": 0}, {"prob": 0.882, "bbox": [1031, 180, 1095, 224], "label": 0}, {"prob": 0.8768, "bbox": [1577, 578, 1624, 621], "label": 0}, {"prob": 0.8715, "bbox": [1334, 219, 1381, 268], "label": 0}, {"prob": 0.8615, "bbox": [473, 403, 508, 448], "label": 0}, {"prob": 0.8605, "bbox": [1284, 453, 1327, 500], "label": 0}, {"prob": 0.8596, "bbox": [451, 351, 481, 391], "label": 0}, {"prob": 0.8542, "bbox": [132, 750, 207, 804], "label": 0}, {"prob": 0.8506, "bbox": [1452, 641, 1492, 689], "label": 0}, {"prob": 0.8462, "bbox": [468, 629, 517, 672], "label": 0}, {"prob": 0.8431, "bbox": [462, 678, 508, 719], "label": 0}, {"prob": 0.8401, "bbox": [1558, 383, 1601, 433], "label": 0}, {"prob": 0.8388, "bbox": [576, 460, 621, 506], "label": 0}, {"prob": 0.8278, "bbox": [760, 534, 819, 574], "label": 0}, {"prob": 0.8201, "bbox": [439, 721, 509, 761], "label": 0}, {"prob": 0.8194, "bbox": [1, 571, 31, 618], "label": 0}, {"prob": 0.8169, "bbox": [858, 746, 906, 781], "label": 0}, {"prob": 0.8159, "bbox": [29, 4, 90, 56], "label": 0}, {"prob": 0.8142, "bbox": [549, 436, 604, 487], "label": 0}, {"prob": 0.7928, "bbox": [1108, 628, 1151, 673], "label": 0}, {"prob": 0.7856, "bbox": [705, 145, 745, 186], "label": 0}, {"prob": 0.7719, "bbox": [1301, 118, 1361, 149], "label": 0}, {"prob": 0.77, "bbox": [913, 134, 950, 173], "label": 0}, {"prob": 0.7423, "bbox": [1067, 482, 1127, 524], "label": 0}, {"prob": 0.7346, "bbox": [1528, 744, 1591, 793], "label": 0}, {"prob": 0.7272, "bbox": [841, 676, 912, 727], "label": 0}, {"prob": 0.7147, "bbox": [1588, 695, 1647, 761], "label": 0}, {"prob": 0.6874, "bbox": [1625, 496, 1647, 538], "label": 0}, {"prob": 0.6619, "bbox": [140, 530, 177, 568], "label": 0}, {"prob": 0.6467, "bbox": [1243, 397, 1295, 437], "label": 0}, {"prob": 0.6435, "bbox": [1106, 448, 1150, 492], "label": 0}, {"prob": 0.6304, "bbox": [603, 798, 666, 844], "label": 0}, {"prob": 0.59, "bbox": [536, 602, 587, 638], "label": 0}, {"prob": 0.5502, "bbox": [811, 735, 873, 779], "label": 0}, {"prob": 0.5208, "bbox": [629, 362, 657, 404], "label": 0}, {"prob": 0.5192, "bbox": [1440, 324, 1479, 377], "label": 0}, {"prob": 0.5086, "bbox": [705, 168, 743, 216], "label": 0}, {"prob": 0.4993, "bbox": [1597, 457, 1640, 500], "label": 0}, {"prob": 0.4856, "bbox": [987, 674, 1026, 710], "label": 0}, {"prob": 0.4812, "bbox": [234, 525, 269, 570], "label": 0}, {"prob": 0.4675, "bbox": [10, 727, 65, 772], "label": 0}, {"prob": 0.4672, "bbox": [430, 771, 487, 809], "label": 0}, {"prob": 0.4262, "bbox": [496, 725, 551, 762], "label": 0}, {"prob": 0.4218, "bbox": [500, 616, 548, 649], "label": 0}, {"prob": 0.4183, "bbox": [1195, 186, 1240, 236], "label": 0}, {"prob": 0.4044, "bbox": [184, 663, 250, 707], "label": 0}, {"prob": 0.3819, "bbox": [224, 677, 264, 709], "label": 0}, {"prob": 0.3754, "bbox": [818, 840, 848, 882], "label": 0}, {"prob": 0.3683, "bbox": [145, 690, 198, 737], "label": 0}, {"prob": 0.3365, "bbox": [430, 267, 476, 307], "label": 0}, {"prob": 0.3196, "bbox": [1309, 498, 1354, 545], "label": 0}, {"prob": 0.2952, "bbox": [557, 519, 597, 555], "label": 0}, {"prob": 0.258, "bbox": [1329, 6, 1384, 71], "label": 0}, {"prob": 0.2163, "bbox": [1590, 188, 1631, 232], "label": 0}, {"prob": 0.1712, "bbox": [738, 151, 777, 193], "label": 0}, {"prob": 0.1319, "bbox": [1303, 62, 1349, 95], "label": 0}, {"prob": 0.118, "bbox": [909, 741, 961, 788], "label": 0}, {"prob": 0.1031, "bbox": [679, 803, 727, 854], "label": 0}], "file": "something"} 2 | -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/IoU_based_decision/final_ROI_with_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/utils/overlapping_tiles_detection/IoU_based_decision/final_ROI_with_output.png -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/IoU_based_decision/run_bbox_mapping.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @__ramraj__ 3 | 4 | 5 | import numpy as np 6 | import pandas as pd 7 | import json 8 | import cv2 9 | from PIL import Image, ImageDraw 10 | from scipy.optimize import linear_sum_assignment 11 | from scipy.spatial import distance 12 | 13 | 14 | OFFSET = 448 15 | TILE_SIZE = 512 16 | THRESHOLD = 0.5 17 | PAD_SIZE = 64 18 | INPUT_JSON = 'detection_objects.json' 19 | 20 | 21 | def load_json(name='detection.json'): 22 | preds = json.loads(open(name).read()).get('objects') 23 | 24 | # Create the 2-dim list 25 | pred_list = [] 26 | for obj in preds: 27 | value = [ 28 | obj['label'], # object class - objectness 29 | obj['bbox'][0], # x1 coordinate 30 | obj['bbox'][1], # y1 coordinate 31 | obj['bbox'][2], # x2 coordinate 32 | obj['bbox'][3], # y2 coordinate 33 | obj['prob'] # Confidence scor of this detected objectness 34 | ] 35 | pred_list.append(value) 36 | 37 | # ++++++++++++++++++++++++++ fILTERING ++++++++++++++++++++++++++ 38 | # Renove the cells, which are close to borders within the PAD_SIZE pixels 39 | pred_filtered_list = [] 40 | for obj in preds: 41 | 42 | x1 = obj['bbox'][0] 43 | y1 = obj['bbox'][1] 44 | x2 = obj['bbox'][2] 45 | y2 = obj['bbox'][3] 46 | 47 | # Get center bbox coordintes 48 | x_cent = int((x1 + x2) / 2) 49 | y_cent = int((y1 + y2) / 2) 50 | 51 | # Check if bbox center is inside the valid error measurable region of ROI. 52 | # if (x_cent >= PAD_SIZE) and (x_cent <= W - PAD_SIZE)\ 53 | # and (y_cent >= PAD_SIZE) and (y_cent <= H - PAD_SIZE): 54 | if (x_cent >= OFFSET) and (x_cent <= TILE_SIZE): 55 | 56 | value = [obj['label'], # object class - objectness 57 | x1, # x1 coordinate 58 | y1, # y1 coordinate 59 | x2, # x2 coordinate 60 | y2, # y2 coordinate 61 | obj['prob']] # Confidence scor of this detected objectness 62 | pred_filtered_list.append(value) 63 | 64 | print('Total Number of Filtered Prediction Counts per ROI : ', 65 | len(pred_filtered_list)) 66 | return pred_filtered_list 67 | 68 | 69 | def compute_box_centers(df): 70 | df['width'] = df['xmax'] - df['xmin'] 71 | df['height'] = df['ymax'] - df['ymin'] 72 | df['xcenter'] = (df['xmax'] + df['xmin']) / 2 73 | df['ycenter'] = (df['ymax'] + df['ymin']) / 2 74 | return df 75 | 76 | 77 | def np_vec_no_jit_iou(bboxes1, bboxes2): 78 | """ 79 | Fast, vectorized IoU. 80 | Source: https://medium.com/@venuktan/vectorized-intersection-over-union ... 81 | -iou-in-numpy-and-tensor-flow-4fa16231b63d 82 | """ 83 | x11, y11, x12, y12 = np.split(bboxes1, 4, axis=1) 84 | x21, y21, x22, y22 = np.split(bboxes2, 4, axis=1) 85 | xA = np.maximum(x11, np.transpose(x21)) 86 | yA = np.maximum(y11, np.transpose(y21)) 87 | xB = np.minimum(x12, np.transpose(x22)) 88 | yB = np.minimum(y12, np.transpose(y22)) 89 | interArea = np.maximum((xB - xA + 1), 0) * np.maximum((yB - yA + 1), 0) 90 | boxAArea = (x12 - x11 + 1) * (y12 - y11 + 1) 91 | boxBArea = (x22 - x21 + 1) * (y22 - y21 + 1) 92 | iou = interArea / (boxAArea + np.transpose(boxBArea) - interArea) 93 | return iou 94 | 95 | 96 | def main(verbose=False): 97 | 98 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 99 | 100 | # Loading the annotations inside the overlapping region 101 | tile1_list = load_json('detection_objects.json') 102 | tile1_list = pd.DataFrame(tile1_list, 103 | columns=['label', 'xmin', 'ymin', 104 | 'xmax', 'ymax', 'confidence']) 105 | tile2_list = tile1_list.copy() 106 | if verbose: 107 | print 'Tile 1 List: ' 108 | print tile1_list 109 | 110 | # Addibg Guassian noise to the copied second tile 111 | noise = np.asarray(np.random.normal(loc=0, scale=5, size=(len(tile1_list), 4)), 112 | np.int32) 113 | tile2_list.iloc[:, 1:5] = tile2_list.iloc[:, 1:5] + noise 114 | if verbose: 115 | print 'Tile 2 List:' 116 | print tile2_list 117 | 118 | # Cast them into Pandas frame 119 | tile1 = pd.DataFrame(tile1_list, 120 | columns=['label', 'xmin', 'ymin', 121 | 'xmax', 'ymax', 'confidence']) 122 | tile2 = pd.DataFrame(tile2_list, 123 | columns=['label', 'xmin', 'ymin', 124 | 'xmax', 'ymax', 'confidence']) 125 | 126 | # Throwing away some elements from tile 2 to generate non-sqare cost matrix 127 | 128 | tile2 = tile2.iloc[:-2, :] 129 | print 'New Tile 2 List' 130 | print tile2 131 | 132 | # get IoU for predictions and labels 133 | bboxes1 = np.asarray(np.concatenate(( 134 | np.array(tile1["xmin"])[:, None], np.array(tile1["ymin"])[:, None], 135 | np.array(tile1["xmax"])[:, None], np.array(tile1["ymax"])[:, None]), 136 | axis=1), np.float32) 137 | bboxes2 = np.asarray(np.concatenate(( 138 | np.array(tile2["xmin"])[:, None], np.array(tile2["ymin"])[:, None], 139 | np.array(tile2["xmax"])[:, None], np.array(tile2["ymax"])[:, None]), 140 | axis=1), np.float32) 141 | iou = np_vec_no_jit_iou(bboxes1, bboxes2) 142 | iou = pd.DataFrame(iou, index=list(tile1.index), columns=list(tile2.index)) 143 | if verbose: 144 | print 'IoU Matrix : ' 145 | print iou 146 | 147 | # only keep predictions which intersect with some ground truth and vice 148 | # versa 149 | iou_NoUnmatched = iou.loc[iou.sum(axis=1) > 0, iou.sum(axis=0) > 0] 150 | print 'Thresold=0 Filtered IoU Matrix' 151 | print iou_NoUnmatched 152 | 153 | # # ++++++++++++++++++++++++++++ Additional Filtering Using 0.5 ++++++++++ 154 | 155 | # # Apply 0.5 Threshold for IoU values 156 | # filtered_index = np.where(np.any(iou_NoUnmatched > THRESHOLD, axis=1)) 157 | # if verbose: 158 | # print 'Remaining Objs Index After Filtering with Actual Threshold' 159 | # print filtered_index 160 | # iou_NoUnmatched = iou_NoUnmatched.loc[filtered_index] 161 | # print 'Remaining Objs After Filtering with Actual Threshold (0.5)' 162 | # print iou_NoUnmatched 163 | 164 | # ++++++++++++++++++++++++++++ Applying Hangarian Algorithm ++++++++++++++ 165 | 166 | # Use linear sum assignment (Hungarian algorithm) to match tile1 167 | # and tile2 to each other in a 1:1 mapping 168 | # IMPORTANT NOTE: the resultant indices are relative to the 169 | # iou_NoUnmatched dataframe 170 | 171 | # which are NOT NECESSARILY THE SAME as the indices relative to the iou 172 | # matrix 173 | row_ind, col_ind = linear_sum_assignment(1 - iou_NoUnmatched.values) 174 | print 'Mapped Row & Column from tile 1 & tile 2' 175 | print row_ind 176 | print col_ind 177 | 178 | # # a = iou_NoUnmatched.iloc[row_ind, col_ind] > 0.5 179 | # # print a 180 | # filtered_row_ind = [] 181 | # filtered_col_ind = [] 182 | # for i in range(len(row_ind)): 183 | # if iou_NoUnmatched.loc[row_ind[i], col_ind[i]] > 0.5: 184 | # filtered_row_ind.append(row_ind[i]) 185 | # filtered_col_ind.append(col_ind[i]) 186 | # row_ind = filtered_row_ind 187 | # col_ind = filtered_col_ind 188 | 189 | # print row_ind 190 | # print col_ind 191 | 192 | # ++++++++++++++ Differentiate Boxes = (unique boxes, mapped boxes) ++++++ 193 | 194 | unique_tile1_objs = tile1[~tile1.index.isin(row_ind)].copy() 195 | # if verbose: 196 | # print 'Tile 1 Unmapped objects : ' 197 | # print unique_tile1_objs 198 | unique_tile2_objs = tile2[~tile2.index.isin(col_ind)].copy() 199 | # if verbose: 200 | # print 'Tile 2 Unmapped objects : ' 201 | # print unique_tile2_objs 202 | unique_objs = pd.concat([unique_tile1_objs, unique_tile2_objs], axis=0) 203 | if verbose: 204 | print 'Total Unmapped objects : ' 205 | print unique_objs 206 | print 'Unmapped Objects : ', len(unique_objs) 207 | 208 | tile1_mapped_objs = tile1.loc[row_ind] 209 | tile2_mapped_objs = tile2.loc[col_ind] 210 | print 'Mapped Objects : ', len(tile1_mapped_objs) 211 | 212 | # ++++++++++++++++++++++ Finding width & heigh of each mapped bbox +++++++ 213 | 214 | tile1_mapped_objs = compute_box_centers(tile1_mapped_objs) 215 | tile2_mapped_objs = compute_box_centers(tile2_mapped_objs) 216 | # if verbose: 217 | # print tile1_mapped_objs 218 | # print tile2_mapped_objs 219 | tile_mappend_objs_w = np.maximum(tile1_mapped_objs['width'], 220 | tile2_mapped_objs['width']) 221 | tile_mappend_objs_h = np.maximum(tile1_mapped_objs['height'], 222 | tile2_mapped_objs['height']) 223 | # ++++++++++++++++++++++ Finding x_c & y_c of each mapped bbox +++++++++++ 224 | tile_mappend_objs_xc = (tile1_mapped_objs['xcenter'] + 225 | tile2_mapped_objs['xcenter']) / 2 226 | tile_mappend_objs_yc = (tile1_mapped_objs['ycenter'] + 227 | tile2_mapped_objs['ycenter']) / 2 228 | 229 | ROI = cv2.imread('91315_leica_at2_40x.svs.98728.44031.1649.892.jpg') 230 | # +++++++++++++++++++ Draw Unique Bbox +++++++++++++++++++++++++++++++++ 231 | for i in unique_objs.index: 232 | x1 = unique_objs.loc[i, 'xmin'] 233 | y1 = unique_objs.loc[i, 'ymin'] 234 | x2 = unique_objs.loc[i, 'xmax'] 235 | y2 = unique_objs.loc[i, 'ymax'] 236 | cv2.rectangle(ROI, (x1, y1), (x2, y2), (255, 0, 0), 3) 237 | 238 | # +++++++++++++++++++ Draw Mapped Bbox +++++++++++++++++++++++++++++++++ 239 | for i in range(tile_mappend_objs_xc.shape[0]): 240 | x1 = tile_mappend_objs_xc[i] - (tile_mappend_objs_w[i] / 2) 241 | y1 = tile_mappend_objs_yc[i] - (tile_mappend_objs_h[i] / 2) 242 | x2 = tile_mappend_objs_xc[i] + (tile_mappend_objs_w[i] / 2) 243 | y2 = tile_mappend_objs_yc[i] + (tile_mappend_objs_h[i] / 2) 244 | cv2.rectangle(ROI, (int(x1), int(y1)), 245 | (int(x2), int(y2)), (0, 255, 0), 1) 246 | 247 | cv2.rectangle(ROI, (0, 0), (512, 512), (255, 0, 0), 2) 248 | cv2.rectangle(ROI, (448, 0), (960, 512), (0, 0, 255), 2) 249 | 250 | cv2.imwrite('final_ROI_with_output.png', ROI) 251 | 252 | 253 | if __name__ == '__main__': 254 | main() 255 | -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/point_based_decision/another.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalSlideArchive/CNNCellDetection/3b78a87074ee8c11aa63fd1f0b60ae1d4a31e4e8/utils/overlapping_tiles_detection/point_based_decision/another.png -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/point_based_decision/detection.json: -------------------------------------------------------------------------------- 1 | [{'prob': 0.9995, 'bbox': [358, 483, 418, 536], 'label': 0}, {'prob': 0.9993, 'bbox': [1053, 122, 1114, 181], 'label': 0}, {'prob': 0.9993, 'bbox': [361, 266, 436, 345], 'label': 0}, {'prob': 0.9991, 'bbox': [243, 611, 305, 667], 'label': 0}, {'prob': 0.9988, 'bbox': [865, 310, 918, 353], 'label': 0}, {'prob': 0.9987, 'bbox': [329, 621, 387, 682], 'label': 0}, {'prob': 0.9987, 'bbox': [996, 126, 1062, 188], 'label': 0}, {'prob': 0.9985, 'bbox': [1525, 100, 1582, 156], 'label': 0}, {'prob': 0.9984, 'bbox': [1078, 323, 1131, 377], 'label': 0}, {'prob': 0.9984, 'bbox': [480, 556, 547, 616], 'label': 0}, {'prob': 0.9983, 'bbox': [331, 426, 395, 497], 'label': 0}, {'prob': 0.9982, 'bbox': [372, 543, 425, 605], 'label': 0}, {'prob': 0.9982, 'bbox': [84, 676, 152, 738], 'label': 0}, {'prob': 0.9978, 'bbox': [172, 566, 250, 637], 'label': 0}, {'prob': 0.9977, 'bbox': [866, 267, 934, 321], 'label': 0}, {'prob': 0.9977, 'bbox': [432, 807, 491, 859], 'label': 0}, {'prob': 0.9976, 'bbox': [1519, 36, 1575, 100], 'label': 0}, {'prob': 0.9976, 'bbox': [95, 475, 145, 511], 'label': 0}, {'prob': 0.9976, 'bbox': [215, 379, 272, 437], 'label': 0}, {'prob': 0.9976, 'bbox': [987, 451, 1048, 503], 'label': 0}, {'prob': 0.9975, 'bbox': [790, 793, 848, 842], 'label': 0}, {'prob': 0.9973, 'bbox': [112, 789, 166, 832], 'label': 0}, {'prob': 0.9972, 'bbox': [501, 825, 578, 885], 'label': 0}, {'prob': 0.9972, 'bbox': [849, 212, 909, 275], 'label': 0}, {'prob': 0.9972, 'bbox': [712, 751, 766, 803], 'label': 0}, {'prob': 0.997, 'bbox': [581, 304, 651, 358], 'label': 0}, {'prob': 0.9968, 'bbox': [719, 650, 766, 702], 'label': 0}, {'prob': 0.9968, 'bbox': [1451, 711, 1505, 761], 'label': 0}, {'prob': 0.9967, 'bbox': [1578, 522, 1622, 568], 'label': 0}, {'prob': 0.9967, 'bbox': [906, 233, 960, 279], 'label': 0}, {'prob': 0.9963, 'bbox': [1006, 384, 1070, 438], 'label': 0}, {'prob': 0.9962, 'bbox': [161, 771, 226, 822], 'label': 0}, {'prob': 0.9961, 'bbox': [307, 554, 373, 625], 'label': 0}, {'prob': 0.9961, 'bbox': [639, 754, 710, 813], 'label': 0}, {'prob': 0.996, 'bbox': [920, 581, 966, 624], 'label': 0}, {'prob': 0.996, 'bbox': [1338, 790, 1406, 860], 'label': 0}, {'prob': 0.9959, 'bbox': [409, 646, 475, 702], 'label': 0}, {'prob': 0.9959, 'bbox': [424, 576, 479, 629], 'label': 0}, {'prob': 0.9956, 'bbox': [14, 594, 83, 644], 'label': 0}, {'prob': 0.9956, 'bbox': [445, 117, 493, 175], 'label': 0}, {'prob': 0.9955, 'bbox': [1233, 277, 1285, 350], 'label': 0}, {'prob': 0.9954, 'bbox': [1379, 168, 1440, 247], 'label': 0}, {'prob': 0.9954, 'bbox': [968, 162, 1031, 210], 'label': 0}, {'prob': 0.9954, 'bbox': [687, 661, 730, 693], 'label': 0}, {'prob': 0.9953, 'bbox': [150, 645, 224, 701], 'label': 0}, {'prob': 0.9953, 'bbox': [802, 599, 869, 646], 'label': 0}, {'prob': 0.9951, 'bbox': [659, 249, 849, 426], 'label': 0}, {'prob': 0.9951, 'bbox': [733, 445, 789, 491], 'label': 0}, {'prob': 0.995, 'bbox': [788, 655, 862, 703], 'label': 0}, {'prob': 0.9947, 'bbox': [1223, 376, 1278, 419], 'label': 0}, {'prob': 0.9946, 'bbox': [597, 414, 658, 464], 'label': 0}, {'prob': 0.9946, 'bbox': [726, 810, 802, 880], 'label': 0}, {'prob': 0.9945, 'bbox': [514, 512, 583, 563], 'label': 0}, {'prob': 0.9945, 'bbox': [984, 620, 1052, 673], 'label': 0}, {'prob': 0.9944, 'bbox': [1263, 332, 1310, 377], 'label': 0}, {'prob': 0.9944, 'bbox': [28, 524, 96, 581], 'label': 0}, {'prob': 0.9944, 'bbox': [1093, 378, 1171, 440], 'label': 0}, {'prob': 0.9942, 'bbox': [548, 363, 620, 427], 'label': 0}, {'prob': 0.9942, 'bbox': [1438, 172, 1497, 235], 'label': 0}, {'prob': 0.9942, 'bbox': [853, 631, 915, 671], 'label': 0}, {'prob': 0.9939, 'bbox': [523, 751, 589, 798], 'label': 0}, {'prob': 0.9939, 'bbox': [879, 384, 940, 427], 'label': 0}, {'prob': 0.9939, 'bbox': [1041, 779, 1120, 834], 'label': 0}, {'prob': 0.9937, 'bbox': [275, 442, 334, 489], 'label': 0}, {'prob': 0.9935, 'bbox': [830, 476, 899, 530], 'label': 0}, {'prob': 0.9934, 'bbox': [371, 406, 422, 478], 'label': 0}, {'prob': 0.9932, 'bbox': [576, 688, 629, 745], 'label': 0}, {'prob': 0.9932, 'bbox': [449, 501, 500, 538], 'label': 0}, {'prob': 0.993, 'bbox': [613, 530, 676, 579], 'label': 0}, {'prob': 0.993, 'bbox': [596, 193, 650, 249], 'label': 0}, {'prob': 0.993, 'bbox': [1204, 58, 1257, 106], 'label': 0}, {'prob': 0.9926, 'bbox': [863, 696, 929, 761], 'label': 0}, {'prob': 0.9926, 'bbox': [936, 395, 1006, 456], 'label': 0}, {'prob': 0.9925, 'bbox': [1144, 318, 1201, 377], 'label': 0}, {'prob': 0.9924, 'bbox': [60, 626, 110, 673], 'label': 0}, {'prob': 0.9922, 'bbox': [756, 626, 813, 674], 'label': 0}, {'prob': 0.9921, 'bbox': [1154, 411, 1219, 477], 'label': 0}, {'prob': 0.992, 'bbox': [419, 402, 472, 478], 'label': 0}, {'prob': 0.9919, 'bbox': [755, 701, 817, 748], 'label': 0}, {'prob': 0.9918, 'bbox': [718, 640, 750, 672], 'label': 0}, {'prob': 0.9917, 'bbox': [1403, 783, 1461, 836], 'label': 0}, {'prob': 0.9917, 'bbox': [383, 215, 437, 263], 'label': 0}, {'prob': 0.9915, 'bbox': [319, 298, 376, 356], 'label': 0}, {'prob': 0.991, 'bbox': [1013, 209, 1077, 263], 'label': 0}, {'prob': 0.9909, 'bbox': [463, 732, 531, 784], 'label': 0}, {'prob': 0.9906, 'bbox': [1416, 115, 1471, 165], 'label': 0}, {'prob': 0.9906, 'bbox': [406, 333, 460, 376], 'label': 0}, {'prob': 0.99, 'bbox': [952, 214, 1011, 259], 'label': 0}, {'prob': 0.9899, 'bbox': [1577, 258, 1625, 309], 'label': 0}, {'prob': 0.9898, 'bbox': [1288, 285, 1355, 336], 'label': 0}, {'prob': 0.9897, 'bbox': [643, 704, 697, 746], 'label': 0}, {'prob': 0.9892, 'bbox': [92, 519, 155, 565], 'label': 0}, {'prob': 0.9887, 'bbox': [440, 610, 498, 661], 'label': 0}, {'prob': 0.9885, 'bbox': [796, 498, 840, 545], 'label': 0}, {'prob': 0.9883, 'bbox': [148, 389, 209, 443], 'label': 0}, {'prob': 0.9878, 'bbox': [1463, 102, 1516, 149], 'label': 0}, {'prob': 0.9876, 'bbox': [215, 528, 250, 571], 'label': 0}, {'prob': 0.9869, 'bbox': [1534, 212, 1606, 258], 'label': 0}, {'prob': 0.9865, 'bbox': [1466, 28, 1522, 84], 'label': 0}, {'prob': 0.9863, 'bbox': [929, 119, 980, 169], 'label': 0}, {'prob': 0.9863, 'bbox': [684, 600, 734, 643], 'label': 0}, {'prob': 0.9861, 'bbox': [44, 702, 94, 760], 'label': 0}, {'prob': 0.986, 'bbox': [142, 349, 190, 394], 'label': 0}, {'prob': 0.9859, 'bbox': [604, 594, 659, 650], 'label': 0}, {'prob': 0.9855, 'bbox': [238, 561, 301, 605], 'label': 0}, {'prob': 0.985, 'bbox': [928, 676, 989, 757], 'label': 0}, {'prob': 0.985, 'bbox': [633, 642, 684, 695], 'label': 0}, {'prob': 0.9846, 'bbox': [607, 107, 650, 145], 'label': 0}, {'prob': 0.9845, 'bbox': [571, 80, 612, 118], 'label': 0}, {'prob': 0.9842, 'bbox': [915, 633, 964, 670], 'label': 0}, {'prob': 0.9839, 'bbox': [801, 552, 862, 596], 'label': 0}, {'prob': 0.9837, 'bbox': [744, 585, 796, 623], 'label': 0}, {'prob': 0.9836, 'bbox': [237, 705, 420, 885], 'label': 0}, {'prob': 0.9826, 'bbox': [1219, 123, 1279, 181], 'label': 0}, {'prob': 0.9817, 'bbox': [982, 770, 1048, 809], 'label': 0}, {'prob': 0.9814, 'bbox': [1284, 154, 1352, 207], 'label': 0}, {'prob': 0.9814, 'bbox': [1026, 331, 1077, 380], 'label': 0}, {'prob': 0.9803, 'bbox': [1083, 84, 1137, 141], 'label': 0}, {'prob': 0.9797, 'bbox': [870, 547, 918, 596], 'label': 0}, {'prob': 0.9794, 'bbox': [906, 193, 968, 233], 'label': 0}, {'prob': 0.9788, 'bbox': [246, 492, 303, 550], 'label': 0}, {'prob': 0.9787, 'bbox': [1333, 197, 1380, 237], 'label': 0}, {'prob': 0.9786, 'bbox': [1038, 697, 1089, 745], 'label': 0}, {'prob': 0.9773, 'bbox': [616, 250, 680, 300], 'label': 0}, {'prob': 0.9769, 'bbox': [1381, 693, 1429, 735], 'label': 0}, {'prob': 0.9739, 'bbox': [951, 456, 994, 502], 'label': 0}, {'prob': 0.9738, 'bbox': [451, 765, 532, 805], 'label': 0}, {'prob': 0.9735, 'bbox': [1577, 143, 1624, 180], 'label': 0}, {'prob': 0.9735, 'bbox': [541, 640, 626, 683], 'label': 0}, {'prob': 0.9725, 'bbox': [614, 810, 685, 877], 'label': 0}, {'prob': 0.9722, 'bbox': [384, 604, 437, 644], 'label': 0}, {'prob': 0.9719, 'bbox': [1076, 525, 1148, 575], 'label': 0}, {'prob': 0.9719, 'bbox': [673, 187, 723, 248], 'label': 0}, {'prob': 0.9711, 'bbox': [1222, 418, 1287, 457], 'label': 0}, {'prob': 0.97, 'bbox': [191, 686, 231, 720], 'label': 0}, {'prob': 0.9699, 'bbox': [472, 438, 523, 487], 'label': 0}, {'prob': 0.9695, 'bbox': [306, 499, 369, 550], 'label': 0}, {'prob': 0.9695, 'bbox': [1126, 495, 1174, 541], 'label': 0}, {'prob': 0.9694, 'bbox': [69, 744, 135, 795], 'label': 0}, {'prob': 0.9693, 'bbox': [1048, 640, 1117, 683], 'label': 0}, {'prob': 0.9687, 'bbox': [1506, 166, 1569, 234], 'label': 0}, {'prob': 0.9687, 'bbox': [712, 681, 762, 723], 'label': 0}, {'prob': 0.9682, 'bbox': [505, 670, 549, 714], 'label': 0}, {'prob': 0.9663, 'bbox': [502, 413, 548, 461], 'label': 0}, {'prob': 0.9652, 'bbox': [729, 498, 781, 544], 'label': 0}, {'prob': 0.9642, 'bbox': [1226, 452, 1276, 491], 'label': 0}, {'prob': 0.9615, 'bbox': [129, 709, 183, 755], 'label': 0}, {'prob': 0.9613, 'bbox': [404, 715, 463, 770], 'label': 0}, {'prob': 0.9596, 'bbox': [1245, 243, 1305, 286], 'label': 0}, {'prob': 0.9591, 'bbox': [145, 712, 222, 774], 'label': 0}, {'prob': 0.9582, 'bbox': [902, 159, 960, 194], 'label': 0}, {'prob': 0.9561, 'bbox': [943, 783, 994, 823], 'label': 0}, {'prob': 0.9547, 'bbox': [405, 437, 450, 491], 'label': 0}, {'prob': 0.9527, 'bbox': [1311, 322, 1359, 365], 'label': 0}, {'prob': 0.9511, 'bbox': [685, 450, 724, 507], 'label': 0}, {'prob': 0.9505, 'bbox': [1052, 831, 1123, 884], 'label': 0}, {'prob': 0.9504, 'bbox': [1152, 91, 1217, 142], 'label': 0}, {'prob': 0.9492, 'bbox': [1032, 550, 1080, 606], 'label': 0}, {'prob': 0.9491, 'bbox': [895, 656, 954, 704], 'label': 0}, {'prob': 0.9487, 'bbox': [1028, 507, 1079, 559], 'label': 0}, {'prob': 0.9479, 'bbox': [1469, 209, 1503, 252], 'label': 0}, {'prob': 0.9465, 'bbox': [1426, 711, 1468, 748], 'label': 0}, {'prob': 0.9457, 'bbox': [977, 559, 1020, 607], 'label': 0}, {'prob': 0.9456, 'bbox': [1075, 565, 1160, 627], 'label': 0}, {'prob': 0.9449, 'bbox': [521, 798, 571, 829], 'label': 0}, {'prob': 0.9435, 'bbox': [1140, 621, 1197, 667], 'label': 0}, {'prob': 0.9427, 'bbox': [996, 695, 1054, 746], 'label': 0}, {'prob': 0.9405, 'bbox': [1245, 77, 1319, 142], 'label': 0}, {'prob': 0.9347, 'bbox': [704, 539, 755, 589], 'label': 0}, {'prob': 0.9319, 'bbox': [277, 521, 328, 576], 'label': 0}, {'prob': 0.9247, 'bbox': [1051, 60, 1093, 96], 'label': 0}, {'prob': 0.9245, 'bbox': [106, 608, 156, 667], 'label': 0}, {'prob': 0.9226, 'bbox': [338, 583, 393, 634], 'label': 0}, {'prob': 0.9197, 'bbox': [813, 703, 864, 759], 'label': 0}, {'prob': 0.9192, 'bbox': [544, 168, 598, 222], 'label': 0}, {'prob': 0.9148, 'bbox': [711, 186, 765, 232], 'label': 0}, {'prob': 0.9124, 'bbox': [665, 494, 707, 536], 'label': 0}, {'prob': 0.9089, 'bbox': [520, 708, 569, 743], 'label': 0}, {'prob': 0.908, 'bbox': [578, 776, 627, 815], 'label': 0}, {'prob': 0.905, 'bbox': [894, 771, 958, 809], 'label': 0}, {'prob': 0.9014, 'bbox': [478, 779, 545, 824], 'label': 0}, {'prob': 0.8994, 'bbox': [1338, 647, 1420, 700], 'label': 0}, {'prob': 0.8929, 'bbox': [229, 699, 290, 747], 'label': 0}, {'prob': 0.8922, 'bbox': [1173, 476, 1236, 521], 'label': 0}, {'prob': 0.8859, 'bbox': [205, 719, 261, 763], 'label': 0}, {'prob': 0.8846, 'bbox': [1169, 525, 1223, 559], 'label': 0}, {'prob': 0.882, 'bbox': [1031, 180, 1095, 224], 'label': 0}, {'prob': 0.8768, 'bbox': [1577, 578, 1624, 621], 'label': 0}, {'prob': 0.8715, 'bbox': [1334, 219, 1381, 268], 'label': 0}, {'prob': 0.8615, 'bbox': [473, 403, 508, 448], 'label': 0}, {'prob': 0.8605, 'bbox': [1284, 453, 1327, 500], 'label': 0}, {'prob': 0.8596, 'bbox': [451, 351, 481, 391], 'label': 0}, {'prob': 0.8542, 'bbox': [132, 750, 207, 804], 'label': 0}, {'prob': 0.8506, 'bbox': [1452, 641, 1492, 689], 'label': 0}, {'prob': 0.8462, 'bbox': [468, 629, 517, 672], 'label': 0}, {'prob': 0.8431, 'bbox': [462, 678, 508, 719], 'label': 0}, {'prob': 0.8401, 'bbox': [1558, 383, 1601, 433], 'label': 0}, {'prob': 0.8388, 'bbox': [576, 460, 621, 506], 'label': 0}, {'prob': 0.8278, 'bbox': [760, 534, 819, 574], 'label': 0}, {'prob': 0.8201, 'bbox': [439, 721, 509, 761], 'label': 0}, {'prob': 0.8194, 'bbox': [1, 571, 31, 618], 'label': 0}, {'prob': 0.8169, 'bbox': [858, 746, 906, 781], 'label': 0}, {'prob': 0.8159, 'bbox': [29, 4, 90, 56], 'label': 0}, {'prob': 0.8142, 'bbox': [549, 436, 604, 487], 'label': 0}, {'prob': 0.7928, 'bbox': [1108, 628, 1151, 673], 'label': 0}, {'prob': 0.7856, 'bbox': [705, 145, 745, 186], 'label': 0}, {'prob': 0.7719, 'bbox': [1301, 118, 1361, 149], 'label': 0}, {'prob': 0.77, 'bbox': [913, 134, 950, 173], 'label': 0}, {'prob': 0.7423, 'bbox': [1067, 482, 1127, 524], 'label': 0}, {'prob': 0.7346, 'bbox': [1528, 744, 1591, 793], 'label': 0}, {'prob': 0.7272, 'bbox': [841, 676, 912, 727], 'label': 0}, {'prob': 0.7147, 'bbox': [1588, 695, 1647, 761], 'label': 0}, {'prob': 0.6874, 'bbox': [1625, 496, 1647, 538], 'label': 0}, {'prob': 0.6619, 'bbox': [140, 530, 177, 568], 'label': 0}, {'prob': 0.6467, 'bbox': [1243, 397, 1295, 437], 'label': 0}, {'prob': 0.6435, 'bbox': [1106, 448, 1150, 492], 'label': 0}, {'prob': 0.6304, 'bbox': [603, 798, 666, 844], 'label': 0}, {'prob': 0.59, 'bbox': [536, 602, 587, 638], 'label': 0}, {'prob': 0.5502, 'bbox': [811, 735, 873, 779], 'label': 0}, {'prob': 0.5208, 'bbox': [629, 362, 657, 404], 'label': 0}, {'prob': 0.5192, 'bbox': [1440, 324, 1479, 377], 'label': 0}, {'prob': 0.5086, 'bbox': [705, 168, 743, 216], 'label': 0}, {'prob': 0.4993, 'bbox': [1597, 457, 1640, 500], 'label': 0}, {'prob': 0.4856, 'bbox': [987, 674, 1026, 710], 'label': 0}, {'prob': 0.4812, 'bbox': [234, 525, 269, 570], 'label': 0}, {'prob': 0.4675, 'bbox': [10, 727, 65, 772], 'label': 0}, {'prob': 0.4672, 'bbox': [430, 771, 487, 809], 'label': 0}, {'prob': 0.4262, 'bbox': [496, 725, 551, 762], 'label': 0}, {'prob': 0.4218, 'bbox': [500, 616, 548, 649], 'label': 0}, {'prob': 0.4183, 'bbox': [1195, 186, 1240, 236], 'label': 0}, {'prob': 0.4044, 'bbox': [184, 663, 250, 707], 'label': 0}, {'prob': 0.3819, 'bbox': [224, 677, 264, 709], 'label': 0}, {'prob': 0.3754, 'bbox': [818, 840, 848, 882], 'label': 0}, {'prob': 0.3683, 'bbox': [145, 690, 198, 737], 'label': 0}, {'prob': 0.3365, 'bbox': [430, 267, 476, 307], 'label': 0}, {'prob': 0.3196, 'bbox': [1309, 498, 1354, 545], 'label': 0}, {'prob': 0.2952, 'bbox': [557, 519, 597, 555], 'label': 0}, {'prob': 0.258, 'bbox': [1329, 6, 1384, 71], 'label': 0}, {'prob': 0.2163, 'bbox': [1590, 188, 1631, 232], 'label': 0}, {'prob': 0.1712, 'bbox': [738, 151, 777, 193], 'label': 0}, {'prob': 0.1319, 'bbox': [1303, 62, 1349, 95], 'label': 0}, {'prob': 0.118, 'bbox': [909, 741, 961, 788], 'label': 0}, {'prob': 0.1031, 'bbox': [679, 803, 727, 854], 'label': 0}] 2 | -------------------------------------------------------------------------------- /utils/overlapping_tiles_detection/point_based_decision/point_mapping.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | import cv2 5 | from PIL import Image, ImageDraw 6 | from scipy.optimize import linear_sum_assignment 7 | from scipy.spatial import distance 8 | 9 | 10 | OFFSET = 448 11 | THRESHOLD = 50 12 | 13 | 14 | BBOX_LIST1 = [[445, 117, 493, 175], [472, 438, 523, 487], [473, 403, 508, 448], [451, 351, 481, 391], [430, 267, 476, 307]] 15 | BBOX_LIST2 = [[448, 116, 492, 179], [471, 433, 526, 482], [452, 352, 480, 387], [434, 263, 472, 307]] 16 | 17 | 18 | def get_centers(obj_list, offset): 19 | obj_cen_list = [] 20 | for obj in obj_list: 21 | x1 = obj[0] 22 | y1 = obj[1] 23 | x2 = obj[2] 24 | y2 = obj[3] 25 | x_c = (x1 + x2) / 2 26 | y_c = (y1 + y1) / 2 27 | w = (x2 - x1) 28 | h = (y2 - y1) 29 | obj_cen_list.append([x_c - offset, y_c, w, h]) 30 | return obj_cen_list 31 | 32 | 33 | def visualize(list1, list2): 34 | ROI = cv2.imread('91315_leica_at2_40x.svs.98728.44031.1649.892.jpg') 35 | cv2.rectangle(ROI, (0, 0), (512, 512), (255, 0, 0), 3) 36 | cv2.rectangle(ROI, (448, 0), (960, 512), (0, 0, 255), 3) 37 | d = 3 38 | for obj in list1: 39 | cv2.circle(ROI, 40 | (OFFSET + obj[0], obj[1]), 41 | d * 2, (0, 255, 0), -1) 42 | cv2.imwrite('another.png', ROI) 43 | 44 | 45 | def main(): 46 | 47 | tile1_coord = get_centers(BBOX_LIST1, OFFSET) 48 | tile2_coord = get_centers(BBOX_LIST2, OFFSET) 49 | print tile1_coord 50 | print tile2_coord 51 | tile1_coord = np.asarray(tile1_coord) 52 | tile2_coord = np.asarray(tile2_coord) 53 | 54 | visualize(tile1_coord, None) 55 | 56 | # dist_matrix = distance.cdist(tile1_coord[:, :2], tile2_coord[:, :2]) 57 | # print dist_matrix 58 | # row_ind, col_ind = linear_sum_assignment(np.asarray(dist_matrix, np.float32)) 59 | # print row_ind 60 | # print col_ind 61 | 62 | # unique_objs = [x for i, x in enumerate(tile1_coord) if i not in row_ind] 63 | # unique_objs += [x for i, x in enumerate(tile2_coord) if i not in col_ind] 64 | # print unique_objs 65 | 66 | # tile1_mapped_objs = tile1_coord[row_ind] 67 | # tile2_mapped_objs = tile2_coord[col_ind] 68 | 69 | # # ++++++++++++++++++++++ Finding width & heigh of each mapped bbox +++++++++++++++++++ 70 | # tile_mappend_objs_w = np.maximum(tile1_mapped_objs, tile2_mapped_objs)[:, 2] 71 | # tile_mappend_objs_h = np.maximum(tile1_mapped_objs, tile2_mapped_objs)[:, 3] 72 | # # ++++++++++++++++++++++ Finding x_c & y_c of each mapped bbox +++++++++++++++++++++++ 73 | # tile_mappend_objs_aveg = (tile1_mapped_objs + tile2_mapped_objs) / 2 74 | # tile_mappend_objs_xc = tile_mappend_objs_aveg[:, 0] 75 | # tile_mappend_objs_yc = tile_mappend_objs_aveg[:, 1] 76 | 77 | # ROI = cv2.imread('91315_leica_at2_40x.svs.98728.44031.1649.892.jpg') 78 | # # +++++++++++++++++++ Draw Unique Bbox +++++++++++++++++++++++++++++++++ 79 | # for obj in unique_objs: 80 | # x1 = obj[0] + OFFSET - (obj[2] / 2) 81 | # y1 = obj[1] - (obj[3] / 2) 82 | # x2 = obj[0] + OFFSET + (obj[2] / 2) 83 | # y2 = obj[1] + (obj[3] / 2) 84 | # cv2.rectangle(ROI, (x1, y1), (x2, y2), (255, 0, 0), 3) 85 | 86 | # # +++++++++++++++++++ Draw Mapped Bbox +++++++++++++++++++++++++++++++++ 87 | # for i in range(tile_mappend_objs_xc.shape[0]): 88 | # x1 = tile_mappend_objs_xc[i] + OFFSET - (tile_mappend_objs_w[i] / 2) 89 | # y1 = tile_mappend_objs_yc[i] - (tile_mappend_objs_h[i] / 2) 90 | # x2 = tile_mappend_objs_xc[i] + OFFSET + (tile_mappend_objs_w[i] / 2) 91 | # y2 = tile_mappend_objs_yc[i] + (tile_mappend_objs_h[i] / 2) 92 | # cv2.rectangle(ROI, (x1, y1), (x2, y2), (0, 255, 0), 3) 93 | # cv2.imwrite('del.png', ROI) 94 | 95 | 96 | if __name__ == '__main__': 97 | main() 98 | --------------------------------------------------------------------------------