├── TotalSegmentator ├── Testing │ ├── CMakeLists.txt │ └── Python │ │ └── CMakeLists.txt ├── Resources │ ├── Icons │ │ └── TotalSegmentator.png │ ├── UI │ │ └── TotalSegmentator.ui │ └── totalsegmentator_snomed_mapping.csv ├── CMakeLists.txt └── TotalSegmentator.py ├── Screenshot01.jpg ├── TotalSegmentator.png ├── TotalSegmentator.xcf ├── CMakeLists.txt ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE └── README.md /TotalSegmentator/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Python) 2 | -------------------------------------------------------------------------------- /Screenshot01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lassoan/SlicerTotalSegmentator/HEAD/Screenshot01.jpg -------------------------------------------------------------------------------- /TotalSegmentator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lassoan/SlicerTotalSegmentator/HEAD/TotalSegmentator.png -------------------------------------------------------------------------------- /TotalSegmentator.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lassoan/SlicerTotalSegmentator/HEAD/TotalSegmentator.xcf -------------------------------------------------------------------------------- /TotalSegmentator/Testing/Python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py) 3 | -------------------------------------------------------------------------------- /TotalSegmentator/Resources/Icons/TotalSegmentator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lassoan/SlicerTotalSegmentator/HEAD/TotalSegmentator/Resources/Icons/TotalSegmentator.png -------------------------------------------------------------------------------- /TotalSegmentator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | set(MODULE_NAME TotalSegmentator) 3 | 4 | #----------------------------------------------------------------------------- 5 | set(MODULE_PYTHON_SCRIPTS 6 | ${MODULE_NAME}.py 7 | ) 8 | 9 | set(MODULE_PYTHON_RESOURCES 10 | Resources/SegmentationCategoryTypeModifier-TotalSegmentator.term.json 11 | Resources/Icons/${MODULE_NAME}.png 12 | Resources/UI/${MODULE_NAME}.ui 13 | Resources/totalsegmentator_snomed_mapping.csv 14 | ) 15 | 16 | #----------------------------------------------------------------------------- 17 | slicerMacroBuildScriptedModule( 18 | NAME ${MODULE_NAME} 19 | SCRIPTS ${MODULE_PYTHON_SCRIPTS} 20 | RESOURCES ${MODULE_PYTHON_RESOURCES} 21 | WITH_GENERIC_TESTS 22 | ) 23 | 24 | #----------------------------------------------------------------------------- 25 | if(BUILD_TESTING) 26 | 27 | # Register the unittest subclass in the main script as a ctest. 28 | # Note that the test will also be available at runtime. 29 | slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py) 30 | 31 | # Additional build-time testing 32 | add_subdirectory(Testing) 33 | endif() 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.3...3.19.7 FATAL_ERROR) 2 | 3 | project(TotalSegmentator) 4 | 5 | #----------------------------------------------------------------------------- 6 | # Extension meta-information 7 | set(EXTENSION_HOMEPAGE "https://github.com/lassoan/SlicerTotalSegmentator") 8 | set(EXTENSION_CATEGORY "Segmentation") 9 | set(EXTENSION_CONTRIBUTORS "Andras Lasso (PerkLab, Queen's University)") 10 | set(EXTENSION_DESCRIPTION "Fully automatic whole-body CT segmentation of 104 structures, using TotalSegmentator AI model.") 11 | set(EXTENSION_ICONURL "https://raw.githubusercontent.com/lassoan/SlicerTotalSegmentator/main/TotalSegmentator.png") 12 | set(EXTENSION_SCREENSHOTURLS "https://raw.githubusercontent.com/lassoan/SlicerTotalSegmentator/main/Screenshot01.jpg") 13 | set(EXTENSION_DEPENDS "PyTorch NNUNet") 14 | 15 | #----------------------------------------------------------------------------- 16 | # Extension dependencies 17 | find_package(Slicer REQUIRED) 18 | include(${Slicer_USE_FILE}) 19 | 20 | #----------------------------------------------------------------------------- 21 | # Extension modules 22 | add_subdirectory(TotalSegmentator) 23 | ## NEXT_MODULE 24 | 25 | #----------------------------------------------------------------------------- 26 | include(${Slicer_EXTENSION_GENERATE_CONFIG}) 27 | include(${Slicer_EXTENSION_CPACK}) 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # HD-BET may be installed in the source tree during testing, exclude those files from revision control 2 | HDBrainExtractionTool/HD_BET/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | https://discourse.slicer.org/u/lassoan. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /TotalSegmentator/Resources/UI/TotalSegmentator.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TotalSegmentator 4 | 5 | 6 | 7 | 0 8 | 0 9 | 230 10 | 441 11 | 12 | 13 | 14 | 15 | 16 | 17 | Inputs 18 | 19 | 20 | 21 | 22 | 23 | Input volume: 24 | 25 | 26 | 27 | 28 | 29 | 30 | Input abdominal, chest, or whole body CT. 31 | 32 | 33 | 34 | vtkMRMLScalarVolumeNode 35 | 36 | 37 | 38 | false 39 | 40 | 41 | false 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | 51 | Segmentation task: 52 | 53 | 54 | 55 | 56 | 57 | 58 | Choose "total" for segmenting all structures, or any of the more specialized segmentation tasks (that segment only a handful of specific structures in a certain region). 59 | 60 | 61 | 62 | 63 | 64 | 65 | Fast: 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Enable fast option to get less accurate results, faster. 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | fast mode is not available for this task 85 | 86 | 87 | not available 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Outputs 100 | 101 | 102 | 103 | 104 | 105 | Segmentation: 106 | 107 | 108 | 109 | 110 | 111 | 112 | This will store the segmentation result. 113 | 114 | 115 | 116 | vtkMRMLSegmentationNode 117 | 118 | 119 | 120 | false 121 | 122 | 123 | true 124 | 125 | 126 | true 127 | 128 | 129 | true 130 | 131 | 132 | true 133 | 134 | 135 | true 136 | 137 | 138 | Create new segmentation on Apply 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | false 152 | 153 | 154 | Start segmentation. 155 | 156 | 157 | Apply 158 | 159 | 160 | true 161 | 162 | 163 | 164 | 165 | 166 | 167 | Advanced 168 | 169 | 170 | true 171 | 172 | 173 | 174 | 175 | 176 | Use standard segment names: 177 | 178 | 179 | 180 | 181 | 182 | 183 | If enabled (default) then segment names are obtained from Slicer standard terminology files. If disabled then TotalSegmentator identifiers are used as segment names. 184 | 185 | 186 | 187 | 188 | 189 | true 190 | 191 | 192 | 193 | 194 | 195 | 196 | Force to use CPU: 197 | 198 | 199 | 200 | 201 | 202 | 203 | Use CPU, even if GPU is available. Useful if the GPU does not have enough memory. 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | TotalSegmentator Python package: 214 | 215 | 216 | 217 | 218 | 219 | 220 | Force upgrade of TotalSegmentator Python package to the version required by this module. 221 | 222 | 223 | Force reinstall 224 | 225 | 226 | 227 | 228 | 229 | 230 | TotalSegmentator license: 231 | 232 | 233 | 234 | 235 | 236 | 237 | Get metadata information for the TotalSegmentator package currently installed 238 | 239 | 240 | Get TotalSegmentator package information 241 | 242 | 243 | 244 | 245 | 246 | 247 | false 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | Set or update TotalSegmentator license key to allow running tasks that require a license. The registration has to be done only once, the license information is stored persistently in the TotalSegmentation configuration. 261 | 262 | 263 | Set license key 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | QPlainTextEdit::NoWrap 274 | 275 | 276 | true 277 | 278 | 279 | Qt::TextSelectableByMouse 280 | 281 | 282 | 283 | 284 | 285 | 286 | Qt::Vertical 287 | 288 | 289 | 290 | 20 291 | 40 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | ctkCollapsibleButton 301 | QWidget 302 |
ctkCollapsibleButton.h
303 | 1 304 |
305 | 306 | ctkFittedTextBrowser 307 | QTextBrowser 308 |
ctkFittedTextBrowser.h
309 |
310 | 311 | ctkMenuButton 312 | QPushButton 313 |
ctkMenuButton.h
314 |
315 | 316 | qMRMLNodeComboBox 317 | QWidget 318 |
qMRMLNodeComboBox.h
319 |
320 | 321 | qMRMLWidget 322 | QWidget 323 |
qMRMLWidget.h
324 | 1 325 |
326 | 327 | qMRMLSegmentationShow3DButton 328 | ctkMenuButton 329 |
qMRMLSegmentationShow3DButton.h
330 |
331 |
332 | 333 | 334 | 335 | TotalSegmentator 336 | mrmlSceneChanged(vtkMRMLScene*) 337 | inputVolumeSelector 338 | setMRMLScene(vtkMRMLScene*) 339 | 340 | 341 | 122 342 | 132 343 | 344 | 345 | 260 346 | 60 347 | 348 | 349 | 350 | 351 | TotalSegmentator 352 | mrmlSceneChanged(vtkMRMLScene*) 353 | outputSegmentationSelector 354 | setMRMLScene(vtkMRMLScene*) 355 | 356 | 357 | 161 358 | 8 359 | 360 | 361 | 260 362 | 175 363 | 364 | 365 | 366 | 367 |
368 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TotalSegmentator 2 | 3 | 3D Slicer extension for fully automatic whole body CT segmentation using ["TotalSegmentator" AI model](https://github.com/wasserth/TotalSegmentator). Computation time is less than one minute. 4 | 5 | ![](Screenshot01.jpg) 6 | 7 | If you use the TotalSegmentator nn-Unet function from this software in your research, please cite: 8 | 9 | > Wasserthal J., Meyer M., , Hanns-Christian Breit H.C., Cyriac J., Shan Y., Segeroth, M.: TotalSegmentator: robust segmentation of 104 anatomical structures in CT images. https://arxiv.org/abs/2208.05868 10 | 11 | ## Setup 12 | 13 | 1. Setup your GPU driver (optional) 14 | 15 | If you have a powerful GPU is available then a full-quality segmentation can be computed in a few minutes, instead of 40-50 minutes on the CPU. Therefore, it is recommended to set up GPU acceleration as described in this section. 16 | 17 | - If a strong GPU with 7GB or more memory is available: 18 | - On Windows: 19 | - If using NVIDIA GPU: Make sure CUDA is installed. [CUDA version must be one of those listed on pytorch website as "Compute platform" for your system](https://pytorch.org/get-started/locally/). You can download CUDA from [here](https://developer.nvidia.com/cuda-downloads). 20 | - PyTorch does not officially support AMD GPUs for on Windows, therefore you need to use the CPU. 21 | - On Linux: 22 | - If using NVIDIA GPU: Make sure NVIDIA drivers are installed. If CUDA is installed then make sure [CUDA version is one of those listed on pytorch website as "Compute platform" for your system](https://pytorch.org/get-started/locally/). If CUDA is not installed then it will be set up automatically during installation (pytorch binary packages contain the appropriate CUDA version). 23 | - If using AMD GPU: In theory, ROCm-compatible AMD GPUs should work, but this is not tested. 24 | - On macOS: PyTorch does not officially support GPUs for macOS, therefore you need to use the CPU. 25 | - If suitable GPU is not available: Graphics driver updates or CUDA installation is not necessary, everything will still work, it will just take more time. 26 | 27 | 2. Install latest version of [3D Slicer](https://slicer.readthedocs.io/en/latest/user_guide/getting_started.html#installing-3d-slicer) 28 | 29 | 3. [Install `TotalSegmentator` extension in 3D Slicer](https://slicer.readthedocs.io/en/latest/user_guide/extensions_manager.html#install-extensions) 30 | 31 | ## Tutorial 32 | 33 | - Start 3D Slicer 34 | - Go to `Sample Data` module and load `CTA Abdomen (Panoramix)` data set 35 | - Go to `TotalSegmentator` module 36 | - Select `Input volume` -> `Panoramix-cropped` 37 | - Select `Segmentation` -> `Create new segmentation` 38 | - Click `Apply` 39 | - When this module is used the first time: 40 | - It needs to download and install PyTorch and TotalSegmentator Python packages and weights for the AI models. This can take 5-10 minutes and several GB disk space. 41 | - You may get an error popup: `Failed to compute results ... Command ... 'pip', 'install' ... returned non-zero exit status 1`. This may be normal, see what to do in [Troubleshooting section](#failed-to-compute-results-error-at-the-first-run) 42 | - Expected computation time: 43 | - With CUDA-capable GPU: 20-30 seconds in fast mode, 40-50 seconds in full-resolution mode. 44 | - Without GPU: 1 minute in fast mode, 40-50 minutes in full-resolution mode. 45 | - To display the segmentation in 3D: click the `Show 3D` button 46 | 47 | ## User interface 48 | 49 | - Inputs 50 | - Input volume: input CT image 51 | - Segmentation task: instead of the default "total" segmentation, a more specialized segmentation model can be chosen 52 | - Fast: performs segmentation faster, but at lower resolution 53 | - Outputs 54 | - Segmentation: it will contain a brain segment, which specifies the brain region 55 | - Show 3D: show/hide segments in 3D views 56 | - Advanced: 57 | - Use standard segment names: use names defined in standard terminology files from [DCMQI](https://github.com/QIICR/dcmqi) (enabled by default). If disabled then TotalSegmentator identifiers will be used as segment names. 58 | - Use latest development version: use latest development version from TotalSegmentator master branch during a forced reinstall. 59 | - Force reinstall: force reinstallation of the AI engine - TotalSegmentator Python package. This may be needed if other modules compromise the installation. 60 | - Import weights: When using TotalSegmentator, weights are often downloaded automatically. You can import any specialized or licensed weights you receive from the developer so that TotalSegmentator can find and use them. 61 | - Get TotalSegmentator package information: retrieve installed version of the AI engine - TotalSegmentator Python package. 62 | 63 | ## Troubleshooting 64 | 65 | ### Failed to compute results error the first time trying to use this Slicer module 66 | 67 | #### Problem: Error popup appears: `Failed to compute results ... Command ... 'pip', 'install' ... returned non-zero exit status 1` 68 | 69 | Explanation: This happens because when the tool has to download and install PyTorch and other required Python packages. 70 | 71 | Solution: 72 | - The module instructs you to restart Slicer and try again if an error occurs. Please try this first. 73 | - If this does not help then make sure you have enough memory space (physical RAM and virtual memory in total should be at least 32GB) and you have enough disk space (at least 20GB free disk space is required) and try again. 74 | - If you still run into issues then report the problem on the [3D Slicer forum](https://discourse.slicer.org). In your forum post, include the full application log of the failed attempt (you can get the application log in menu: Help / Report a bug). 75 | 76 | #### Problem: Error popup on the first run: `Failed to compute results ... Command ... 'PythonSlicer', TotalSegmentator.exe ... returned non-zero exit status 120` 77 | 78 | Explanation: This typically happens when PyTorch is not installed correctly or your computer runs out of memory. 79 | 80 | Solution: 81 | - Check the message log (textbox under the Apply button). If you see a message like `RuntimeError: ... DefaultCPUAllocator: not enough memory: you tried to allocate ... bytes.` then it means that your computer has not enough memory to process the input image. You can use `Crop volume` module to crop the your image to the relevant region and/or resample it (with using a scaling factor >1) until the memory usage drops low enough so that your computer can handle it. Alternatively, you can install more physical RAM or configure your operating system to use more virtual memory. 82 | - If the problem does not seem to be due to running out of memory then reinstall PyTorch as described in solution of `Segmentation fails while predicting` issue. 83 | 84 | ### Segmentation fails while predicting 85 | 86 | #### RuntimeError: CUDA out of memory 87 | 88 | Problem: Segmentation fails while predicting and the `RuntimeError: CUDA out of memory.` message is found in the message log (textbox under the Apply button). 89 | 90 | Explanation: This means that a CUDA-capable GPU is available, but it is not powerful enough to be used by TotalSegmentator. 91 | 92 | Solution: It is recommended to switch to use the CPU by the following steps: 93 | - Go to `PyTorch Util` module, click `Uninstall PyTorch`. An error may be reported at the end of this step, as some PyTorch files are in use. Click `Restart the application` button to unload all PyTorch files. 94 | - Go to `PyTorch Util` module, select `cpu` as `Computation backend`, and click `Install PyTorch`. 95 | 96 | If your GPU has more than 7GB memory and you still get this error then the error message might indicate that the PyTorch CUDA version does not match the CUDA version installed on the system. Reinstall PyTorch with the correct CUDA version by following the instructions given below for [GPU is not found](#gpu-is-not-found). 97 | 98 | #### numpy.core._exceptions._ArrayMemoryError: Unable to allocate 99 | 100 | Problem: Segmentation fails while predicting and a message similar to this is found in the message log (textbox under the Apply button): `numpy.core._exceptions._ArrayMemoryError: Unable to allocate 6.85 GiB for an array with shape (287, 233, 233, 118) and data type float32` 101 | 102 | Explanation: This means that your computer has ran out of memory (RAM) while performing the segmentation. 103 | 104 | Solution: It is recommended to reduce the image size or increase avaialable memory size by one of the following options: 105 | - A. Crop and/or resample the input image using `Crop volume` module. Cropping the image to a smaller size will reduce memory need without decreasing the segmentation quality. Setting "Spacing scale" to value larger than 1 (for example 2 or 3) will preserve the extents of the image but small details may be lost (this should not be an issue when the object of interest is a large structure with a smooth surface). 106 | - B. Increase the available "virtual memory" (also known as "swap") size in your computer. On Windows and Linux, you can configure the virtual memory size in your system settings. On macOS, virtual memory is automatically allocated if there is sufficient free disk space. Increasing virtual memory size can avoid issues cause by short memory usage peaks, but can severely slow down the segmentation. To avoid slowdown, add more physical RAM to your computer. 107 | - C. Upgrade your computer hardware. Add physical RAM to your computer, or if it is not upgradeable then get a new computer or rent a virtual machine from a cloud computing provider. If you have more physical RAM then you can process larger images without making the segmentation take significantly longer time. 108 | 109 | #### AttributeError: 'DummyFile' object has no attribute 'flush' 110 | 111 | Problem: Segmentation fails while predicting and the `'DummyFile' object has no attribute 'flush'` message is found in the message log (textbox under the Apply button). 112 | 113 | Explanation: This error message can be safely ignored (it is just a small bug in the implementation of the helper class that suppresses nnunet output). If segmentation failed then it is due to another error in the output. 114 | 115 | Solution: Look for other messages in the output. 116 | 117 | ### GPU is not found 118 | 119 | Problem: Your computer has a CUDA-capable GPU but TotalSegmentator reports that GPU is not available. 120 | 121 | Explanation: CUDA may not be installed on the system or CUDA version in PyTorch does not match the system CUDA version. 122 | 123 | Solution: 124 | - Make sure that the the CUDA vesion installed on the system [is one of those listed on pytorch website as "Compute platform" for your system](https://pytorch.org/get-started/locally/). You can download CUDA from [here](https://developer.nvidia.com/cuda-downloads). 125 | - Go to `PyTorch Util` module, click `Uninstall PyTorch`. An error may be reported at the end of this step, as some PyTorch files are in use. Click `Restart the application` button to unload all PyTorch files. 126 | - Go to `PyTorch Util` module, select the `Computation backend` that matches the system CUDA version, and click `Install PyTorch`. The CUDA computational backend name has the format `cuNNN`, where _NNN_ corresponds to the CUDA major+minor version. For example, CUDA 11.7 backend name is `cu117`. 127 | 128 | ### Face segment is inaccurate 129 | 130 | Problem: There is a big segment called `face` at the front of the head, which is not an accurate segmentation of the face. 131 | 132 | Explanation: This segment is not designed to match the shape of an anatomical feature, but it designates the general area of the face. It can be used to remove features (for example by masking or blurring the image or clipping models) that might otherwise identify the individual subject. Removing these features makes it easier to share 3D data. 133 | 134 | ### Fail to download model files 135 | 136 | Model files are hosted on github.com or Zenodo.org and downloaded automatically when segmenting the first time. Institutional firewall or proxy servers may prevent access or the server may be temporarily overloaded, which may cause an error report similar to `requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://zenodo.org/record/6802052/files/Task256_TotalSegmentator_3mm_1139subj.zip?download=1`. Potential solutions: 137 | - retry later when the server may be less overloaded 138 | - talk to IT administrators or use a VPN to access the server 139 | - download the file manually and unzip it in the `.totalsegmentator` folder in the user's profile (for example in `c:\Users\(yourusername)\.totalsegmentator\nnunet\results\Dataset291_TotalSegmentator_part1_organs_1559subj`) 140 | 141 | ### json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 142 | 143 | Problem: Your TotalSegmentator config.json file was corrupted or was not created correctly. 144 | 145 | Solution: Delete your `.totalsegmentator` folder in the user's profile (for example in `c:\Users\(yourusername)\.totalsegmentator`). The folder will be recreated with the correct content. 146 | 147 | ## Contributing 148 | 149 | Contributions to this extensions are welcome. Please send a pull request with any suggested changes. [3D Slicer contribution guidelines](https://github.com/Slicer/Slicer/blob/main/CONTRIBUTING.md) apply. 150 | 151 | ## Contact 152 | 153 | Please post any questions to the [Slicer Forum](https://discourse.slicer.org). 154 | 155 | Developers of this extension are not associated with the developers of TotalSegmentator, just provide the convenient 3D Slicer based user interface. 156 | -------------------------------------------------------------------------------- /TotalSegmentator/Resources/totalsegmentator_snomed_mapping.csv: -------------------------------------------------------------------------------- 1 | Name,Category_CodingScheme,Category_CodeValue,Category_CodeMeaning,Type_CodingScheme,Type_CodeValue,Type_CodeMeaning,TypeModifier_CodingScheme,TypeModifier_CodeValue,TypeModifier_CodeMeaning,Region_CodingScheme,Region_CodeValue,Region_CodeMeaning,RegionModifier_CodingScheme,RegionModifier_CodeValue,RegionModifier_CodeMeaning,Color_R,Color_G,Color_B,, 2 | spleen,SCT,123037004,Anatomical Structure,SCT,78961009,Spleen,,,,,,,,,,157,108,162,, 3 | kidney_right,SCT,123037004,Anatomical Structure,SCT,64033007,Kidney,SCT,24028007,Right,,,,,,,212,126,151,, 4 | kidney_left,SCT,123037004,Anatomical Structure,SCT,64033007,Kidney,SCT,7771000,Left,,,,,,,212,126,151,, 5 | gallbladder,SCT,123037004,Anatomical Structure,SCT,28231008,Gallbladder,,,,,,,,,,139,150,98,, 6 | liver,SCT,123037004,Anatomical Structure,SCT,10200004,Liver,,,,,,,,,,221,130,101,, 7 | stomach,SCT,123037004,Anatomical Structure,SCT,69695003,Stomach,,,,,,,,,,160,139,76,, 8 | aorta,SCT,123037004,Anatomical Structure,SCT,15825003,Aorta,,,,,,,,,,224,97,76,, 9 | inferior_vena_cava,SCT,123037004,Anatomical Structure,SCT,64131007,Inferior vena cava,,,,,,,,,,110,165,54,, 10 | portal_vein_and_splenic_vein,SCT,123037004,Anatomical Structure,SCT,110765007,Portal vein and splenic vein,,,,,,,,,,0,151,206,, 11 | pancreas,SCT,123037004,Anatomical Structure,SCT,15776009,Pancreas,,,,,,,,,,249,180,111,, 12 | adrenal_gland_right,SCT,123037004,Anatomical Structure,SCT,23451007,Adrenal gland,SCT,24028007,Right,,,,,,,199,27,27,, 13 | adrenal_gland_left,SCT,123037004,Anatomical Structure,SCT,23451007,Adrenal gland,SCT,7771000,Left,,,,,,,199,27,27,, 14 | lung_upper_lobe_left,SCT,123037004,Anatomical Structure,SCT,45653009,Upper lobe of lung,SCT,7771000,Left,,,,,,,112,162,95,, 15 | lung_lower_lobe_left,SCT,123037004,Anatomical Structure,SCT,90572001,Lower lobe of lung,SCT,7771000,Left,,,,,,,242,150,232,, 16 | lung_upper_lobe_right,SCT,123037004,Anatomical Structure,SCT,45653009,Upper lobe of lung,SCT,24028007,Right,,,,,,,173,69,44,, 17 | lung_middle_lobe_right,SCT,123037004,Anatomical Structure,SCT,72481006,Middle lobe of right lung,,,,,,,,,,202,164,140,, 18 | lung_lower_lobe_right,SCT,123037004,Anatomical Structure,SCT,90572001,Lower lobe of lung,SCT,24028007,Right,,,,,,,212,191,32,, 19 | vertebrae_L5,SCT,123037004,Anatomical Structure,SCT,49668003,L5 vertebra,,,,,,,,,,117,78,55,, 20 | vertebrae_L4,SCT,123037004,Anatomical Structure,SCT,11994002,L4 vertebra,,,,,,,,,,255,255,128,, 21 | vertebrae_L3,SCT,123037004,Anatomical Structure,SCT,36470004,L3 vertebra,,,,,,,,,,157,126,0,, 22 | vertebrae_L2,SCT,123037004,Anatomical Structure,SCT,14293000,L2 vertebra,,,,,,,,,,255,144,32,, 23 | vertebrae_L1,SCT,123037004,Anatomical Structure,SCT,66794005,L1 vertebra,,,,,,,,,,255,255,1,, 24 | vertebrae_T12,SCT,123037004,Anatomical Structure,SCT,23215003,T12 vertebra,,,,,,,,,,166,89,255,, 25 | vertebrae_T11,SCT,123037004,Anatomical Structure,SCT,12989004,T11 vertebra,,,,,,,,,,16,164,144,, 26 | vertebrae_T10,SCT,123037004,Anatomical Structure,SCT,7610001,T10 vertebra,,,,,,,,,,26,97,228,, 27 | vertebrae_T9,SCT,123037004,Anatomical Structure,SCT,82687006,T9 vertebra,,,,,,,,,,96,188,62,, 28 | vertebrae_T8,SCT,123037004,Anatomical Structure,SCT,11068009,T8 vertebra,,,,,,,,,,164,216,199,, 29 | vertebrae_T7,SCT,123037004,Anatomical Structure,SCT,62487009,T7 vertebra,,,,,,,,,,109,0,145,, 30 | vertebrae_T6,SCT,123037004,Anatomical Structure,SCT,45296009,T6 vertebra,,,,,,,,,,88,119,255,, 31 | vertebrae_T5,SCT,123037004,Anatomical Structure,SCT,56401006,T5 vertebra,,,,,,,,,,8,82,73,, 32 | vertebrae_T4,SCT,123037004,Anatomical Structure,SCT,73071006,T4 vertebra,,,,,,,,,,73,228,202,, 33 | vertebrae_T3,SCT,123037004,Anatomical Structure,SCT,1626008,T3 vertebra,,,,,,,,,,30,128,188,, 34 | vertebrae_T2,SCT,123037004,Anatomical Structure,SCT,53733008,T2 vertebra,,,,,,,,,,141,186,216,, 35 | vertebrae_T1,SCT,123037004,Anatomical Structure,SCT,64864005,T1 vertebra,,,,,,,,,,0,2,145,, 36 | vertebrae_C7,SCT,123037004,Anatomical Structure,SCT,87391001,C7 vertebra,,,,,,,,,,255,0,0,, 37 | vertebrae_C6,SCT,123037004,Anatomical Structure,SCT,36054005,C6 vertebra,,,,,,,,,,253,134,125,, 38 | vertebrae_C5,SCT,123037004,Anatomical Structure,SCT,36978003,C5 vertebra,,,,,,,,,,118,40,9,, 39 | vertebrae_C4,SCT,123037004,Anatomical Structure,SCT,5329002,C4 vertebra,,,,,,,,,,250,14,116,, 40 | vertebrae_C3,SCT,123037004,Anatomical Structure,SCT,113205007,C3 vertebra,,,,,,,,,,167,117,152,, 41 | vertebrae_C2,SCT,123037004,Anatomical Structure,SCT,39976000,C2 vertebra,,,,,,,,,,193,99,36,, 42 | vertebrae_C1,SCT,123037004,Anatomical Structure,SCT,14806007,C1 vertebra,,,,,,,,,,255,186,107,, 43 | esophagus,SCT,123037004,Anatomical Structure,SCT,32849002,Esophagus,,,,,,,,,,211,171,143,, 44 | trachea,SCT,123037004,Anatomical Structure,SCT,44567001,Trachea,,,,,,,,,,182,228,255,, 45 | heart_myocardium,SCT,123037004,Anatomical Structure,SCT,74281007,Myocardium,,,,,,,,,,192,104,88,, 46 | heart_atrium_left,SCT,123037004,Anatomical Structure,SCT,82471001,Left atrium,,,,,,,,,,221,129,37,, 47 | heart_ventricle_left,SCT,123037004,Anatomical Structure,SCT,87878005,Left ventricle of heart,,,,,,,,,,152,55,13,, 48 | heart_atrium_right,SCT,123037004,Anatomical Structure,SCT,73829009,Right atrium,,,,,,,,,,221,129,37,, 49 | heart_ventricle_right,SCT,123037004,Anatomical Structure,SCT,53085002,Right ventricle of heart,,,,,,,,,,181,85,57,, 50 | pulmonary_artery,SCT,123037004,Anatomical Structure,SCT,81040000,Pulmonary artery,,,,,,,,,,0,122,171,, 51 | brain,SCT,123037004,Anatomical Structure,SCT,12738006,Brain,,,,,,,,,,250,250,225,, 52 | iliac_artery_left,SCT,123037004,Anatomical Structure,SCT,73634005,Common iliac artery,SCT,7771000,Left,,,,,,,217,162,48,, 53 | iliac_artery_right,SCT,123037004,Anatomical Structure,SCT,73634005,Common iliac artery,SCT,24028007,Right,,,,,,,217,162,48,, 54 | iliac_vena_left,SCT,123037004,Anatomical Structure,SCT,46027005,Common iliac vein,SCT,7771000,Left,,,,,,,197,174,37,, 55 | iliac_vena_right,SCT,123037004,Anatomical Structure,SCT,46027005,Common iliac vein,SCT,24028007,Right,,,,,,,197,174,37,, 56 | small_bowel,SCT,123037004,Anatomical Structure,SCT,30315005,Small Intestine,,,,,,,,,,205,167,142,, 57 | duodenum,SCT,123037004,Anatomical Structure,SCT,38848004,Duodenum,,,,,,,,,,255,253,229,, 58 | colon,SCT,123037004,Anatomical Structure,SCT,71854001,Colon,,,,,,,,,,204,168,143,, 59 | rib_left_1,SCT,123037004,Anatomical Structure,SCT,48535007,First rib,SCT,7771000,Left,,,,,,,0,2,145,, 60 | rib_left_2,SCT,123037004,Anatomical Structure,SCT,78247007,Second rib,SCT,7771000,Left,,,,,,,141,186,216,, 61 | rib_left_3,SCT,123037004,Anatomical Structure,SCT,25888004,Third rib,SCT,7771000,Left,,,,,,,30,128,188,, 62 | rib_left_4,SCT,123037004,Anatomical Structure,SCT,25523003,Fourth rib,SCT,7771000,Left,,,,,,,73,228,202,, 63 | rib_left_5,SCT,123037004,Anatomical Structure,SCT,15339008,Fifth rib,SCT,7771000,Left,,,,,,,8,82,73,, 64 | rib_left_6,SCT,123037004,Anatomical Structure,SCT,59558009,Sixth rib,SCT,7771000,Left,,,,,,,88,119,255,, 65 | rib_left_7,SCT,123037004,Anatomical Structure,SCT,24915002,Seventh rib,SCT,7771000,Left,,,,,,,109,0,145,, 66 | rib_left_8,SCT,123037004,Anatomical Structure,SCT,5953002,Eighth rib,SCT,7771000,Left,,,,,,,164,216,199,, 67 | rib_left_9,SCT,123037004,Anatomical Structure,SCT,22565002,Ninth rib,SCT,7771000,Left,,,,,,,96,188,62,, 68 | rib_left_10,SCT,123037004,Anatomical Structure,SCT,77644006,Tenth rib,SCT,7771000,Left,,,,,,,26,97,228,, 69 | rib_left_11,SCT,123037004,Anatomical Structure,SCT,58830002,Eleventh rib,SCT,7771000,Left,,,,,,,16,164,144,, 70 | rib_left_12,SCT,123037004,Anatomical Structure,SCT,43993008,Twelfth rib,SCT,7771000,Left,,,,,,,166,89,255,, 71 | rib_right_1,SCT,123037004,Anatomical Structure,SCT,48535007,First rib,SCT,24028007,Right,,,,,,,0,2,145,, 72 | rib_right_2,SCT,123037004,Anatomical Structure,SCT,78247007,Second rib,SCT,24028007,Right,,,,,,,141,186,216,, 73 | rib_right_3,SCT,123037004,Anatomical Structure,SCT,25888004,Third rib,SCT,24028007,Right,,,,,,,30,128,188,, 74 | rib_right_4,SCT,123037004,Anatomical Structure,SCT,25523003,Fourth rib,SCT,24028007,Right,,,,,,,73,228,202,, 75 | rib_right_5,SCT,123037004,Anatomical Structure,SCT,15339008,Fifth rib,SCT,24028007,Right,,,,,,,8,82,73,, 76 | rib_right_6,SCT,123037004,Anatomical Structure,SCT,59558009,Sixth rib,SCT,24028007,Right,,,,,,,88,119,255,, 77 | rib_right_7,SCT,123037004,Anatomical Structure,SCT,24915002,Seventh rib,SCT,24028007,Right,,,,,,,109,0,145,, 78 | rib_right_8,SCT,123037004,Anatomical Structure,SCT,5953002,Eighth rib,SCT,24028007,Right,,,,,,,164,216,199,, 79 | rib_right_9,SCT,123037004,Anatomical Structure,SCT,22565002,Ninth rib,SCT,24028007,Right,,,,,,,96,188,62,, 80 | rib_right_10,SCT,123037004,Anatomical Structure,SCT,77644006,Tenth rib,SCT,24028007,Right,,,,,,,26,97,228,, 81 | rib_right_11,SCT,123037004,Anatomical Structure,SCT,58830002,Eleventh rib,SCT,24028007,Right,,,,,,,16,164,144,, 82 | rib_right_12,SCT,123037004,Anatomical Structure,SCT,43993008,Twelfth rib,SCT,24028007,Right,,,,,,,166,89,255,, 83 | humerus_left,SCT,123037004,Anatomical Structure,SCT,85050009,Humerus,SCT,7771000,Left,,,,,,,95,229,185,, 84 | humerus_right,SCT,123037004,Anatomical Structure,SCT,85050009,Humerus,SCT,24028007,Right,,,,,,,95,229,185,, 85 | scapula_left,SCT,123037004,Anatomical Structure,SCT,79601000,Scapula,SCT,7771000,Left,,,,,,,199,204,131,, 86 | scapula_right,SCT,123037004,Anatomical Structure,SCT,79601000,Scapula,SCT,24028007,Right,,,,,,,199,204,131,, 87 | clavicula_left,SCT,123037004,Anatomical Structure,SCT,51299004,Clavicle,SCT,7771000,Left,,,,,,,241,188,38,, 88 | clavicula_right,SCT,123037004,Anatomical Structure,SCT,51299004,Clavicle,SCT,24028007,Right,,,,,,,241,188,38,, 89 | femur_left,SCT,123037004,Anatomical Structure,SCT,71341001,Femur,SCT,7771000,Left,,,,,,,130,222,207,, 90 | femur_right,SCT,123037004,Anatomical Structure,SCT,71341001,Femur,SCT,24028007,Right,,,,,,,130,222,207,, 91 | hip_left,SCT,123037004,Anatomical Structure,SCT,29836001,Hip,SCT,7771000,Left,,,,,,,241,214,145,, 92 | hip_right,SCT,123037004,Anatomical Structure,SCT,29836001,Hip,SCT,24028007,Right,,,,,,,241,214,145,, 93 | sacrum,SCT,123037004,Anatomical Structure,SCT,54735007,Sacrum,,,,,,,,,,163,140,140,, 94 | face,SCT,123037004,Anatomical Structure,SCT,89545001,Face,,,,,,,,,,255,182,193,, 95 | gluteus_maximus_left,SCT,123037004,Anatomical Structure,SCT,181674001,Gluteus maximus muscle,SCT,7771000,Left,,,,,,,192,104,88,, 96 | gluteus_maximus_right,SCT,123037004,Anatomical Structure,SCT,181674001,Gluteus maximus muscle,SCT,24028007,Right,,,,,,,192,104,88,, 97 | gluteus_medius_left,SCT,123037004,Anatomical Structure,SCT,78333006,Gluteus medius muscle,SCT,7771000,Left,,,,,,,192,104,88,, 98 | gluteus_medius_right,SCT,123037004,Anatomical Structure,SCT,78333006,Gluteus medius muscle,SCT,24028007,Right,,,,,,,192,104,88,, 99 | gluteus_minimus_left,SCT,123037004,Anatomical Structure,SCT,75297007,Gluteus minius muscle,SCT,7771000,Left,,,,,,,192,104,88,, 100 | gluteus_minimus_right,SCT,123037004,Anatomical Structure,SCT,75297007,Gluteus minius muscle,SCT,24028007,Right,,,,,,,192,104,88,, 101 | autochthon_left,SCT,123037004,Anatomical Structure,SCT,244849004,Deep muscle of back,SCT,7771000,Left,,,,,,,192,104,88,, 102 | autochthon_right,SCT,123037004,Anatomical Structure,SCT,244849004,Deep muscle of back,SCT,24028007,Right,,,,,,,192,104,88,, 103 | iliopsoas_left,SCT,123037004,Anatomical Structure,SCT,68455001,Iliopsoas muscle,SCT,7771000,Left,,,,,,,192,104,88,, 104 | iliopsoas_right,SCT,123037004,Anatomical Structure,SCT,68455001,Iliopsoas muscle,SCT,24028007,Right,,,,,,,192,104,88,, 105 | urinary_bladder,SCT,123037004,Anatomical Structure,SCT,89837001,Urinary bladder,,,,,,,,,,222,154,132,, 106 | thyroid_gland,SCT,123037004,Anatomical Structure,SCT,69748006,Thyroid gland,,,,,,,,,,,,,, 107 | prostate,SCT,123037004,Anatomical Structure,SCT,41216001,Prostate,,,,,,,,,,,,,, 108 | kidney_cyst_left,SCT,49755003,Morphologically Altered Structure,SCT,367643001,Cyst,,,,SCT,64033007,Kidney,SCT,7771000,Left,,,,, 109 | kidney_cyst_right,SCT,49755003,Morphologically Altered Structure,SCT,367643001,Cyst,,,,SCT,64033007,Kidney,SCT,24028007,Right,,,,, 110 | vertebrae_S1,SCT,123037004,Anatomical Structure,SCT,65985001,S1 vertebra,,,,,,,,,,,,,, 111 | heart,SCT,123037004,Anatomical Structure,SCT,80891009,Heart,,,,,,,,,,,,,, 112 | pulmonary_vein,SCT,123037004,Anatomical Structure,SCT,122972007,Pulmonary vein,,,,,,,,,,,,,, 113 | brachiocephalic_trunk,SCT,123037004,Anatomical Structure,SCT,12691009,Brachiocephalic artery,,,,,,,,,,,,,, 114 | subclavian_artery_right,SCT,123037004,Anatomical Structure,SCT,29700009,Subclavian artery,SCT,24028007,Right,,,,,,,,,,, 115 | subclavian_artery_left,SCT,123037004,Anatomical Structure,SCT,29700009,Subclavian artery,SCT,7771000,Left,,,,,,,,,,, 116 | common_carotid_artery_right,SCT,123037004,Anatomical Structure,SCT,32062004,Common carotid artery,SCT,24028007,Right,,,,,,,,,,, 117 | common_carotid_artery_left,SCT,123037004,Anatomical Structure,SCT,32062004,Common carotid artery,SCT,7771000,Left,,,,,,,,,,, 118 | brachiocephalic_vein_left,SCT,123037004,Anatomical Structure,SCT,8887007,Brachiocephalic vein,SCT,7771000,Left,,,,,,,,,,, 119 | brachiocephalic_vein_right,SCT,123037004,Anatomical Structure,SCT,8887007,Brachiocephalic vein,SCT,24028007,Right,,,,,,,,,,, 120 | atrial_appendage_left,SCT,123037004,Anatomical Structure,SCT,68786006,Auricular appendage,SCT,7771000,Left,,,,,,,,,,, 121 | superior_vena_cava,SCT,123037004,Anatomical Structure,SCT,48345005,Superior vena cava,,,,,,,,,,,,,, 122 | spinal_cord,SCT,123037004,Anatomical Structure,SCT,2748008,Spinal cord,,,,,,,,,,,,,, 123 | skull,SCT,123037004,Anatomical Structure,SCT,89546000,Skull,,,,,,,,,,,,,, 124 | sternum,SCT,123037004,Anatomical Structure,SCT,56873002,Sternum,,,,,,,,,,,,,, 125 | costal_cartilages,SCT,123037004,Anatomical Structure,SCT,50016007,Costal cartilage,,,,,,,,,,,,,, 126 | lung_left,SCT,123037004,Anatomical Structure,SCT,39607008,Lung,SCT,7771000,Left,,,,,,,,,,, 127 | lung_right,SCT,123037004,Anatomical Structure,SCT,39607008,Lung,SCT,24028007,Right,,,,,,,,,,, 128 | vertebrae,SCT,123037004,Anatomical Structure,SCT,51282000,Vertebra,,,,,,,,,,,,,, 129 | intervertebral_discs,SCT,123037004,Anatomical Structure,SCT,360499006,Intervertebral disc,,,,,,,,,,,,,, 130 | fibula,SCT,123037004,Anatomical Structure,SCT,87342007,Fibula,,,,,,,,,,,,,, 131 | tibia,SCT,123037004,Anatomical Structure,SCT,12611008,Tibia,,,,,,,,,,,,,, 132 | quadriceps_femoris_left,SCT,123037004,Anatomical Structure,SCT,21989003,Quadriceps femoris muscle,,,,,,,,,,,,,, 133 | quadriceps_femoris_right,SCT,123037004,Anatomical Structure,SCT,21989003,Quadriceps femoris muscle,,,,,,,,,,,,,, 134 | thigh_medial_compartment_left,SCT,123037004,Anatomical Structure,SCT,1179657002,Structure of medial muscle of thigh,SCT,7771000,Left,,,,,,,,,,, 135 | thigh_medial_compartment_right,SCT,123037004,Anatomical Structure,SCT,1179657002,Structure of medial muscle of thigh,SCT,24028007,Right,,,,,,,,,,, 136 | thigh_posterior_compartment_left,SCT,123037004,Anatomical Structure,SCT,128511007,Posterior muscle of thigh structure,SCT,7771000,Left,,,,,,,,,,, 137 | thigh_posterior_compartment_right,SCT,123037004,Anatomical Structure,SCT,128511007,Posterior muscle of thigh structure,SCT,24028007,Right,,,,,,,,,,, 138 | sartorius_left,SCT,123037004,Anatomical Structure,SCT,12595009,Sartorius muscle,SCT,7771000,Left,,,,,,,,,,, 139 | sartorius_right,SCT,123037004,Anatomical Structure,SCT,12595009,Sartorius muscle,SCT,24028007,Right,,,,,,,,,,, 140 | lung_vessels,SCT,123037004,Anatomical Structure,SCT,59820001,Blood vessel,,,,SCT,39607008,Lung,,,,,,,, 141 | lung_trachea_bronchia,SCT,123037004,Anatomical Structure,SCT,110726009,Trachea and bronchus,,,,,,,,,,,,,, 142 | lung_covid_infiltrate,SCT,49755003,Morphologically Altered Structure,SCT,47351003,Infiltrate,SCT,840539006,COVID-19,SCT,39607008,Lung,,,,,,,, 143 | intracerebral_hemorrhage,SCT,49755003,Morphologically Altered Structure,SCT,50960005,Hemorrhage,,,,SCT,12738006,Brain,,,,,,,, 144 | hip_implant,SCT,260787004,Physical object,SCT,40388003,Implant,,,,SCT,24136001,Hip joint,,,,,,,, 145 | coronary_arteries,SCT,123037004,Anatomical Structure,SCT,41801008,Coronary artery,,,,,,,,,,,,,, 146 | body_trunc,SCT,123037004,Anatomical Structure,SCT,22943007,Trunk,,,,,,,,,,,,,, 147 | body_extremities,SCT,123037004,Anatomical Structure,SCT,66019005,Limb,,,,,,,,,,,,,, 148 | pleural_effusion,SCT,49755003,Morphologically Altered Structure,SCT,41699000,Effusion,,,,SCT,91381003,Pleural cavity,,,,,,,, 149 | pericardial_effusion,SCT,49755003,Morphologically Altered Structure,SCT,41699000,Effusion,,,,SCT,25489000,Pericardial cavity,,,,,,,, 150 | liver_vessels,SCT,123037004,Anatomical Structure,SCT,59820001,Blood vessel,,,,SCT,10200004,Liver,,,,,,,, 151 | liver_tumor,SCT,49755003,Morphologically Altered Structure,SCT,108369006,Neoplasm,,,,SCT,10200004,Liver,,,,,,,, 152 | vertebrae_body,SCT,123037004,Anatomical Structure,SCT,3572006,Vertebral body,,,,,,,,,,,,,, 153 | patella,SCT,123037004,Anatomical Structure,SCT,64234005,Patella,,,,,,,,,,,,,, 154 | tarsal,SCT,123037004,Anatomical Structure,SCT,108371006,Tarsal bones,,,,,,,,,,,,,, 155 | metatarsal,SCT,123037004,Anatomical Structure,SCT,53884002,Metatarsal,,,,,,,,,,,,,, 156 | phalanges_feet,SCT,123037004,Anatomical Structure,SCT,28641004,Phalanx structure,,,,,,,,,,,,,, 157 | ulna,SCT,123037004,Anatomical Structure,SCT,23416004,Ulna,,,,,,,,,,,,,, 158 | radius,SCT,123037004,Anatomical Structure,SCT,62413002,Radius,,,,,,,,,,,,,, 159 | carpal,SCT,123037004,Anatomical Structure,SCT,83936004,Carpus,,,,,,,,,,,,,, 160 | metacarpal,SCT,123037004,Anatomical Structure,SCT,36455000,Metacarpal bones,,,,,,,,,,,,,, 161 | phalanges_hand,SCT,123037004,Anatomical Structure,SCT,28641004,Phalanx structure,,,,,,,,,,,,,, 162 | humerus,SCT,123037004,Anatomical Structure,SCT,85050009,Humerus,,,,,,,,,,,,,, 163 | femur,SCT,123037004,Anatomical Structure,SCT,71341001,Femur,,,,,,,,,,,,,, 164 | subcutaneous_fat,SCT,85756007,Tissue,SCT,67769002,Subcutaneous fatty tissue,,,,,,,,,,,,,, 165 | torso_fat,SCT,85756007,Tissue,SCT,55603005,Fat,,,,SCT,22943007,Trunk,,,,,,,, 166 | skeletal_muscle,SCT,123037004,Anatomical Structure,SCT,127954009,Skeletal muscle,,,,,,,,,,,,,, 167 | intermuscular_fat,SCT,123037004,Anatomical Structure,SCT,55603005,Fat,,,,SCT,71616004,Muscle,,,,,,,, 168 | medial_pterygoid_right,SCT,123037004,Anatomical Structure,SCT,85002005,Medial pterygoid muscle,SCT,24028007,Right,,,,,,,,,,, 169 | medial_pterygoid_left,SCT,123037004,Anatomical Structure,SCT,85002005,Medial pterygoid muscle,SCT,7771000,Left,,,,,,,,,,, 170 | lateral_pterygoid_right,SCT,123037004,Anatomical Structure,SCT,88938001,Lateral pterygoid muscle,SCT,24028007,Right,,,,,,,,,,, 171 | lateral_pterygoid_left,SCT,123037004,Anatomical Structure,SCT,88938001,Lateral pterygoid muscle,SCT,7771000,Left,,,,,,,,,,, 172 | masseter_right,SCT,123037004,Anatomical Structure,SCT,79368004,Masseter muscle,SCT,24028007,Right,,,,,,,,,,, 173 | masseter_left,SCT,123037004,Anatomical Structure,SCT,79368004,Masseter muscle,SCT,7771000,Left,,,,,,,,,,, 174 | temporalis_right,SCT,123037004,Anatomical Structure,SCT,52927003,Temporalis muscle,SCT,24028007,Right,,,,,,,,,,, 175 | temporalis_left,SCT,123037004,Anatomical Structure,SCT,52927003,Temporalis muscle,SCT,7771000,Left,,,,,,,,,,, 176 | digastric_right,SCT,123037004,Anatomical Structure,SCT,52410001,Digastric muscle,SCT,24028007,Right,,,,,,,,,,, 177 | digastric_left,SCT,123037004,Anatomical Structure,SCT,52410001,Digastric muscle,SCT,7771000,Left,,,,,,,,,,, 178 | tongue,SCT,123037004,Anatomical Structure,SCT,21974007,Tongue,,,,,,,,,,,,,, 179 | nasal_cavity_right,SCT,123037004,Anatomical Structure,SCT,279549004,Nasal cavity,SCT,24028007,Right,,,,,,,,,,, 180 | nasal_cavity_left,SCT,123037004,Anatomical Structure,SCT,279549004,Nasal cavity,SCT,7771000,Left,,,,,,,,,,, 181 | eye_right,SCT,123037004,Anatomical Structure,SCT,1290032005,Right eye,,,,,,,,,,,,,, 182 | eye_left,SCT,123037004,Anatomical Structure,SCT,1290031003,Left eye,,,,,,,,,,,,,, 183 | eye_lens_right,SCT,123037004,Anatomical Structure,SCT,72345007,Lens of right eye,,,,,,,,,,,,,, 184 | eye_lens_left,SCT,123037004,Anatomical Structure,SCT,88258005,Lens of left eye,,,,,,,,,,,,,, 185 | optic_nerve_right,SCT,123037004,Anatomical Structure,SCT,18234004,Optic nerve,SCT,24028007,Right,,,,,,,,,,, 186 | optic_nerve_left,SCT,123037004,Anatomical Structure,SCT,18234004,Optic nerve,SCT,7771000,Left,,,,,,,,,,, 187 | parotid_gland_right,SCT,123037004,Anatomical Structure,SCT,45289007,Parotid gland,,,,,,,,,,,,,, 188 | parotid_gland_left,SCT,123037004,Anatomical Structure,SCT,45289007,Parotid gland,,,,,,,,,,,,,, 189 | submandibular_gland_right,SCT,123037004,Anatomical Structure,SCT,54019009,Submandibular gland,SCT,24028007,Right,,,,,,,,,,, 190 | submandibular_gland_left,SCT,123037004,Anatomical Structure,SCT,54019009,Submandibular gland,SCT,7771000,Left,,,,,,,,,,, 191 | nasopharynx,SCT,123037004,Anatomical Structure,SCT,360955006,Nasopharynx,,,,,,,,,,,,,, 192 | oropharynx,SCT,123037004,Anatomical Structure,SCT,31389004,Oropharynx,,,,,,,,,,,,,, 193 | hypopharynx,SCT,123037004,Anatomical Structure,SCT,81502006,Hypopharynx,,,,,,,,,,,,,, 194 | auditory_canal_right,SCT,123037004,Anatomical Structure,SCT,45124007,Right external auditory canal,,,,,,,,,,,,,, 195 | auditory_canal_left,SCT,123037004,Anatomical Structure,SCT,71471005,Left external auditory canal,,,,,,,,,,,,,, 196 | hard_palate,SCT,123037004,Anatomical Structure,SCT,90228003,Hard palate,,,,,,,,,,,,,, 197 | soft_palate,SCT,123037004,Anatomical Structure,SCT,49460000,Soft palate,,,,,,,,,,,,,, 198 | sternocleidomastoid_right,SCT,123037004,Anatomical Structure,SCT,22823000,Sternocleidomastoid muscle,SCT,24028007,Right,,,,,,,,,,, 199 | sternocleidomastoid_left,SCT,123037004,Anatomical Structure,SCT,22823000,Sternocleidomastoid muscle,SCT,7771000,Left,,,,,,,,,,, 200 | superior_pharyngeal_constrictor,SCT,123037004,Anatomical Structure,SCT,82182001,Superior pharyngeal constrictor muscle,,,,,,,,,,,,,, 201 | middle_pharyngeal_constrictor,SCT,123037004,Anatomical Structure,SCT,66138004,Middle pharyngeal constrictor muscle,,,,,,,,,,,,,, 202 | inferior_pharyngeal_constrictor,SCT,123037004,Anatomical Structure,SCT,80461005,Inferior pharyngeal constrictor muscle,,,,,,,,,,,,,, 203 | trapezius_right,SCT,123037004,Anatomical Structure,SCT,31764008,Trapezius muscle,SCT,24028007,Right,,,,,,,,,,, 204 | trapezius_left,SCT,123037004,Anatomical Structure,SCT,31764008,Trapezius muscle,SCT,7771000,Left,,,,,,,,,,, 205 | platysma_right,SCT,123037004,Anatomical Structure,SCT,18252004,Platysma muscle,SCT,24028007,Right,,,,,,,,,,, 206 | platysma_left,SCT,123037004,Anatomical Structure,SCT,18252004,Platysma muscle,SCT,7771000,Left,,,,,,,,,,, 207 | levator_scapulae_right,SCT,123037004,Anatomical Structure,SCT,11327007,Levator scapulae muscle,SCT,24028007,Right,,,,,,,,,,, 208 | levator_scapulae_left,SCT,123037004,Anatomical Structure,SCT,11327007,Levator scapulae muscle,SCT,7771000,Left,,,,,,,,,,, 209 | anterior_scalene_right,SCT,123037004,Anatomical Structure,SCT,50755001,Scalenus anterior muscle,SCT,24028007,Right,,,,,,,,,,, 210 | anterior_scalene_left,SCT,123037004,Anatomical Structure,SCT,50755001,Scalenus anterior muscle,SCT,7771000,Left,,,,,,,,,,, 211 | middle_scalene_right,SCT,123037004,Anatomical Structure,SCT,38630006,Scalenus medius muscle ,SCT,24028007,Right,,,,,,,,,,, 212 | middle_scalene_left,SCT,123037004,Anatomical Structure,SCT,38630006,Scalenus medius muscle ,SCT,7771000,Left,,,,,,,,,,, 213 | posterior_scalene_right,SCT,123037004,Anatomical Structure,SCT,58162002,Scalenus posterior muscle,SCT,24028007,Right,,,,,,,,,,, 214 | posterior_scalene_left,SCT,123037004,Anatomical Structure,SCT,58162002,Scalenus posterior muscle,SCT,7771000,Left,,,,,,,,,,, 215 | sterno_thyroid_right,SCT,123037004,Anatomical Structure,SCT,1087003,Sternothyroid muscle,SCT,24028007,Right,,,,,,,,,,, 216 | sterno_thyroid_left,SCT,123037004,Anatomical Structure,SCT,1087003,Sternothyroid muscle,SCT,7771000,Left,,,,,,,,,,, 217 | thyrohyoid_right,SCT,123037004,Anatomical Structure,SCT,113226007,Thyrohyoid muscle,SCT,24028007,Right,,,,,,,,,,, 218 | thyrohyoid_left,SCT,123037004,Anatomical Structure,SCT,113226007,Thyrohyoid muscle,SCT,7771000,Left,,,,,,,,,,, 219 | prevertebral_right,SCT,123037004,Anatomical Structure,SCT,714472006,Anterior vertebral muscle of neck,SCT,24028007,Right,,,,,,,,,,, 220 | prevertebral_left,SCT,123037004,Anatomical Structure,SCT,714472006,Anterior vertebral muscle of neck,SCT,7771000,Left,,,,,,,,,,, 221 | ventricle_frontal_horn_right,SCT,123037004,Anatomical Structure,SCT,30399003,Anterior horn of lateral ventricle,SCT,24028007,Right,,,,,,,,,,, 222 | ventricle_frontal_horn_left,SCT,123037004,Anatomical Structure,SCT,30399003,Anterior horn of lateral ventricle,SCT,7771000,Left,,,,,,,,,,, 223 | ventricle_body_right,SCT,123037004,Anatomical Structure,SCT,34803007,Body of lateral ventricle,SCT,24028007,Right,,,,,,,,,,, 224 | ventricle_body_left,SCT,123037004,Anatomical Structure,SCT,34803007,Body of lateral ventricle,SCT,7771000,Left,,,,,,,,,,, 225 | ventricle_temporal_horn_right,SCT,123037004,Anatomical Structure,SCT,53118009,Inferior horn of lateral ventricle,SCT,24028007,Right,,,,,,,,,,, 226 | ventricle_temporal_horn_left,SCT,123037004,Anatomical Structure,SCT,53118009,Inferior horn of lateral ventricle,SCT,7771000,Left,,,,,,,,,,, 227 | ventricle_trigone_right,SCT,123037004,Anatomical Structure,NEU,217,Collateral trigone,SCT,24028007,Right,,,,,,,,,,, 228 | ventricle_trigone_left,SCT,123037004,Anatomical Structure,NEU,217,Collateral trigone,SCT,7771000,Left,,,,,,,,,,, 229 | ventricle_occipital_horn_right,SCT,123037004,Anatomical Structure,SCT,52943005,Posterior horn of lateral ventricle,SCT,24028007,Right,,,,,,,,,,, 230 | ventricle_occipital_horn_left,SCT,123037004,Anatomical Structure,SCT,52943005,Posterior horn of lateral ventricle,SCT,7771000,Left,,,,,,,,,,, 231 | third_ventricle,SCT,123037004,Anatomical Structure,SCT,49841001,Third ventricle,,,,,,,,,,,,,, 232 | fourth_ventricle,SCT,123037004,Anatomical Structure,SCT,35918002,Fourth ventricle,,,,,,,,,,,,,, 233 | brainstem,SCT,123037004,Anatomical Structure,SCT,15926001,Brainstem,,,,,,,,,,,,,, 234 | subarachnoid_space,SCT,123037004,Anatomical Structure,SCT,35951006,Subarachnoid space,,,,,,,,,,,,,, 235 | venous_sinuses,SCT,123037004,Anatomical Structure,SCT,80334009,Venous sinus,,,,,,,,,,,,,, 236 | septum_pellucidum,SCT,123037004,Anatomical Structure,SCT,70146006,Septum pellucidum,,,,,,,,,,,,,, 237 | cerebellum,SCT,123037004,Anatomical Structure,SCT,113305005,Cerebellum,,,,,,,,,,,,,, 238 | caudate_nucleus,SCT,123037004,Anatomical Structure,SCT,11000004,Caudate nucleus,,,,,,,,,,,,,, 239 | lentiform_nucleus,SCT,123037004,Anatomical Structure,SCT,41648007,Lentiform nucleus,,,,,,,,,,,,,, 240 | internal_capsule,SCT,123037004,Anatomical Structure,SCT,85637007,Internal capsule of brain,,,,,,,,,,,,,, 241 | insular_cortex,SCT,123037004,Anatomical Structure,SCT,36169008,Insula,,,,,,,,,,,,,, 242 | ventricle,SCT,123037004,Anatomical Structure,SCT,35764002,Brain ventricle,,,,,,,,,,,,,, 243 | central_sulcus,SCT,123037004,Anatomical Structure,SCT,28294002,Central sulcus,,,,,,,,,,,,,, 244 | frontal_lobe,SCT,123037004,Anatomical Structure,SCT,83251001,Frontal lobe,,,,,,,,,,,,,, 245 | parietal_lobe,SCT,123037004,Anatomical Structure,SCT,16630005,Parietal lobe,,,,,,,,,,,,,, 246 | occipital_lobe,SCT,123037004,Anatomical Structure,SCT,31065004,Occipital lobe,,,,,,,,,,,,,, 247 | temporal_lobe,SCT,123037004,Anatomical Structure,SCT,78277001,Temporal lobe,,,,,,,,,,,,,, 248 | thalamus,SCT,123037004,Anatomical Structure,SCT,119406000,Thalamus,,,,,,,,,,,,,, 249 | left_ventricular_outflow_tract,SCT,123037004,Anatomical Structure,SCT,13418002,Left ventricle outflow tract,,,,,,,,,,,,,, 250 | right_coronary_cusp,SCT,123037004,Anatomical Structure,SCT,45324002,Right cusp of aortic valve,,,,,,,,,,,,,, 251 | left_coronary_cusp,SCT,123037004,Anatomical Structure,SCT,63059004,Left cusp of aortic valve,,,,,,,,,,,,,, 252 | non_coronary_cusp,SCT,123037004,Anatomical Structure,SCT,69374003,Posterior cusp of aortic valve,,,,,,,,,,,,,, 253 | eyeball_right,SCT,123037004,Anatomical Structure,SCT,79652003,Eyeball,SCT,24028007,Right,,,,,,,,,,, 254 | eyeball_left,SCT,123037004,Anatomical Structure,SCT,79652003,Eyeball,SCT,7771000,Left,,,,,,,,,,, 255 | lateral_rectus_muscle_right,SCT,123037004,Anatomical Structure,SCT,31836009,Lateral rectus muscle,SCT,24028007,Right,,,,,,,,,,, 256 | lateral_rectus_muscle_left,SCT,123037004,Anatomical Structure,SCT,31836009,Lateral rectus muscle,SCT,7771000,Left,,,,,,,,,,, 257 | superior_oblique_muscle_right,SCT,123037004,Anatomical Structure,SCT,66041005,Superior oblique muscle,SCT,24028007,Right,,,,,,,,,,, 258 | superior_oblique_muscle_left,SCT,123037004,Anatomical Structure,SCT,66041005,Superior oblique muscle,SCT,7771000,Left,,,,,,,,,,, 259 | levator_palpebrae_superioris_right,SCT,123037004,Anatomical Structure,SCT,58641002,Superior levator palpebrae muscle,SCT,24028007,Right,,,,,,,,,,, 260 | levator_palpebrae_superioris_left,SCT,123037004,Anatomical Structure,SCT,58641002,Superior levator palpebrae muscle,SCT,7771000,Left,,,,,,,,,,, 261 | superior_rectus_muscle_right,SCT,123037004,Anatomical Structure,SCT,5815008,Superior rectus muscle,SCT,24028007,Right,,,,,,,,,,, 262 | superior_rectus_muscle_left,SCT,123037004,Anatomical Structure,SCT,5815008,Superior rectus muscle,SCT,7771000,Left,,,,,,,,,,, 263 | medial_rectus_muscle_right,SCT,123037004,Anatomical Structure,SCT,24937009,Medial rectus muscle,SCT,24028007,Right,,,,,,,,,,, 264 | medial_rectus_muscle_left,SCT,123037004,Anatomical Structure,SCT,24937009,Medial rectus muscle,SCT,7771000,Left,,,,,,,,,,, 265 | inferior_oblique_muscle_right,SCT,123037004,Anatomical Structure,SCT,57516003,Inferior oblique muscle,SCT,24028007,Right,,,,,,,,,,, 266 | inferior_oblique_muscle_left,SCT,123037004,Anatomical Structure,SCT,57516003,Inferior oblique muscle,SCT,7771000,Left,,,,,,,,,,, 267 | inferior_rectus_muscle_right,SCT,123037004,Anatomical Structure,SCT,47869003,Inferior rectus muscle,SCT,24028007,Right,,,,,,,,,,, 268 | inferior_rectus_muscle_left,SCT,123037004,Anatomical Structure,SCT,47869003,Inferior rectus muscle,SCT,7771000,Left,,,,,,,,,,, 269 | deltoid,SCT,123037004,Anatomical Structure,SCT,35259002,Deltoid muscle,,,,,,,,,,,,,, 270 | supraspinatus,SCT,123037004,Anatomical Structure,SCT,6423006,Supraspinatus muscle,,,,,,,,,,,,,, 271 | infraspinatus,SCT,123037004,Anatomical Structure,SCT,72573008,Infraspinatus muscle,,,,,,,,,,,,,, 272 | subscapularis,SCT,123037004,Anatomical Structure,SCT,90588001,Subscapularis muscle,,,,,,,,,,,,,, 273 | coracobrachial,SCT,123037004,Anatomical Structure,SCT,44202005,Coracobrachialis muscle,,,,,,,,,,,,,, 274 | trapezius,SCT,123037004,Anatomical Structure,SCT,31764008,Trapezius muscle,,,,,,,,,,,,,, 275 | pectoralis_minor,SCT,123037004,Anatomical Structure,SCT,18686000,Pectoralis minor muscle,,,,,,,,,,,,,, 276 | serratus_anterior,SCT,123037004,Anatomical Structure,SCT,18346003,Serratus anterior muscle,,,,,,,,,,,,,, 277 | teres_major,SCT,123037004,Anatomical Structure,SCT,1193009,Teres major muscle,,,,,,,,,,,,,, 278 | triceps_brachii,SCT,123037004,Anatomical Structure,SCT,40284004,Triceps brachii muscle,,,,,,,,,,,,,, 279 | larynx_air,SCT,91720002,Body Substance,SCT,15158005,Air,,,,SCT,4596009,Larynx,,,,,,,, 280 | thyroid_cartilage,SCT,123037004,Anatomical Structure,SCT,52940008,Thyroid cartilage,,,,,,,,,,,,,, 281 | hyoid,SCT,123037004,Anatomical Structure,SCT,21387005,Hyoid bone,,,,,,,,,,,,250,210,139 282 | cricoid_cartilage,SCT,123037004,Anatomical Structure,SCT,8600008,Cricoid cartilage,,,,,,,,,,,,,, 283 | zygomatic_arch_right,SCT,123037004,Anatomical Structure,SCT,51204001,Zygomatic arch,SCT,24028007,Right,,,,,,,,,,, 284 | zygomatic_arch_left,SCT,123037004,Anatomical Structure,SCT,51204001,Zygomatic arch,SCT,7771000,Left,,,,,,,,,,, 285 | styloid_process_right,SCT,123037004,Anatomical Structure,SCT,279226000,Styloid process of skull,SCT,24028007,Right,,,,,,,,,,, 286 | styloid_process_left,SCT,123037004,Anatomical Structure,SCT,279226000,Styloid process of skull,SCT,7771000,Left,,,,,,,,,,, 287 | internal_carotid_artery_right,SCT,123037004,Anatomical Structure,SCT,86117002,Internal carotid artery,SCT,24028007,Right,,,,,,,,,,, 288 | internal_carotid_artery_left,SCT,123037004,Anatomical Structure,SCT,86117002,Internal carotid artery,SCT,7771000,Left,,,,,,,,,,, 289 | internal_jugular_vein_right,SCT,123037004,Anatomical Structure,SCT,12123001,Internal jugular vein,SCT,24028007,Right,,,,,,,,,,, 290 | internal_jugular_vein_left,SCT,123037004,Anatomical Structure,SCT,12123001,Internal jugular vein,SCT,7771000,Left,,,,,,,,,,, 291 | liver_segment_1,SCT,123037004,Anatomical Structure,SCT,71133005,Caudate lobe of liver,,,,,,,,,,,,,, 292 | liver_segment_2,SCT,123037004,Anatomical Structure,SCT,277956007,Couinaud hepatic segment II,,,,,,,,,,,,,, 293 | liver_segment_3,SCT,123037004,Anatomical Structure,SCT,277957003,Couinaud hepatic segment III,,,,,,,,,,,,,, 294 | liver_segment_4,SCT,123037004,Anatomical Structure,SCT,277958008,Couinaud hepatic segment IV,,,,,,,,,,,,,, 295 | liver_segment_5,SCT,123037004,Anatomical Structure,SCT,277959000,Couinaud hepatic segment V,,,,,,,,,,,,,, 296 | liver_segment_6,SCT,123037004,Anatomical Structure,SCT,277960005,Couinaud hepatic segment VI,,,,,,,,,,,,,, 297 | liver_segment_7,SCT,123037004,Anatomical Structure,SCT,277961009,Couinaud hepatic segment VII,,,,,,,,,,,,,, 298 | liver_segment_8,SCT,123037004,Anatomical Structure,SCT,277962002,Couinaud hepatic segment VIII,,,,,,,,,,,,,, 299 | lung,SCT,123037004,Anatomical Structure,SCT,39607008,Lung,,,,,,,,,,,,,, 300 | lung_nodules,SCT,123037004,Anatomical Structure,SCT,786838002,Lung nodule,,,,,,,,,,,,,, 301 | breast,SCT,123037004,Anatomical Structure,SCT,76752008,Breast,,,,,,,,,,,,,, 302 | -------------------------------------------------------------------------------- /TotalSegmentator/TotalSegmentator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import re 4 | 5 | import vtk 6 | 7 | import slicer 8 | from slicer.i18n import tr as _ 9 | from slicer.i18n import translate 10 | from slicer.ScriptedLoadableModule import * 11 | from slicer.util import VTKObservationMixin 12 | 13 | 14 | # 15 | # TotalSegmentator 16 | # 17 | # 18 | 19 | class TotalSegmentator(ScriptedLoadableModule): 20 | """Uses ScriptedLoadableModule base class, available at: 21 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 22 | """ 23 | 24 | def __init__(self, parent): 25 | ScriptedLoadableModule.__init__(self, parent) 26 | self.parent.title = _("Total Segmentator") 27 | self.parent.categories = [translate("qSlicerAbstractCoreModule", "Segmentation")] 28 | self.parent.dependencies = [] 29 | self.parent.contributors = ["Andras Lasso (PerkLab, Queen's University)"] 30 | self.parent.helpText = _(""" 31 | 3D Slicer extension for fully automatic whole body CT segmentation using "TotalSegmentator" AI model. 32 | See more information in the extension documentation. 33 | """) 34 | self.parent.acknowledgementText = _(""" 35 | This file was originally developed by Andras Lasso (PerkLab, Queen's University). 36 | The module uses TotalSegmentator. 37 | If you use the TotalSegmentator nn-Unet function from this software in your research, please cite: 38 | Wasserthal J., Meyer M., , Hanns-Christian Breit H.C., Cyriac J., Shan Y., Segeroth, M.: 39 | TotalSegmentator: robust segmentation of 104 anatomical structures in CT images. 40 | https://arxiv.org/abs/2208.05868 41 | """) 42 | slicer.app.connect("startupCompleted()", self.configureDefaultTerminology) 43 | 44 | def configureDefaultTerminology(self): 45 | moduleDir = os.path.dirname(self.parent.path) 46 | totalSegmentatorTerminologyFilePath = os.path.join(moduleDir, 'Resources', 'SegmentationCategoryTypeModifier-TotalSegmentator.term.json') 47 | tlogic = slicer.modules.terminologies.logic() 48 | self.terminologyName = tlogic.LoadTerminologyFromFile(totalSegmentatorTerminologyFilePath) 49 | 50 | # 51 | # TotalSegmentatorWidget 52 | # 53 | 54 | class TotalSegmentatorWidget(ScriptedLoadableModuleWidget, VTKObservationMixin): 55 | """Uses ScriptedLoadableModuleWidget base class, available at: 56 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 57 | """ 58 | 59 | def __init__(self, parent=None): 60 | """ 61 | Called when the user opens the module the first time and the widget is initialized. 62 | """ 63 | ScriptedLoadableModuleWidget.__init__(self, parent) 64 | VTKObservationMixin.__init__(self) # needed for parameter node observation 65 | self.logic = None 66 | self._parameterNode = None 67 | self._updatingGUIFromParameterNode = False 68 | 69 | def setup(self): 70 | """ 71 | Called when the user opens the module the first time and the widget is initialized. 72 | """ 73 | ScriptedLoadableModuleWidget.setup(self) 74 | 75 | # Load widget from .ui file (created by Qt Designer). 76 | # Additional widgets can be instantiated manually and added to self.layout. 77 | uiWidget = slicer.util.loadUI(self.resourcePath('UI/TotalSegmentator.ui')) 78 | self.layout.addWidget(uiWidget) 79 | self.ui = slicer.util.childWidgetVariables(uiWidget) 80 | 81 | # Set scene in MRML widgets. Make sure that in Qt designer the top-level qMRMLWidget's 82 | # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's. 83 | # "setMRMLScene(vtkMRMLScene*)" slot. 84 | uiWidget.setMRMLScene(slicer.mrmlScene) 85 | 86 | # Create logic class. Logic implements all computations that should be possible to run 87 | # in batch mode, without a graphical user interface. 88 | self.logic = TotalSegmentatorLogic() 89 | self.logic.logCallback = self.addLog 90 | 91 | for task in self.logic.tasks: 92 | taskTitle = self.logic.tasks[task]['title'] 93 | if self.logic.isLicenseRequiredForTask(task): 94 | taskTitle = _("{task_title} [license required]").format(task_title=taskTitle) 95 | self.ui.taskComboBox.addItem(taskTitle, task) 96 | 97 | # Connections 98 | 99 | # These connections ensure that we update parameter node when scene is closed 100 | self.addObserver(slicer.mrmlScene, slicer.mrmlScene.StartCloseEvent, self.onSceneStartClose) 101 | self.addObserver(slicer.mrmlScene, slicer.mrmlScene.EndCloseEvent, self.onSceneEndClose) 102 | 103 | # These connections ensure that whenever user changes some settings on the GUI, that is saved in the MRML scene 104 | # (in the selected parameter node). 105 | self.ui.inputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI) 106 | self.ui.fastCheckBox.connect('toggled(bool)', self.updateParameterNodeFromGUI) 107 | self.ui.cpuCheckBox.connect('toggled(bool)', self.updateParameterNodeFromGUI) 108 | self.ui.useStandardSegmentNamesCheckBox.connect('toggled(bool)', self.updateParameterNodeFromGUI) 109 | 110 | 111 | self.ui.taskComboBox.currentTextChanged.connect(self.updateParameterNodeFromGUI) 112 | self.ui.outputSegmentationSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI) 113 | self.ui.outputSegmentationSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.ui.segmentationShow3DButton.setSegmentationNode) 114 | 115 | # Buttons 116 | self.ui.packageInfoUpdateButton.connect('clicked(bool)', self.onPackageInfoUpdate) 117 | self.ui.packageUpgradeButton.connect('clicked(bool)', self.onPackageUpgrade) 118 | self.ui.setLicenseButton.connect('clicked(bool)', self.onSetLicense) 119 | self.ui.applyButton.connect('clicked(bool)', self.onApplyButton) 120 | 121 | # Make sure parameter node is initialized (needed for module reload) 122 | self.initializeParameterNode() 123 | 124 | def cleanup(self): 125 | """ 126 | Called when the application closes and the module widget is destroyed. 127 | """ 128 | self.removeObservers() 129 | 130 | def enter(self): 131 | """ 132 | Called each time the user opens this module. 133 | """ 134 | # Make sure parameter node exists and observed 135 | self.initializeParameterNode() 136 | 137 | def exit(self): 138 | """ 139 | Called each time the user opens a different module. 140 | """ 141 | # Do not react to parameter node changes (GUI wlil be updated when the user enters into the module) 142 | self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) 143 | 144 | def onSceneStartClose(self, caller, event): 145 | """ 146 | Called just before the scene is closed. 147 | """ 148 | # Parameter node will be reset, do not use it anymore 149 | self.setParameterNode(None) 150 | 151 | def onSceneEndClose(self, caller, event): 152 | """ 153 | Called just after the scene is closed. 154 | """ 155 | # If this module is shown while the scene is closed then recreate a new parameter node immediately 156 | if self.parent.isEntered: 157 | self.initializeParameterNode() 158 | 159 | def initializeParameterNode(self): 160 | """ 161 | Ensure parameter node exists and observed. 162 | """ 163 | # Parameter node stores all user choices in parameter values, node selections, etc. 164 | # so that when the scene is saved and reloaded, these settings are restored. 165 | 166 | self.setParameterNode(self.logic.getParameterNode()) 167 | 168 | # Select default input nodes if nothing is selected yet to save a few clicks for the user 169 | if not self._parameterNode.GetNodeReference("InputVolume"): 170 | firstVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode") 171 | if firstVolumeNode: 172 | self._parameterNode.SetNodeReferenceID("InputVolume", firstVolumeNode.GetID()) 173 | 174 | def setParameterNode(self, inputParameterNode): 175 | """ 176 | Set and observe parameter node. 177 | Observation is needed because when the parameter node is changed then the GUI must be updated immediately. 178 | """ 179 | 180 | if inputParameterNode: 181 | self.logic.setDefaultParameters(inputParameterNode) 182 | 183 | # Unobserve previously selected parameter node and add an observer to the newly selected. 184 | # Changes of parameter node are observed so that whenever parameters are changed by a script or any other module 185 | # those are reflected immediately in the GUI. 186 | if self._parameterNode is not None: 187 | self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) 188 | self._parameterNode = inputParameterNode 189 | if self._parameterNode is not None: 190 | self.addObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) 191 | 192 | # Initial GUI update 193 | self.updateGUIFromParameterNode() 194 | 195 | def updateGUIFromParameterNode(self, caller=None, event=None): 196 | """ 197 | This method is called whenever parameter node is changed. 198 | The module GUI is updated to show the current state of the parameter node. 199 | """ 200 | 201 | if self._parameterNode is None or self._updatingGUIFromParameterNode: 202 | return 203 | 204 | # Make sure GUI changes do not call updateParameterNodeFromGUI (it could cause infinite loop) 205 | self._updatingGUIFromParameterNode = True 206 | 207 | # Update node selectors and sliders 208 | self.ui.inputVolumeSelector.setCurrentNode(self._parameterNode.GetNodeReference("InputVolume")) 209 | task = self._parameterNode.GetParameter("Task") 210 | self.ui.taskComboBox.setCurrentIndex(self.ui.taskComboBox.findData(task)) 211 | self.ui.fastCheckBox.checked = self._parameterNode.GetParameter("Fast") == "true" 212 | self.ui.cpuCheckBox.checked = self._parameterNode.GetParameter("CPU") == "true" 213 | self.ui.useStandardSegmentNamesCheckBox.checked = self._parameterNode.GetParameter("UseStandardSegmentNames") == "true" 214 | self.ui.outputSegmentationSelector.setCurrentNode(self._parameterNode.GetNodeReference("OutputSegmentation")) 215 | 216 | # Update buttons states and tooltips 217 | inputVolume = self._parameterNode.GetNodeReference("InputVolume") 218 | if inputVolume: 219 | self.ui.applyButton.toolTip = _("Start segmentation") 220 | self.ui.applyButton.enabled = True 221 | else: 222 | self.ui.applyButton.toolTip = _("Select input volume") 223 | self.ui.applyButton.enabled = False 224 | 225 | if inputVolume: 226 | self.ui.outputSegmentationSelector.baseName = _("{volume_name} segmentation").format(volume_name=inputVolume.GetName()) 227 | 228 | fastModeSupported = self.logic.isFastModeSupportedForTask(task) 229 | self.ui.fastCheckBox.visible = fastModeSupported 230 | self.ui.fastNotAvailableLabel.visible = not fastModeSupported 231 | 232 | # All the GUI updates are done 233 | self._updatingGUIFromParameterNode = False 234 | 235 | def updateParameterNodeFromGUI(self, caller=None, event=None): 236 | """ 237 | This method is called when the user makes any change in the GUI. 238 | The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded). 239 | """ 240 | 241 | if self._parameterNode is None or self._updatingGUIFromParameterNode: 242 | return 243 | 244 | wasModified = self._parameterNode.StartModify() # Modify all properties in a single batch 245 | 246 | self._parameterNode.SetNodeReferenceID("InputVolume", self.ui.inputVolumeSelector.currentNodeID) 247 | self._parameterNode.SetParameter("Task", self.ui.taskComboBox.currentData) 248 | self._parameterNode.SetParameter("Fast", "true" if self.ui.fastCheckBox.checked else "false") 249 | self._parameterNode.SetParameter("CPU", "true" if self.ui.cpuCheckBox.checked else "false") 250 | self._parameterNode.SetParameter("UseStandardSegmentNames", "true" if self.ui.useStandardSegmentNamesCheckBox.checked else "false") 251 | self._parameterNode.SetNodeReferenceID("OutputSegmentation", self.ui.outputSegmentationSelector.currentNodeID) 252 | 253 | self._parameterNode.EndModify(wasModified) 254 | 255 | def addLog(self, text): 256 | """Append text to log window 257 | """ 258 | self.ui.statusLabel.appendPlainText(text) 259 | slicer.app.processEvents() # force update 260 | 261 | def onApplyButton(self): 262 | """ 263 | Run processing when user clicks "Apply" button. 264 | """ 265 | self.ui.statusLabel.plainText = '' 266 | 267 | import qt 268 | 269 | sequenceBrowserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForProxyNode(self.ui.inputVolumeSelector.currentNode()) 270 | if sequenceBrowserNode: 271 | if not slicer.util.confirmYesNoDisplay(_("The input volume you provided are part of a sequence. Do you want to segment all frames of that sequence?")): 272 | sequenceBrowserNode = None 273 | 274 | try: 275 | slicer.app.setOverrideCursor(qt.Qt.WaitCursor) 276 | self.logic.setupPythonRequirements() 277 | slicer.app.restoreOverrideCursor() 278 | except Exception as e: 279 | slicer.app.restoreOverrideCursor() 280 | import traceback 281 | traceback.print_exc() 282 | self.ui.statusLabel.appendPlainText(_("Failed to install Python dependencies:\n{exception}\n").format(exception=e)) 283 | restartRequired = False 284 | if isinstance(e, InstallError): 285 | restartRequired = e.restartRequired 286 | if restartRequired: 287 | self.ui.statusLabel.appendPlainText("\n" + _("Application restart required.")) 288 | if slicer.util.confirmOkCancelDisplay( 289 | _("Application is required to complete installation of required Python packages.\nPress OK to restart."), 290 | _("Confirm application restart"), 291 | detailedText=str(e) 292 | ): 293 | slicer.util.restart() 294 | else: 295 | return 296 | else: 297 | slicer.util.errorDisplay(_("Failed to install required packages.\n\n{exception}").format(exception=e)) 298 | return 299 | 300 | with slicer.util.tryWithErrorDisplay(_("Failed to compute results."), waitCursor=True): 301 | 302 | # Create new segmentation node, if not selected yet 303 | if not self.ui.outputSegmentationSelector.currentNode(): 304 | self.ui.outputSegmentationSelector.addNode() 305 | 306 | self.logic.useStandardSegmentNames = self.ui.useStandardSegmentNamesCheckBox.checked 307 | 308 | # Compute output 309 | self.logic.process(self.ui.inputVolumeSelector.currentNode(), self.ui.outputSegmentationSelector.currentNode(), 310 | self.ui.fastCheckBox.checked, self.ui.cpuCheckBox.checked, self.ui.taskComboBox.currentData, interactive = True, sequenceBrowserNode = sequenceBrowserNode) 311 | 312 | self.ui.statusLabel.appendPlainText("\n" + _("Processing finished.")) 313 | 314 | def onPackageInfoUpdate(self): 315 | self.ui.packageInfoTextBrowser.plainText = '' 316 | with slicer.util.tryWithErrorDisplay(_("Failed to get TotalSegmentator package version information"), waitCursor=True): 317 | self.ui.packageInfoTextBrowser.plainText = self.logic.installedTotalSegmentatorPythonPackageInfo().rstrip() 318 | 319 | def onPackageUpgrade(self): 320 | with slicer.util.tryWithErrorDisplay(_("Failed to upgrade TotalSegmentator"), waitCursor=True): 321 | self.logic.setupPythonRequirements(upgrade=True) 322 | self.onPackageInfoUpdate() 323 | if not slicer.util.confirmOkCancelDisplay(_("This TotalSegmentator update requires a 3D Slicer restart. Press OK to restart.")): 324 | raise ValueError(_("Restart was cancelled.")) 325 | else: 326 | slicer.util.restart() 327 | 328 | def onSetLicense(self): 329 | import qt 330 | licenseText = qt.QInputDialog.getText(slicer.util.mainWindow(), _("Set TotalSegmentator license key"), _("License key:")) 331 | 332 | success = False 333 | with slicer.util.tryWithErrorDisplay(_("Failed to set TotalSegmentator license."), waitCursor=True): 334 | if not licenseText: 335 | raise ValueError(_("License is not specified.")) 336 | self.logic.setupPythonRequirements() 337 | self.logic.setLicense(licenseText) 338 | success = True 339 | 340 | if success: 341 | slicer.util.infoDisplay(_("License key is set. You can now use TotalSegmentator tasks that require a license.")) 342 | 343 | 344 | # 345 | # TotalSegmentatorLogic 346 | # 347 | 348 | class InstallError(Exception): 349 | def __init__(self, message, restartRequired=False): 350 | # Call the base class constructor with the parameters it needs 351 | super().__init__(message) 352 | self.message = message 353 | self.restartRequired = restartRequired 354 | def __str__(self): 355 | return self.message 356 | 357 | class TotalSegmentatorLogic(ScriptedLoadableModuleLogic): 358 | """This class should implement all the actual 359 | computation done by your module. The interface 360 | should be such that other python code can import 361 | this class and make use of the functionality without 362 | requiring an instance of the Widget. 363 | Uses ScriptedLoadableModuleLogic base class, available at: 364 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 365 | """ 366 | 367 | def __init__(self): 368 | """ 369 | Called when the logic class is instantiated. Can be used for initializing member variables. 370 | """ 371 | from collections import OrderedDict 372 | 373 | ScriptedLoadableModuleLogic.__init__(self) 374 | 375 | import sys 376 | if sys.version_info < (3, 12): 377 | # Python 3.9 (Slicer-5.8 and earlier) 378 | self.totalSegmentatorPythonPackageDownloadUrl = "https://github.com/wasserth/TotalSegmentator/archive/25a858672cf9400e34c7421e9635dca23770344b.zip" # latest version (post 2.6.0) as of 2025-02-16 379 | else: 380 | # Python >= 3.12 (Slicer-5.9 and later) 381 | self.totalSegmentatorPythonPackageDownloadUrl = "https://github.com/wasserth/TotalSegmentator/archive/0a1c3c31588487e64ba1d43996f3934afd0e4bb9.zip" # latest version (post 2.9.0) as of 2025-06-20 382 | 383 | # Custom applications can set custom location for weights. 384 | # For example, it could be set to `sysconfig.get_path('scripts')` to have an independent copy of 385 | # the weights for each Slicer installation. However, setting such custom path would result in extra downloads and 386 | # storage space usage if there were multiple Slicer installations on the same computer. 387 | self.totalSegmentatorWeightsPath = None 388 | 389 | self.logCallback = None 390 | self.clearOutputFolder = True 391 | self.useStandardSegmentNames = True 392 | self.pullMaster = False 393 | 394 | # List of property type codes that are specified by in the TotalSegmentator terminology. 395 | # 396 | # # Codes are stored as a list of strings containing coding scheme designator and code value of the property type, 397 | # separated by "^" character. For example "SCT^123456". 398 | # 399 | # If property the code is found in this list then the TotalSegmentator terminology will be used, 400 | # otherwise the DICOM terminology will be used. This is necessary because the DICOM terminology 401 | # does not contain all the necessary items and some items are incomplete (e.g., don't have color or 3D Slicer label). 402 | # 403 | self.totalSegmentatorTerminologyPropertyTypes = [] 404 | 405 | # Map from TotalSegmentator structure name to terminology string. 406 | # Terminology string uses Slicer terminology entry format - see specification at 407 | # https://slicer.readthedocs.io/en/latest/developer_guide/modules/segmentations.html#terminologyentry-tag 408 | self.totalSegmentatorLabelTerminology = {} 409 | 410 | # Segmentation tasks specified by TotalSegmentator 411 | # Ideally, this information should be provided by TotalSegmentator itself. 412 | self.tasks = OrderedDict() 413 | 414 | # Main 415 | self.tasks['total'] = {'title': 'total', 'modalities': ['CT'], 'supportsFast': True, 'supportsFastest': True, 'supportsMultiLabel': True} 416 | self.tasks['total_mr'] = {'title': 'total (MR)', 'modalities': ['MR'], 'supportsFast': True, 'supportsFastest': True, 'supportsMultiLabel': True} 417 | self.tasks['vertebrae_mr'] = {'title': 'vertebrae (MR)', 'modalities': ['MR'], 'description:': 'acrum, vertebrae L1-5, vertebrae T1-12, vertebrae C1-7 (for CT this is part of the `total` task)', 'supportsMultiLabel': True} 418 | self.tasks['lung_nodules'] = {'title': 'lung: nodules', 'modalities': ['CT'], 'description': 'lung, lung_nodules (provided by [BLUEMIND AI](https://bluemind.co/): Fitzjalen R., Aladin M., Nanyan G.) (trained on 1353 subjects, partly from LIDC-IDRI)', 'supportsMultiLabel': True} 419 | self.tasks['lung_vessels'] = {'title': 'lung: vessels'} 420 | 421 | self.tasks['kidney_cysts'] = {'title': 'kidney: cysts', 'modalities': ['CT'], 'description': 'kidney_cyst_left, kidney_cyst_right (strongly improved accuracy compared to kidney_cysts inside of `total` task)', 'supportsMultiLabel': True} 422 | self.tasks['breasts'] = {'title': 'breasts', 'modalities': ['CT'], 'supportsMultiLabel': True} 423 | self.tasks['liver_segments'] = {'title': 'liver: segments', 'modalities': ['CT'], 'description': 'liver_segment_1, liver_segment_2, liver_segment_3, liver_segment_4, liver_segment_5, liver_segment_6, liver_segment_7, liver_segment_8 (Couinaud segments)', 'supportsMultiLabel': True} 424 | self.tasks['liver_segments_mr'] = {'title': 'liver: segments (MR)', 'modalities': ['MR'], 'description': 'liver_segment_1, liver_segment_2, liver_segment_3, liver_segment_4, liver_segment_5, liver_segment_6, liver_segment_7, liver_segment_8 (for MR images) (Couinaud segments)', 'supportsMultiLabel': True} 425 | self.tasks['liver_vessels'] = {'title': 'liver: vessels', 'supportsMultiLabel': True} 426 | 427 | self.tasks['body'] = {'title': 'body', 'supportsFast': True} 428 | self.tasks['body_mr'] = {'title': 'body (MR)', 'modalities': ['MR'], 'description:': 'body_trunc, body_extremities (for MR images)', 'supportsFast': True, 'supportsMultiLabel': True} 429 | 430 | self.tasks['head_glands_cavities'] = {'title': 'head: glands and cavities', 'supportsMultiLabel': True} 431 | self.tasks['head_muscles'] = {'title': 'head: muscles', 'supportsMultiLabel': True} 432 | self.tasks['oculomotor_muscles'] = {'title': 'head: oculomotor muscles', 'modalities': ['CT'], 'description': 'skull, eyeball_right, lateral_rectus_muscle_right, superior_oblique_muscle_right, levator_palpebrae_superioris_right, superior_rectus_muscle_right, medial_rectus_muscle_left, inferior_oblique_muscle_right, inferior_rectus_muscle_right, optic_nerve_left, eyeball_left, lateral_rectus_muscle_left, superior_oblique_muscle_left, levator_palpebrae_superioris_left, superior_rectus_muscle_left, medial_rectus_muscle_right, inferior_oblique_muscle_left, inferior_rectus_muscle_left, optic_nerve_right', 'supportsMultiLabel': True} 433 | self.tasks['headneck_bones_vessels'] = {'title': 'head and neck: bones and vessels', 'supportsMultiLabel': True} 434 | self.tasks['headneck_muscles'] = {'title': 'head and neck: muscles', 'supportsMultiLabel': True} 435 | 436 | # Trained on reduced data set 437 | self.tasks['cerebral_bleed'] = {'title': 'brain: cerebral bleed', 'supportsMultiLabel': True} 438 | self.tasks['hip_implant'] = {'title': 'hip implant', 'supportsMultiLabel': True} 439 | self.tasks['pleural_pericard_effusion'] = {'title': 'heart: pleural and pericardial effusion', 'supportsMultiLabel': True} 440 | 441 | # Requires license 442 | self.tasks['coronary_arteries'] = {'title': 'heart: coronary arteries', 'description': 'coronary_arteries (also works on non-contrast images)', 'supportsMultiLabel': True, 'requiresLicense': True} 443 | self.tasks['vertebrae_body'] = {'title': 'vertebrae body', 'requiresLicense': True} 444 | self.tasks['appendicular_bones'] = {'title': 'appendicular bones', 'supportsMultiLabel': True, 'requiresLicense': True} 445 | self.tasks['appendicular_bones_mr'] = {'title': 'appendicular bones (MR)', 'modalities': ['MR'], 'description': 'patella, tibia, fibula, tarsal, metatarsal, phalanges_feet, ulna, radius (for MR images)', 'supportsMultiLabel': True, 'requiresLicense': True} 446 | self.tasks['tissue_types'] = {'title': 'tissue types', 'supportsMultiLabel': True, 'requiresLicense': True} 447 | self.tasks['tissue_4_types'] = {'title': 'tissue 4 types', 'description': 'subcutaneous_fat, torso_fat, skeletal_muscle, intermuscular_fat (in contrast to `tissue_types` skeletal_muscle is split into two classes: muscle and fat)', 'supportsMultiLabel': True, 'requiresLicense': True} 448 | self.tasks['tissue_types_mr'] = {'title': 'tissue types (MR)', 'modalities': ['MR'], 'supportsMultiLabel': True, 'requiresLicense': True} 449 | self.tasks['heartchambers_highres'] = {'title': 'heart: chambers highres' , 'supportsMultiLabel': True, 'requiresLicense': True} 450 | self.tasks['face'] = {'title': 'face', 'supportsMultiLabel': True, 'requiresLicense': True} 451 | self.tasks['face_mr'] = {'title': 'face (MR)', 'modalities': ['MR'], 'description': 'face_region (for anonymization)', 'supportsMultiLabel': True, 'requiresLicense': True} 452 | self.tasks['brain_structures'] = {'title': 'brain: structures', 'supportsMultiLabel': True, 'requiresLicense': True} 453 | 454 | self.tasks['thigh_shoulder_muscles'] = {'title': 'thigh and shoulder: muscles', 'description': 'quadriceps_femoris_left, quadriceps_femoris_right, thigh_medial_compartment_left, thigh_medial_compartment_right, thigh_posterior_compartment_left, thigh_posterior_compartment_right, sartorius_left, sartorius_right, deltoid, supraspinatus, infraspinatus, subscapularis, coracobrachial, trapezius, pectoralis_minor, serratus_anterior, teres_major, triceps_brachi', 'supportsMultiLabel': True, 'requiresLicense': True} 455 | self.tasks['thigh_shoulder_muscles_mr'] = {'title': 'thigh and shoulder: muscles (MR)', 'description': 'quadriceps_femoris_left, quadriceps_femoris_right, thigh_medial_compartment_left, thigh_medial_compartment_right, thigh_posterior_compartment_left, thigh_posterior_compartment_right, sartorius_left, sartorius_right, deltoid, supraspinatus, infraspinatus, subscapularis, coracobrachial, trapezius, pectoralis_minor, serratus_anterior, teres_major, triceps_brachi (for MR images)', 'supportsMultiLabel': True, 'requiresLicense': True} 456 | 457 | # Experimental 458 | # self.tasks['ventricle_parts'] = {'title': 'ventricle_parts', 'supportsFast': False, 'supportsMultiLabel': True, 'requiresLicense': False} 459 | # self.tasks['aortic_sinuses'] = {'title': 'aortic_sinuses', 'supportsFast': False, 'supportsMultiLabel': True, 'requiresLicense': True} 460 | 461 | self.loadTotalSegmentatorLabelTerminology() 462 | 463 | def loadTotalSegmentatorLabelTerminology(self): 464 | """Load label terminology from totalsegmentator_snomed_mapping.csv file. 465 | Terminology entries are either in DICOM or TotalSegmentator "Segmentation category and type". 466 | """ 467 | 468 | moduleDir = os.path.dirname(slicer.util.getModule('TotalSegmentator').path) 469 | totalSegmentatorTerminologyMappingFilePath = os.path.join(moduleDir, 'Resources', 'totalsegmentator_snomed_mapping.csv') 470 | 471 | terminologiesLogic = slicer.util.getModuleLogic('Terminologies') 472 | totalSegmentatorTerminologyName = "Segmentation category and type - Total Segmentator" 473 | 474 | anatomicalStructureCategory = slicer.vtkSlicerTerminologyCategory() 475 | numberOfCategories = terminologiesLogic.GetNumberOfCategoriesInTerminology(totalSegmentatorTerminologyName) 476 | for i in range(numberOfCategories): 477 | terminologiesLogic.GetNthCategoryInTerminology(totalSegmentatorTerminologyName, i, anatomicalStructureCategory) 478 | if anatomicalStructureCategory.GetCodingSchemeDesignator() == 'SCT' and anatomicalStructureCategory.GetCodeValue() == '123037004': 479 | # Found the (123037004, SCT, "Anatomical Structure") category within DICOM master list 480 | break 481 | 482 | alteredStructureCategory = slicer.vtkSlicerTerminologyCategory() 483 | for i in range(numberOfCategories): 484 | terminologiesLogic.GetNthCategoryInTerminology(totalSegmentatorTerminologyName, i, alteredStructureCategory) 485 | if alteredStructureCategory.GetCodingSchemeDesignator() == 'SCT' and alteredStructureCategory.GetCodeValue() == '49755003': 486 | # Found the (49755003, SCT, "Morphologically Altered Structure") category within DICOM master list 487 | break 488 | 489 | bodySubstanceCategory = slicer.vtkSlicerTerminologyCategory() 490 | for i in range(numberOfCategories): 491 | terminologiesLogic.GetNthCategoryInTerminology(totalSegmentatorTerminologyName, i, bodySubstanceCategory) 492 | if bodySubstanceCategory.GetCodingSchemeDesignator() == 'SCT' and bodySubstanceCategory.GetCodeValue() == '91720002': 493 | # Found the (91720002, SCT, "Body Substance") category within DICOM master list 494 | break 495 | 496 | # Retrieve all property type codes from the TotalSegmentator terminology 497 | self.totalSegmentatorTerminologyPropertyTypes = [] 498 | terminologyType = slicer.vtkSlicerTerminologyType() 499 | numberOfTypes = terminologiesLogic.GetNumberOfTypesInTerminologyCategory(totalSegmentatorTerminologyName, anatomicalStructureCategory) 500 | for i in range(numberOfTypes): 501 | if terminologiesLogic.GetNthTypeInTerminologyCategory(totalSegmentatorTerminologyName, anatomicalStructureCategory, i, terminologyType): 502 | self.totalSegmentatorTerminologyPropertyTypes.append(terminologyType.GetCodingSchemeDesignator() + "^" + terminologyType.GetCodeValue()) 503 | numberOfTypes = terminologiesLogic.GetNumberOfTypesInTerminologyCategory(totalSegmentatorTerminologyName, alteredStructureCategory) 504 | for i in range(numberOfTypes): 505 | if terminologiesLogic.GetNthTypeInTerminologyCategory(totalSegmentatorTerminologyName, alteredStructureCategory, i, terminologyType): 506 | self.totalSegmentatorTerminologyPropertyTypes.append(terminologyType.GetCodingSchemeDesignator() + "^" + terminologyType.GetCodeValue()) 507 | numberOfTypes = terminologiesLogic.GetNumberOfTypesInTerminologyCategory(totalSegmentatorTerminologyName, bodySubstanceCategory) 508 | for i in range(numberOfTypes): 509 | if terminologiesLogic.GetNthTypeInTerminologyCategory(totalSegmentatorTerminologyName, bodySubstanceCategory, i, terminologyType): 510 | self.totalSegmentatorTerminologyPropertyTypes.append(terminologyType.GetCodingSchemeDesignator() + "^" + terminologyType.GetCodeValue()) 511 | 512 | # Helper function to get code string from CSV file row 513 | def getCodeString(field, columnNames, row): 514 | columnValues = [] 515 | for fieldName in ["CodingScheme", "CodeValue", "CodeMeaning"]: 516 | columnIndex = columnNames.index(f"{field}_{fieldName}") 517 | try: 518 | columnValue = row[columnIndex] 519 | except IndexError: 520 | # Probably the line in the CSV file was not terminated by multiple commas (,) 521 | columnValue = '' 522 | columnValues.append(columnValue) 523 | return columnValues 524 | 525 | import csv 526 | with open(totalSegmentatorTerminologyMappingFilePath, "r") as f: 527 | reader = csv.reader(f) 528 | columnNames = next(reader) 529 | data = {} 530 | # Loop through the rows of the csv file 531 | for row in reader: 532 | 533 | # Determine segmentation category (DICOM or TotalSegmentator) 534 | terminologyEntryStrWithoutCategoryName = ( 535 | "~" 536 | # Property category: "SCT^123037004^Anatomical Structure" or "SCT^49755003^Morphologically Altered Structure" 537 | + '^'.join(getCodeString("Category", columnNames, row)) 538 | + '~' 539 | # Property type: "SCT^23451007^Adrenal gland", "SCT^367643001^Cyst", ... 540 | + '^'.join(getCodeString("Type", columnNames, row)) 541 | + '~' 542 | # Property type modifier: "SCT^7771000^Left", ... 543 | + '^'.join(getCodeString("TypeModifier", columnNames, row)) 544 | + '~Anatomic codes - DICOM master list' 545 | + '~' 546 | # Anatomic region (set if category is not anatomical structure): "SCT^64033007^Kidney", ... 547 | + '^'.join(getCodeString("Region", columnNames, row)) 548 | + '~' 549 | # Anatomic region modifier: "SCT^7771000^Left", ... 550 | + '^'.join(getCodeString("RegionModifier", columnNames, row)) 551 | + '|') 552 | terminologyEntry = slicer.vtkSlicerTerminologyEntry() 553 | terminologyPropertyTypeStr = ( # Example: SCT^23451007 554 | row[columnNames.index("Type_CodingScheme")] 555 | + "^" + row[columnNames.index("Type_CodeValue")]) 556 | if terminologyPropertyTypeStr in self.totalSegmentatorTerminologyPropertyTypes: 557 | terminologyEntryStr = "Segmentation category and type - Total Segmentator" + terminologyEntryStrWithoutCategoryName 558 | else: 559 | terminologyEntryStr = "Segmentation category and type - DICOM master list" + terminologyEntryStrWithoutCategoryName 560 | 561 | # Store the terminology string for this structure 562 | totalSegmentatorStructureName = row[columnNames.index("Name")] # TotalSegmentator structure name, such as "adrenal_gland_left" 563 | self.totalSegmentatorLabelTerminology[totalSegmentatorStructureName] = terminologyEntryStr 564 | 565 | 566 | def isFastModeSupportedForTask(self, task): 567 | return (task in self.tasks) and ('supportsFast' in self.tasks[task]) and self.tasks[task]['supportsFast'] 568 | 569 | def isMultiLabelSupportedForTask(self, task): 570 | return (task in self.tasks) and ('supportsMultiLabel' in self.tasks[task]) and self.tasks[task]['supportsMultiLabel'] 571 | 572 | def isLicenseRequiredForTask(self, task): 573 | return (task in self.tasks) and ('requiresLicense' in self.tasks[task]) and self.tasks[task]['requiresLicense'] 574 | 575 | def getSegmentLabelColor(self, terminologyEntryStr): 576 | """Get segment label and color from terminology""" 577 | 578 | def labelColorFromTypeObject(typeObject, typeModifierObject=None): 579 | if typeModifierObject is not None: 580 | if typeModifierObject.GetSlicerLabel(): 581 | # Slicer label is specified for the modifier that includes the full name, use that 582 | label = typeModifierObject.GetSlicerLabel() 583 | else: 584 | # Slicer label is not specified, assemble label from type and modifier 585 | typeLabel = typeObject.GetSlicerLabel() if typeObject.GetSlicerLabel() else typeObject.GetCodeMeaning() 586 | label = f"{typeLabel} {typeModifierObject.GetCodeMeaning()}" 587 | rgb = typeModifierObject.GetRecommendedDisplayRGBValue() 588 | if rgb[0] == 127 and rgb[1] == 127 and rgb[2] == 127: 589 | # Type modifier did not have color specified, try to use the color of the type 590 | rgb = typeObject.GetRecommendedDisplayRGBValue() 591 | return label, (rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0) 592 | label = typeObject.GetSlicerLabel() if typeObject.GetSlicerLabel() else typeObject.GetCodeMeaning() 593 | rgb = typeObject.GetRecommendedDisplayRGBValue() 594 | return label, (rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0) 595 | 596 | tlogic = slicer.modules.terminologies.logic() 597 | 598 | terminologyEntry = slicer.vtkSlicerTerminologyEntry() 599 | if not tlogic.DeserializeTerminologyEntry(terminologyEntryStr, terminologyEntry): 600 | raise RuntimeError(_("Failed to deserialize terminology string: {terminology_entry_str}").format(terminology_entry_str=terminologyEntryStr)) 601 | 602 | numberOfTypes = tlogic.GetNumberOfTypesInTerminologyCategory(terminologyEntry.GetTerminologyContextName(), terminologyEntry.GetCategoryObject()) 603 | foundTerminologyEntry = slicer.vtkSlicerTerminologyEntry() 604 | for typeIndex in range(numberOfTypes): 605 | tlogic.GetNthTypeInTerminologyCategory(terminologyEntry.GetTerminologyContextName(), terminologyEntry.GetCategoryObject(), typeIndex, foundTerminologyEntry.GetTypeObject()) 606 | if terminologyEntry.GetTypeObject().GetCodingSchemeDesignator() != foundTerminologyEntry.GetTypeObject().GetCodingSchemeDesignator(): 607 | continue 608 | if terminologyEntry.GetTypeObject().GetCodeValue() != foundTerminologyEntry.GetTypeObject().GetCodeValue(): 609 | continue 610 | if terminologyEntry.GetTypeModifierObject() and terminologyEntry.GetTypeModifierObject().GetCodeValue(): 611 | # Type has a modifier, get the color from there 612 | numberOfModifiers = tlogic.GetNumberOfTypeModifiersInTerminologyType(terminologyEntry.GetTerminologyContextName(), terminologyEntry.GetCategoryObject(), terminologyEntry.GetTypeObject()) 613 | foundMatchingModifier = False 614 | for modifierIndex in range(numberOfModifiers): 615 | tlogic.GetNthTypeModifierInTerminologyType(terminologyEntry.GetTerminologyContextName(), terminologyEntry.GetCategoryObject(), terminologyEntry.GetTypeObject(), 616 | modifierIndex, foundTerminologyEntry.GetTypeModifierObject()) 617 | if terminologyEntry.GetTypeModifierObject().GetCodingSchemeDesignator() != foundTerminologyEntry.GetTypeModifierObject().GetCodingSchemeDesignator(): 618 | continue 619 | if terminologyEntry.GetTypeModifierObject().GetCodeValue() != foundTerminologyEntry.GetTypeModifierObject().GetCodeValue(): 620 | continue 621 | return labelColorFromTypeObject(foundTerminologyEntry.GetTypeObject(), foundTerminologyEntry.GetTypeModifierObject()) 622 | continue 623 | return labelColorFromTypeObject(foundTerminologyEntry.GetTypeObject()) 624 | 625 | raise RuntimeError(f"Color was not found for terminology {terminologyEntryStr}") 626 | 627 | def log(self, text): 628 | logging.info(text) 629 | if self.logCallback: 630 | self.logCallback(text) 631 | 632 | def installedTotalSegmentatorPythonPackageDownloadUrl(self): 633 | """Get package download URL of the installed TotalSegmentator Python package""" 634 | import importlib.metadata 635 | import json 636 | try: 637 | metadataPath = [p for p in importlib.metadata.files('TotalSegmentator') if 'direct_url.json' in str(p)][0] 638 | with open(metadataPath.locate()) as json_file: 639 | data = json.load(json_file) 640 | return data['url'] 641 | except: 642 | # Failed to get version information, probably not installed from download URL 643 | return None 644 | 645 | def installedTotalSegmentatorPythonPackageInfo(self): 646 | import shutil 647 | import subprocess 648 | versionInfo = subprocess.check_output([shutil.which('PythonSlicer'), "-m", "pip", "show", "TotalSegmentator"]).decode() 649 | 650 | # Get download URL, as the version information does not contain the github hash 651 | downloadUrl = self.installedTotalSegmentatorPythonPackageDownloadUrl() 652 | if downloadUrl: 653 | versionInfo += "Download URL: " + downloadUrl 654 | 655 | return versionInfo 656 | 657 | def simpleITKPythonPackageVersion(self): 658 | """Utility function to get version of currently installed SimpleITK. 659 | Currently not used, but it can be useful for diagnostic purposes. 660 | """ 661 | 662 | import shutil 663 | import subprocess 664 | versionInfo = subprocess.check_output([shutil.which('PythonSlicer'), "-m", "pip", "show", "SimpleITK"]).decode() 665 | 666 | # versionInfo looks something like this: 667 | # 668 | # Name: SimpleITK 669 | # Version: 2.2.0rc2.dev368 670 | # Summary: SimpleITK is a simplified interface to the Insight Toolkit (ITK) for image registration and segmentation 671 | # ... 672 | # 673 | 674 | # Get version string (second half of the second line): 675 | version = versionInfo.split('\n')[1].split(' ')[1].strip() 676 | return version 677 | 678 | def pipInstallSelective(self, packageToInstall, installCommand, packagesToSkip): 679 | """Installs a Python package, skipping a list of packages. 680 | Return the list of skipped requirements (package name with version requirement). 681 | """ 682 | slicer.util.pip_install(f"{installCommand} --no-deps") 683 | skippedRequirements = [] # list of all missed packages and their version 684 | 685 | # Get path to site-packages\nnunetv2-2.2.dist-info\METADATA 686 | import importlib.metadata 687 | metadataPath = [p for p in importlib.metadata.files(packageToInstall) if 'METADATA' in str(p)][0] 688 | metadataPath.locate() 689 | 690 | # Remove line: `Requires-Dist: SimpleITK (==2.0.2)` 691 | # User Latin-1 encoding to read the file, as it may contain non-ASCII characters and not necessarily in UTF-8 encoding. 692 | filteredMetadata = "" 693 | with open(metadataPath.locate(), "r+", encoding="latin1") as file: 694 | for line in file: 695 | skipThisPackage = False 696 | requirementPrefix = 'Requires-Dist: ' 697 | if line.startswith(requirementPrefix): 698 | for packageToSkip in packagesToSkip: 699 | if packageToSkip in line: 700 | skipThisPackage = True 701 | break 702 | if skipThisPackage: 703 | # skip SimpleITK requirement 704 | skippedRequirements.append(line.removeprefix(requirementPrefix)) 705 | continue 706 | filteredMetadata += line 707 | # Update file content with filtered result 708 | file.seek(0) 709 | file.write(filteredMetadata) 710 | file.truncate() 711 | 712 | # Install all dependencies but the ones listed in packagesToSkip 713 | import importlib.metadata 714 | requirements = importlib.metadata.requires(packageToInstall) 715 | for requirement in requirements: 716 | skipThisPackage = False 717 | for packageToSkip in packagesToSkip: 718 | if requirement.startswith(packageToSkip): 719 | # Do not install 720 | skipThisPackage = True 721 | break 722 | 723 | match = False 724 | if not match: 725 | # Rewrite optional depdendencies info returned by importlib.metadata.requires to be valid for pip_install: 726 | # Requirement Original: ruff; extra == "dev" 727 | # Requirement Rewritten: ruff 728 | match = re.match(r"([\S]+)[\s]*; extra == \"([^\"]+)\"", requirement) 729 | if match: 730 | requirement = f"{match.group(1)}" 731 | if not match: 732 | # nibabel >=2.3.0 -> rewrite to: nibabel>=2.3.0 733 | match = re.match(r"([\S]+)[\s](.+)", requirement) 734 | if match: 735 | requirement = f"{match.group(1)}{match.group(2)}" 736 | 737 | if skipThisPackage: 738 | self.log(_('- Skip {requirement}').format(requirement=requirement)) 739 | else: 740 | self.log(_('- Installing {requirement}...').format(requirement=requirement)) 741 | slicer.util.pip_install(requirement) 742 | 743 | return skippedRequirements 744 | 745 | def setupPythonRequirements(self, upgrade=False): 746 | import importlib.metadata 747 | import importlib.util 748 | import packaging 749 | 750 | # TotalSegmentator requires this, yet it is not listed among its dependencies 751 | try: 752 | import pandas 753 | except ModuleNotFoundError as e: 754 | slicer.util.pip_install("pandas") 755 | 756 | # TotalSegmentator requires dicom2nifti (we don't use any DICOM features in Slicer but DICOM support is not optional in TotalSegmentator) 757 | # but latest dicom2nifti is broken on Python-3.9. We need to install an older version. 758 | # (dicom2nifti was recently updated to version 2.6. This version needs pydicom >= 3.0.0, which requires python >= 3.10) 759 | try: 760 | import dicom2nifti 761 | except ModuleNotFoundError as e: 762 | slicer.util.pip_install("dicom2nifti<=2.5.1") 763 | 764 | # These packages come preinstalled with Slicer and should remain unchanged 765 | packagesToSkip = [ 766 | 'SimpleITK', # Slicer's SimpleITK uses a special IO class, which should not be replaced 767 | 'torch', # needs special installation using SlicerPyTorch 768 | 'nnunetv2', # needs special installation using SlicerNNUNet 769 | 'requests', # TotalSegmentator would want to force a specific version of requests, which would require a restart of Slicer and it is unnecessary 770 | 'rt_utils', # Only needed for RTSTRUCT export, which is not needed in Slicer; rt_utils depends on opencv-python which is hard to build 771 | 'dicom2nifti', # We already installed a known working version, do not let TotalSegmentator to upgrade to a newer version that may not work on Python-3.9 772 | ] 773 | 774 | # Ask for confirmation before installing PyTorch and nnUNet 775 | confirmPackagesToInstall = [] 776 | 777 | try: 778 | import PyTorchUtils 779 | except ModuleNotFoundError as e: 780 | raise InstallError("This module requires PyTorch extension. Install it from the Extensions Manager.") 781 | 782 | minimumTorchVersion = "2.0.0" # per https://github.com/wasserth/TotalSegmentator/blob/7274faac4673298d17b63a5a8335006f02e6d426/setup.py#L19 783 | torchLogic = PyTorchUtils.PyTorchUtilsLogic() 784 | if not torchLogic.torchInstalled(): 785 | confirmPackagesToInstall.append("PyTorch") 786 | 787 | try: 788 | import SlicerNNUNetLib 789 | except ModuleNotFoundError as e: 790 | raise InstallError("This module requires SlicerNNUNet extension. Install it from the Extensions Manager.") 791 | 792 | minimumNNUNetVersion = "2.2.1" # per https://github.com/wasserth/TotalSegmentator/blob/7274faac4673298d17b63a5a8335006f02e6d426/setup.py#L26 793 | nnunetlogic = SlicerNNUNetLib.InstallLogic(doAskConfirmation=False) 794 | nnunetlogic.getInstalledNNUnetVersion() 795 | from packaging.requirements import Requirement 796 | if not nnunetlogic.isPackageInstalled(Requirement("nnunetv2")): 797 | confirmPackagesToInstall.append("nnunetv2") 798 | 799 | if confirmPackagesToInstall: 800 | if not slicer.util.confirmOkCancelDisplay( 801 | _("This module requires installation of additional Python packages. Installation needs network connection and may take several minutes. Click OK to proceed."), 802 | _("Confirm Python package installation"), 803 | detailedText=_("Python packages that will be installed: {package_list}").format(package_list=', '.join(confirmPackagesToInstall)) 804 | ): 805 | raise InstallError("User cancelled.") 806 | 807 | # Install PyTorch 808 | if "PyTorch" in confirmPackagesToInstall: 809 | self.log(_('PyTorch Python package is required. Installing... (it may take several minutes)')) 810 | torch = torchLogic.installTorch(askConfirmation=False, torchVersionRequirement = f">={minimumTorchVersion}") 811 | if torch is None: 812 | raise InstallError("This module requires PyTorch extension. Install it from the Extensions Manager.") 813 | else: 814 | # torch is installed, check version 815 | from packaging import version 816 | if version.parse(torchLogic.torch.__version__) < version.parse(minimumTorchVersion): 817 | raise InstallError(f'PyTorch version {torchLogic.torch.__version__} is not compatible with this module.' 818 | + f' Minimum required version is {minimumTorchVersion}. You can use "PyTorch Util" module to install PyTorch' 819 | + f' with version requirement set to: >={minimumTorchVersion}') 820 | 821 | # Install nnUNet 822 | if "nnunetv2" in confirmPackagesToInstall: 823 | self.log(_('nnunetv2 package is required. Installing... (it may take several minutes)')) 824 | nnunet = nnunetlogic.setupPythonRequirements(f"nnunetv2>={minimumNNUNetVersion}") 825 | if not nnunet: 826 | raise InstallError("This module requires SlicerNNUNet extension. Install it from the Extensions Manager.") 827 | else: 828 | installed_nnunet_version = nnunetlogic.getInstalledNNUnetVersion() 829 | if installed_nnunet_version < version.parse(minimumNNUNetVersion): 830 | raise InstallError(f'nnUNetv2 version {installed_nnunet_version} is not compatible with this module.' 831 | + f' Minimum required version is {minimumNNUNetVersion}. You can use "nnUNet" module to install nnUNet' 832 | + f' with version requirement set to: >={minimumNNUNetVersion}') 833 | 834 | # Install TotalSegmentator with selected dependencies only 835 | # (it would replace Slicer's "requests") 836 | needToInstallSegmenter = False 837 | try: 838 | import totalsegmentator 839 | if not upgrade: 840 | # Check if we need to update TotalSegmentator Python package version 841 | downloadUrl = self.installedTotalSegmentatorPythonPackageDownloadUrl() 842 | if downloadUrl and (downloadUrl != self.totalSegmentatorPythonPackageDownloadUrl): 843 | # TotalSegmentator have been already installed from GitHub, from a different URL that this module needs 844 | if not slicer.util.confirmOkCancelDisplay( 845 | _("This module requires TotalSegmentator Python package update."), 846 | detailedText=_("Currently installed: {downloadUrl}\n\nRequired: {requiredUrl}").format(downloadUrl=downloadUrl, requiredUrl=self.totalSegmentatorPythonPackageDownloadUrl)): 847 | raise ValueError('TotalSegmentator update was cancelled.') 848 | upgrade = True 849 | except ModuleNotFoundError as e: 850 | needToInstallSegmenter = True 851 | 852 | if needToInstallSegmenter or upgrade: 853 | self.log(_('TotalSegmentator Python package is required. Installing it from {downloadUrl}... (it may take several minutes)').format(downloadUrl=self.totalSegmentatorPythonPackageDownloadUrl)) 854 | 855 | if upgrade: 856 | # TotalSegmentator version information is usually not updated with each git revision, therefore we must uninstall it to force the upgrade 857 | slicer.util.pip_uninstall("TotalSegmentator") 858 | 859 | # Update TotalSegmentator and all its dependencies 860 | self.pipInstallSelective( 861 | "TotalSegmentator", 862 | self.totalSegmentatorPythonPackageDownloadUrl + (" --upgrade" if upgrade else ""), 863 | packagesToSkip) 864 | 865 | self.log(_('TotalSegmentator installation completed successfully.')) 866 | 867 | 868 | def setDefaultParameters(self, parameterNode): 869 | """ 870 | Initialize parameter node with default settings. 871 | """ 872 | if not parameterNode.GetParameter("Fast"): 873 | parameterNode.SetParameter("Fast", "True") 874 | if not parameterNode.GetParameter("Task"): 875 | parameterNode.SetParameter("Task", "total") 876 | if not parameterNode.GetParameter("UseStandardSegmentNames"): 877 | parameterNode.SetParameter("UseStandardSegmentNames", "true") 878 | 879 | def logProcessOutput(self, proc, returnOutput=False): 880 | # Wait for the process to end and forward output to the log 881 | output = "" 882 | from subprocess import CalledProcessError 883 | while True: 884 | try: 885 | line = proc.stdout.readline() 886 | if not line: 887 | break 888 | if returnOutput: 889 | output += line 890 | self.log(line.rstrip()) 891 | except UnicodeDecodeError as e: 892 | # Code page conversion happens because `universal_newlines=True` sets process output to text mode, 893 | # and it fails because probably system locale is not UTF8. We just ignore the error and discard the string, 894 | # as we only guarantee correct behavior if an UTF8 locale is used. 895 | pass 896 | 897 | proc.wait() 898 | retcode = proc.returncode 899 | if retcode != 0: 900 | raise CalledProcessError(retcode, proc.args, output=proc.stdout, stderr=proc.stderr) 901 | return output if returnOutput else None 902 | 903 | 904 | def check_zip_extension(self, file_path): 905 | _, ext = os.path.splitext(file_path) 906 | 907 | if ext.lower() != '.zip': 908 | raise ValueError(f"The selected file '{file_path}' is not a .zip file!") 909 | 910 | @staticmethod 911 | def executableName(name): 912 | return name + ".exe" if os.name == "nt" else name 913 | 914 | def setLicense(self, licenseStr): 915 | 916 | """ 917 | Import weights. 918 | Weights are provided in ZIP format. 919 | This function can be used without GUI widget. 920 | """ 921 | 922 | # Get totalseg_import_weights command 923 | # totalseg_import_weights (.py file, without extension) is installed in Python Scripts folder 924 | 925 | if not licenseStr: 926 | raise ValueError(f"The license string is empty.") 927 | 928 | self.log(_('Setting license...')) 929 | 930 | # Get Python executable path 931 | import shutil 932 | pythonSlicerExecutablePath = shutil.which('PythonSlicer') 933 | if not pythonSlicerExecutablePath: 934 | raise RuntimeError("Python was not found") 935 | 936 | # Get arguments 937 | import sysconfig 938 | totalSegmentatorLicenseToolExecutablePath = os.path.join(sysconfig.get_path('scripts'), TotalSegmentatorLogic.executableName("totalseg_set_license")) 939 | cmd = [pythonSlicerExecutablePath, totalSegmentatorLicenseToolExecutablePath, "-l", licenseStr] 940 | 941 | # Launch command 942 | logging.debug(f"Launch TotalSegmentator license tool: {cmd}") 943 | proc = slicer.util.launchConsoleProcess(cmd) 944 | licenseToolOutput = self.logProcessOutput(proc, returnOutput=True) 945 | if "ERROR: Invalid license number" in licenseToolOutput: 946 | raise ValueError('Invalid license number. Please check your license number or contact support.') 947 | 948 | self.log(_('License has been successfully set.')) 949 | 950 | if slicer.util.confirmOkCancelDisplay(_("This license update requires a 3D Slicer restart. Press OK to restart.")): 951 | slicer.util.restart() 952 | else: 953 | raise ValueError('Restart was cancelled.') 954 | 955 | 956 | def process(self, inputVolume, outputSegmentation, fast=True, cpu=False, task=None, subset=None, interactive=False, sequenceBrowserNode=None): 957 | """ 958 | Run the processing algorithm on a volume or a sequence of volumes. 959 | Can be used without GUI widget. 960 | :param inputVolume: volume to be thresholded 961 | :param outputVolume: thresholding result 962 | :param fast: faster and less accurate output 963 | :param task: one of self.tasks, default is "total" 964 | :param subset: a list of structures (TotalSegmentator classe names https://github.com/wasserth/TotalSegmentator#class-detailsTotalSegmentator) to segment. 965 | Default is None, which means that all available structures will be segmented." 966 | :param interactive: set to True to enable warning popups to be shown to users 967 | :param sequenceBrowserNode: if specified then all frames of the inputVolume sequence will be segmented 968 | """ 969 | 970 | if not inputVolume: 971 | raise ValueError("Input or output volume is invalid") 972 | 973 | if task == None and not subset: 974 | task = "total" 975 | 976 | import time 977 | startTime = time.time() 978 | self.log(_('Processing started')) 979 | 980 | if self.totalSegmentatorWeightsPath: 981 | os.environ["TOTALSEG_WEIGHTS_PATH"] = self.totalSegmentatorWeightsPath 982 | 983 | # Create new empty folder 984 | tempFolder = slicer.util.tempDirectory() 985 | 986 | inputFile = tempFolder+"/total-segmentator-input.nii" 987 | outputSegmentationFolder = tempFolder + "/segmentation" 988 | # print (outputSegmentationFolder) 989 | outputSegmentationFile = tempFolder + "/segmentation.nii" 990 | 991 | # Recommend the user to switch to fast mode if no GPU or not enough memory is available 992 | import torch 993 | 994 | cuda = torch.cuda if torch.backends.cuda.is_built() and torch.cuda.is_available() else None 995 | 996 | if not fast and not cuda and interactive: 997 | 998 | import ctk 999 | import qt 1000 | mbox = ctk.ctkMessageBox(slicer.util.mainWindow()) 1001 | mbox.text = _("No GPU is detected. Switch to 'fast' mode to get low-resolution result in a few minutes or compute full-resolution result which may take 5 to 50 minutes (depending on computer configuration)?") 1002 | mbox.addButton(_("Fast (~2 minutes)"), qt.QMessageBox.AcceptRole) 1003 | mbox.addButton(_("Full-resolution (~5 to 50 minutes)"), qt.QMessageBox.RejectRole) 1004 | # Windows 10 peek feature in taskbar shows all hidden but not destroyed windows 1005 | # (after creating and closing a messagebox, hovering over the mouse on Slicer icon, moving up the 1006 | # mouse to the peek thumbnail would show it again). 1007 | mbox.deleteLater() 1008 | fast = (mbox.exec_() == qt.QMessageBox.AcceptRole) 1009 | 1010 | if not fast and cuda and cuda.get_device_properties(cuda.current_device()).total_memory < 7e9 and interactive: 1011 | if slicer.util.confirmYesNoDisplay(_("You have less than 7 GB of GPU memory available. Enable 'fast' mode to ensure segmentation can be completed successfully?")): 1012 | fast = True 1013 | 1014 | # Get TotalSegmentator launcher command 1015 | # TotalSegmentator (.py file, without extension) is installed in Python Scripts folder 1016 | import sysconfig 1017 | totalSegmentatorExecutablePath = os.path.join(sysconfig.get_path('scripts'), TotalSegmentatorLogic.executableName("TotalSegmentator")) 1018 | # Get Python executable path 1019 | import shutil 1020 | pythonSlicerExecutablePath = shutil.which('PythonSlicer') 1021 | if not pythonSlicerExecutablePath: 1022 | raise RuntimeError("Python was not found") 1023 | totalSegmentatorCommand = [ pythonSlicerExecutablePath, totalSegmentatorExecutablePath] 1024 | 1025 | inputVolumeSequence = None 1026 | if sequenceBrowserNode: 1027 | inputVolumeSequence = sequenceBrowserNode.GetSequenceNode(inputVolume) 1028 | 1029 | if inputVolumeSequence is not None: 1030 | 1031 | # If the volume already has a sequence in the current browser node then use that 1032 | segmentationSequence = sequenceBrowserNode.GetSequenceNode(outputSegmentation) 1033 | if not segmentationSequence: 1034 | segmentationSequence = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceNode", outputSegmentation.GetName()) 1035 | sequenceBrowserNode.AddProxyNode(outputSegmentation, segmentationSequence, False) 1036 | 1037 | selectedItemNumber = sequenceBrowserNode.GetSelectedItemNumber() 1038 | sequenceBrowserNode.PlaybackActiveOff() 1039 | sequenceBrowserNode.SelectFirstItem() 1040 | sequenceBrowserNode.SetRecording(segmentationSequence, True) 1041 | sequenceBrowserNode.SetSaveChanges(segmentationSequence, True) 1042 | 1043 | numberOfItems = sequenceBrowserNode.GetNumberOfItems() 1044 | for i in range(numberOfItems): 1045 | self.log(f"Segmenting item {i+1}/{numberOfItems} of sequence") 1046 | self.processVolume(inputFile, inputVolume, 1047 | outputSegmentationFolder, outputSegmentation, outputSegmentationFile, 1048 | task, subset, cpu, totalSegmentatorCommand, fast) 1049 | sequenceBrowserNode.SelectNextItem() 1050 | sequenceBrowserNode.SetSelectedItemNumber(selectedItemNumber) 1051 | 1052 | else: 1053 | # Segment a single volume 1054 | self.processVolume(inputFile, inputVolume, 1055 | outputSegmentationFolder, outputSegmentation, outputSegmentationFile, 1056 | task, subset, cpu, totalSegmentatorCommand, fast) 1057 | 1058 | stopTime = time.time() 1059 | self.log(_("Processing completed in {time_elapsed:.2f} seconds").format(time_elapsed=stopTime-startTime)) 1060 | 1061 | if self.clearOutputFolder: 1062 | self.log(_("Cleaning up temporary folder...")) 1063 | if os.path.isdir(tempFolder): 1064 | shutil.rmtree(tempFolder) 1065 | else: 1066 | self.log(_("Not cleaning up temporary folder: {temp_folder}").format(temp_folder=tempFolder)) 1067 | 1068 | def processVolume(self, inputFile, inputVolume, outputSegmentationFolder, outputSegmentation, outputSegmentationFile, task, subset, cpu, totalSegmentatorCommand, fast): 1069 | """Segment a single volume 1070 | """ 1071 | 1072 | # Write input volume to file 1073 | # TotalSegmentator requires NIFTI 1074 | self.log(_("Writing input file to {input_file}").format(input_file=inputFile)) 1075 | volumeStorageNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLVolumeArchetypeStorageNode") 1076 | volumeStorageNode.SetFileName(inputFile) 1077 | volumeStorageNode.UseCompressionOff() 1078 | volumeStorageNode.WriteData(inputVolume) 1079 | volumeStorageNode.UnRegister(None) 1080 | 1081 | # Get options 1082 | options = ["-i", inputFile, "-o", outputSegmentationFolder] 1083 | 1084 | # Launch TotalSegmentator in fast mode to get initial segmentation, if needed 1085 | 1086 | #options.extend(["--nr_thr_saving", "1"]) 1087 | #options.append("--force_split") 1088 | 1089 | # Launch TotalSegmentator 1090 | 1091 | # When there are many segments then reading each segment from a separate file would be too slow, 1092 | # but we need to do it for some specialized models. 1093 | multilabel = self.isMultiLabelSupportedForTask(task) 1094 | 1095 | # some tasks do not support fast mode 1096 | if not self.isFastModeSupportedForTask(task): 1097 | fast = False 1098 | 1099 | if multilabel: 1100 | options.append("--ml") 1101 | if task: 1102 | options.extend(["--task", task]) 1103 | if fast: 1104 | options.append("--fast") 1105 | if cpu: 1106 | options.extend(["--device", "cpu"]) 1107 | if subset: 1108 | options.append("--roi_subset") 1109 | # append each item of the subset 1110 | for item in subset: 1111 | try: 1112 | if self.totalSegmentatorLabelTerminology[item]: 1113 | options.append(item) 1114 | except: 1115 | # Failed to get terminology info, item probably misspelled 1116 | raise ValueError("'" + item + "' is not a valid TotalSegmentator label terminology.") 1117 | 1118 | self.log(_('Creating segmentations with TotalSegmentator AI...')) 1119 | self.log(_("Total Segmentator arguments: {options}").format(options=options)) 1120 | proc = slicer.util.launchConsoleProcess(totalSegmentatorCommand + options) 1121 | self.logProcessOutput(proc) 1122 | 1123 | # Load result 1124 | self.log(_('Importing segmentation results...')) 1125 | if multilabel: 1126 | self.readSegmentation(outputSegmentation, outputSegmentationFile, task) 1127 | else: 1128 | self.readSegmentationFolder(outputSegmentation, outputSegmentationFolder, task, subset) 1129 | 1130 | # Set source volume - required for DICOM Segmentation export 1131 | outputSegmentation.SetNodeReferenceID(outputSegmentation.GetReferenceImageGeometryReferenceRole(), inputVolume.GetID()) 1132 | outputSegmentation.SetReferenceImageGeometryParameterFromVolumeNode(inputVolume) 1133 | 1134 | # Place segmentation node in the same place as the input volume 1135 | shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) 1136 | inputVolumeShItem = shNode.GetItemByDataNode(inputVolume) 1137 | studyShItem = shNode.GetItemParent(inputVolumeShItem) 1138 | segmentationShItem = shNode.GetItemByDataNode(outputSegmentation) 1139 | shNode.SetItemParent(segmentationShItem, studyShItem) 1140 | 1141 | def readSegmentationFolder(self, outputSegmentation, output_segmentation_dir, task, subset=None): 1142 | """ 1143 | The method is very slow, but this is the only option for some specialized tasks. 1144 | """ 1145 | 1146 | import os 1147 | 1148 | outputSegmentation.GetSegmentation().RemoveAllSegments() 1149 | 1150 | # Get color node with random colors 1151 | randomColorsNode = slicer.mrmlScene.GetNodeByID('vtkMRMLColorTableNodeRandom') 1152 | rgba = [0, 0, 0, 0] 1153 | # Get label descriptions 1154 | 1155 | # Get label descriptions if task is provided 1156 | from totalsegmentator.map_to_binary import class_map 1157 | labelValueToSegmentName = class_map[task] if task else {} 1158 | 1159 | def import_labelmap_to_segmentation(labelmapVolumeNode, segmentName, segmentId): 1160 | updatedSegmentIds = vtk.vtkStringArray() 1161 | updatedSegmentIds.InsertNextValue(segmentId) 1162 | slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, outputSegmentation, updatedSegmentIds) 1163 | self.setTerminology(outputSegmentation, segmentName, segmentId) 1164 | slicer.mrmlScene.RemoveNode(labelmapVolumeNode) 1165 | 1166 | # Read each candidate file 1167 | for labelValue, segmentName in labelValueToSegmentName.items(): 1168 | self.log(_("Importing candidate {segment_name}").format(segment_name=segmentName)) 1169 | labelVolumePath = os.path.join(output_segmentation_dir, f"{segmentName}.nii.gz") 1170 | if not os.path.exists(labelVolumePath): 1171 | self.log(_("Path {segment_name} not exists.").format(segment_name=segmentName)) 1172 | continue 1173 | labelmapVolumeNode = slicer.util.loadLabelVolume(labelVolumePath, {"name": segmentName}) 1174 | randomColorsNode.GetColor(labelValue, rgba) 1175 | segmentId = outputSegmentation.GetSegmentation().AddEmptySegment(segmentName, segmentName, rgba[0:3]) 1176 | import_labelmap_to_segmentation(labelmapVolumeNode, segmentName, segmentId) 1177 | 1178 | # Read each subset file if subset is provided 1179 | if subset is not None and task is None: 1180 | for segmentName in subset: 1181 | self.log(_("Importing subset {segment_name}").format(segment_name=segmentName)) 1182 | labelVolumePath = os.path.join(output_segmentation_dir, f"{segmentName}.nii.gz") 1183 | if os.path.exists(labelVolumePath): 1184 | labelmapVolumeNode = slicer.util.loadLabelVolume(labelVolumePath, {"name": segmentName}) 1185 | segmentId = outputSegmentation.GetSegmentation().AddEmptySegment(segmentName, segmentName) 1186 | import_labelmap_to_segmentation(labelmapVolumeNode, segmentName, segmentId) 1187 | else: 1188 | self.log(_("{segment_name} not found.").format(segment_name=segmentName)) 1189 | 1190 | def readSegmentation(self, outputSegmentation, outputSegmentationFile, task): 1191 | 1192 | # Get label descriptions 1193 | from totalsegmentator.map_to_binary import class_map 1194 | labelValueToSegmentName = class_map[task] 1195 | maxLabelValue = max(labelValueToSegmentName.keys()) 1196 | if min(labelValueToSegmentName.keys()) < 0: 1197 | raise RuntimeError("Label values in class_map must be positive") 1198 | 1199 | # Get color node with random colors 1200 | randomColorsNode = slicer.mrmlScene.GetNodeByID('vtkMRMLColorTableNodeRandom') 1201 | rgba = [0, 0, 0, 0] 1202 | 1203 | # Create color table for this segmentation task 1204 | colorTableNode = slicer.vtkMRMLColorTableNode() 1205 | colorTableNode.SetTypeToUser() 1206 | colorTableNode.SetNumberOfColors(maxLabelValue+1) 1207 | colorTableNode.SetName(task) 1208 | for labelValue in labelValueToSegmentName: 1209 | randomColorsNode.GetColor(labelValue,rgba) 1210 | colorTableNode.SetColor(labelValue, rgba[0], rgba[1], rgba[2], rgba[3]) 1211 | colorTableNode.SetColorName(labelValue, labelValueToSegmentName[labelValue]) 1212 | slicer.mrmlScene.AddNode(colorTableNode) 1213 | 1214 | # Load the segmentation 1215 | outputSegmentation.SetLabelmapConversionColorTableNodeID(colorTableNode.GetID()) 1216 | outputSegmentation.AddDefaultStorageNode() 1217 | storageNode = outputSegmentation.GetStorageNode() 1218 | storageNode.SetFileName(outputSegmentationFile) 1219 | storageNode.ReadData(outputSegmentation) 1220 | 1221 | slicer.mrmlScene.RemoveNode(colorTableNode) 1222 | 1223 | # Set terminology and color 1224 | for labelValue in labelValueToSegmentName: 1225 | segmentName = labelValueToSegmentName[labelValue] 1226 | segmentId = segmentName 1227 | self.setTerminology(outputSegmentation, segmentName, segmentId) 1228 | 1229 | def setTerminology(self, segmentation, segmentName, segmentId): 1230 | segment = segmentation.GetSegmentation().GetSegment(segmentId) 1231 | if not segment: 1232 | # Segment is not present in this segmentation 1233 | return 1234 | if segmentName in self.totalSegmentatorLabelTerminology: 1235 | terminologyEntryStr = self.totalSegmentatorLabelTerminology[segmentName] 1236 | segment.SetTag(segment.GetTerminologyEntryTagName(), terminologyEntryStr) 1237 | try: 1238 | label, color = self.getSegmentLabelColor(terminologyEntryStr) 1239 | if self.useStandardSegmentNames: 1240 | segment.SetName(label) 1241 | # Compare color to default gray (127.0/255.0) to avoid using undefined color 1242 | if not TotalSegmentatorLogic.isDefaultColor(color): 1243 | segment.SetColor(color) 1244 | except RuntimeError as e: 1245 | self.log(str(e)) 1246 | 1247 | @staticmethod 1248 | def isDefaultColor(color): 1249 | return all(abs(colorComponent - 127.0/255.0) < 0.01 for colorComponent in color) 1250 | 1251 | # 1252 | # TotalSegmentatorTest 1253 | # 1254 | 1255 | class TotalSegmentatorTest(ScriptedLoadableModuleTest): 1256 | """ 1257 | This is the test case for your scripted module. 1258 | Uses ScriptedLoadableModuleTest base class, available at: 1259 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 1260 | """ 1261 | 1262 | def setUp(self): 1263 | """ Do whatever is needed to reset the state - typically a scene clear will be enough. 1264 | """ 1265 | slicer.mrmlScene.Clear() 1266 | 1267 | def runTest(self): 1268 | """Run as few or as many tests as needed here. 1269 | """ 1270 | self.setUp() 1271 | self.test_TotalSegmentator1() 1272 | self.setUp() 1273 | self.test_TotalSegmentatorSubset() 1274 | 1275 | def test_TotalSegmentator1(self): 1276 | """ Ideally you should have several levels of tests. At the lowest level 1277 | tests should exercise the functionality of the logic with different inputs 1278 | (both valid and invalid). At higher levels your tests should emulate the 1279 | way the user would interact with your code and confirm that it still works 1280 | the way you intended. 1281 | One of the most important features of the tests is that it should alert other 1282 | developers when their changes will have an impact on the behavior of your 1283 | module. For example, if a developer removes a feature that you depend on, 1284 | your test should break so they know that the feature is needed. 1285 | """ 1286 | 1287 | self.delayDisplay("Starting the test") 1288 | 1289 | # Get/create input data 1290 | 1291 | import SampleData 1292 | inputVolume = SampleData.downloadSample('CTACardio') 1293 | self.delayDisplay('Loaded test data set') 1294 | 1295 | outputSegmentation = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode') 1296 | 1297 | # Test the module logic 1298 | 1299 | # Logic testing is disabled by default to not overload automatic build machines (pytorch is a huge package and computation 1300 | # on CPU takes 5-10 minutes). Set testLogic to True to enable testing. 1301 | testLogic = False 1302 | 1303 | if testLogic: 1304 | logic = TotalSegmentatorLogic() 1305 | logic.logCallback = self._mylog 1306 | 1307 | self.delayDisplay('Set up required Python packages') 1308 | logic.setupPythonRequirements() 1309 | 1310 | self.delayDisplay('Compute output') 1311 | logic.process(inputVolume, outputSegmentation, fast=False) 1312 | 1313 | else: 1314 | logging.warning("test_TotalSegmentator1 logic testing was skipped") 1315 | 1316 | self.delayDisplay('Test passed') 1317 | 1318 | def _mylog(self,text): 1319 | print(text) 1320 | 1321 | def test_TotalSegmentatorSubset(self): 1322 | """ Ideally you should have several levels of tests. At the lowest level 1323 | tests should exercise the functionality of the logic with different inputs 1324 | (both valid and invalid). At higher levels your tests should emulate the 1325 | way the user would interact with your code and confirm that it still works 1326 | the way you intended. 1327 | One of the most important features of the tests is that it should alert other 1328 | developers when their changes will have an impact on the behavior of your 1329 | module. For example, if a developer removes a feature that you depend on, 1330 | your test should break so they know that the feature is needed. 1331 | """ 1332 | 1333 | self.delayDisplay("Starting the test") 1334 | 1335 | # Get/create input data 1336 | 1337 | import SampleData 1338 | inputVolume = SampleData.downloadSample('CTACardio') 1339 | self.delayDisplay('Loaded test data set') 1340 | 1341 | outputSegmentation = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode') 1342 | 1343 | # Test the module logic 1344 | 1345 | # Logic testing is disabled by default to not overload automatic build machines (pytorch is a huge package and computation 1346 | # on CPU takes 5-10 minutes). Set testLogic to True to enable testing. 1347 | testLogic = False 1348 | 1349 | if testLogic: 1350 | logic = TotalSegmentatorLogic() 1351 | logic.logCallback = self._mylog 1352 | 1353 | self.delayDisplay('Set up required Python packages') 1354 | logic.setupPythonRequirements() 1355 | 1356 | self.delayDisplay('Compute output') 1357 | _subset = ["lung_upper_lobe_left","lung_lower_lobe_right","trachea"] 1358 | logic.process(inputVolume, outputSegmentation, fast = False, subset = _subset) 1359 | 1360 | else: 1361 | logging.warning("test_TotalSegmentator1 logic testing was skipped") 1362 | 1363 | self.delayDisplay('Test passed') 1364 | --------------------------------------------------------------------------------