├── .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 |  | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------