├── 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 |
303 | 1
304 |
305 |
306 | ctkFittedTextBrowser
307 | QTextBrowser
308 |
309 |
310 |
311 | ctkMenuButton
312 | QPushButton
313 |
314 |
315 |
316 | qMRMLNodeComboBox
317 | QWidget
318 |
319 |
320 |
321 | qMRMLWidget
322 | QWidget
323 |
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 | 
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 |
--------------------------------------------------------------------------------