├── .gitignore
├── CMakeLists.txt
├── License.txt
├── Logo
├── SliceTracker.ai
└── SliceTracker_original.ai
├── README.md
├── Screenshots
├── Slicetracker.gif
└── needle_tracking.png
├── SliceTracker.png
├── SliceTracker
├── CMakeLists.txt
├── Resources
│ ├── Colors
│ │ └── mpReviewColors.csv
│ ├── Icons
│ │ ├── SliceTracker.png
│ │ ├── icon-needle.png
│ │ ├── icon-newImageData.png
│ │ ├── icon-quickSegmentation.png
│ │ ├── icon-revealCursor.png
│ │ ├── icon-template.png
│ │ ├── icon-track.png
│ │ └── icon-zframe.png
│ ├── default.cfg
│ ├── version.json
│ ├── version.json.in
│ └── zframe
│ │ ├── ProstateTemplate.csv
│ │ ├── zframe-config.csv
│ │ └── zframe-model.vtk
├── SliceTracker.py
├── SliceTrackerRegistration.py
├── SliceTrackerUtils
│ ├── __init__.py
│ ├── algorithms
│ │ ├── __init__.py
│ │ ├── automaticProstateSegmentation.py
│ │ └── zFrameRegistration.py
│ ├── configuration.py
│ ├── constants.py
│ ├── helpers.py
│ ├── preopHandler.py
│ ├── session.py
│ ├── sessionData.py
│ ├── steps
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── evaluation.py
│ │ ├── overview.py
│ │ ├── plugins
│ │ │ ├── __init__.py
│ │ │ ├── case.py
│ │ │ ├── charts.py
│ │ │ ├── needle.py
│ │ │ ├── results.py
│ │ │ ├── segmentation
│ │ │ │ ├── __init__.py
│ │ │ │ ├── automatic.py
│ │ │ │ ├── base.py
│ │ │ │ └── manual.py
│ │ │ ├── segmentationValidator.py
│ │ │ ├── targeting.py
│ │ │ ├── targets.py
│ │ │ └── training.py
│ │ ├── segmentation.py
│ │ └── zFrameRegistration.py
│ └── watch.py
├── SurfaceCutToLabel.py
└── __init__.py
├── Testing
├── CMakeLists.txt
└── SliceTrackerTests.py
├── doc
├── output_example.json
└── output_schema.json
├── puml
├── RegistrationResults.puml
├── SliceTracker.puml
├── SliceTrackerPlugins.puml
├── SliceTrackerSession.puml
├── SliceTrackerSteps.puml
├── SlicerDevelopmentToolbox.puml
├── components.puml
└── intraop_reception.puml
├── startSlicelet.sh
└── startSlicelet_Ubuntu.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | /RegistrationModule/RegistrationModule.pyc
2 | /RegistrationModule/qRegistrationModule.qrc
3 | /RegistrationModule/Log
4 | RegistrationModule/Resources/Testing/intraopDir/*
5 | RegistrationModule/Resources/Testing/preopDir/*
6 | RegistrationModule/Resources/Testing/testData_1/*
7 | RegistrationModule/Resources/Testing/testData_2/*
8 | RegistrationModule/Resources/Testing/testData_3/*
9 | .gitignore.save
10 | *.pyc
11 | .idea/*
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.9)
2 |
3 | #-----------------------------------------------------------------------------
4 | # Extension meta-information
5 | set(EXTENSION_NAME SliceTracker)
6 | set(EXTENSION_HOMEPAGE "https://github.com/SlicerProstate/SliceTracker")
7 | set(EXTENSION_CATEGORY "IGT")
8 | set(EXTENSION_CONTRIBUTORS "Christian Herz (SPL), Peter Behringer (SPL), Kyle MacNeil (Med-i Lab, Queen's; SPL), Andrey Fedorov (SPL)")
9 | set(EXTENSION_DESCRIPTION "This extension provides modules to support in-bore MRI-guided prostate biopsy.")
10 | set(EXTENSION_ICONURL "https://www.slicer.org/w/images/b/b1/SliceTracker_Logo_1.1_128x128.png")
11 | set(EXTENSION_SCREENSHOTURLS "http://wiki.slicer.org/slicerWiki/images/7/78/Slicetracker.gif")
12 | set(EXTENSION_STATUS "beta")
13 | set(EXTENSION_DEPENDS "SlicerDevelopmentToolbox mpReview SegmentEditorExtraEffects DeepInfer ZFrameRegistration")
14 |
15 | #-----------------------------------------------------------------------------
16 | find_package(Slicer REQUIRED)
17 | include(${Slicer_USE_FILE})
18 |
19 | add_subdirectory(SliceTracker)
20 |
21 |
22 | #-----------------------------------------------------------------------------
23 | if(BUILD_TESTING)
24 |
25 | # Register the unittest subclass in the main script as a ctest.
26 | # Note that the test will also be available at runtime.
27 | # slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py)
28 |
29 | # Additional build-time testing
30 | # add_subdirectory(Testing)
31 | endif()
32 |
33 | #-----------------------------------------------------------------------------
34 | include(${Slicer_EXTENSION_CPACK})
35 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 |
2 | For more information, please see:
3 |
4 | http://www.slicer.org
5 |
6 | The 3D Slicer license below is a BSD style license, with extensions
7 | to cover contributions and other issues specific to 3D Slicer.
8 |
9 |
10 | 3D Slicer Contribution and Software License Agreement ("Agreement")
11 | Version 1.0 (December 20, 2005)
12 |
13 | This Agreement covers contributions to and downloads from the 3D
14 | Slicer project ("Slicer") maintained by The Brigham and Women's
15 | Hospital, Inc. ("Brigham"). Part A of this Agreement applies to
16 | contributions of software and/or data to Slicer (including making
17 | revisions of or additions to code and/or data already in Slicer). Part
18 | B of this Agreement applies to downloads of software and/or data from
19 | Slicer. Part C of this Agreement applies to all transactions with
20 | Slicer. If you distribute Software (as defined below) downloaded from
21 | Slicer, all of the paragraphs of Part B of this Agreement must be
22 | included with and apply to such Software.
23 |
24 | Your contribution of software and/or data to Slicer (including prior
25 | to the date of the first publication of this Agreement, each a
26 | "Contribution") and/or downloading, copying, modifying, displaying,
27 | distributing or use of any software and/or data from Slicer
28 | (collectively, the "Software") constitutes acceptance of all of the
29 | terms and conditions of this Agreement. If you do not agree to such
30 | terms and conditions, you have no right to contribute your
31 | Contribution, or to download, copy, modify, display, distribute or use
32 | the Software.
33 |
34 | PART A. CONTRIBUTION AGREEMENT - License to Brigham with Right to
35 | Sublicense ("Contribution Agreement").
36 |
37 | 1. As used in this Contribution Agreement, "you" means the individual
38 | contributing the Contribution to Slicer and the institution or
39 | entity which employs or is otherwise affiliated with such
40 | individual in connection with such Contribution.
41 |
42 | 2. This Contribution Agreement applies to all Contributions made to
43 | Slicer, including without limitation Contributions made prior to
44 | the date of first publication of this Agreement. If at any time you
45 | make a Contribution to Slicer, you represent that (i) you are
46 | legally authorized and entitled to make such Contribution and to
47 | grant all licenses granted in this Contribution Agreement with
48 | respect to such Contribution; (ii) if your Contribution includes
49 | any patient data, all such data is de-identified in accordance with
50 | U.S. confidentiality and security laws and requirements, including
51 | but not limited to the Health Insurance Portability and
52 | Accountability Act (HIPAA) and its regulations, and your disclosure
53 | of such data for the purposes contemplated by this Agreement is
54 | properly authorized and in compliance with all applicable laws and
55 | regulations; and (iii) you have preserved in the Contribution all
56 | applicable attributions, copyright notices and licenses for any
57 | third party software or data included in the Contribution.
58 |
59 | 3. Except for the licenses granted in this Agreement, you reserve all
60 | right, title and interest in your Contribution.
61 |
62 | 4. You hereby grant to Brigham, with the right to sublicense, a
63 | perpetual, worldwide, non-exclusive, no charge, royalty-free,
64 | irrevocable license to use, reproduce, make derivative works of,
65 | display and distribute the Contribution. If your Contribution is
66 | protected by patent, you hereby grant to Brigham, with the right to
67 | sublicense, a perpetual, worldwide, non-exclusive, no-charge,
68 | royalty-free, irrevocable license under your interest in patent
69 | rights covering the Contribution, to make, have made, use, sell and
70 | otherwise transfer your Contribution, alone or in combination with
71 | any other code.
72 |
73 | 5. You acknowledge and agree that Brigham may incorporate your
74 | Contribution into Slicer and may make Slicer available to members
75 | of the public on an open source basis under terms substantially in
76 | accordance with the Software License set forth in Part B of this
77 | Agreement. You further acknowledge and agree that Brigham shall
78 | have no liability arising in connection with claims resulting from
79 | your breach of any of the terms of this Agreement.
80 |
81 | 6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION
82 | DOES NOT CONTAIN ANY CODE THAT REQURES OR PRESCRIBES AN "OPEN
83 | SOURCE LICENSE" FOR DERIVATIVE WORKS (by way of non-limiting
84 | example, the GNU General Public License or other so-called
85 | "reciprocal" license that requires any derived work to be licensed
86 | under the GNU General Public License or other "open source
87 | license").
88 |
89 | PART B. DOWNLOADING AGREEMENT - License from Brigham with Right to
90 | Sublicense ("Software License").
91 |
92 | 1. As used in this Software License, "you" means the individual
93 | downloading and/or using, reproducing, modifying, displaying and/or
94 | distributing the Software and the institution or entity which
95 | employs or is otherwise affiliated with such individual in
96 | connection therewith. The Brigham and Women?s Hospital,
97 | Inc. ("Brigham") hereby grants you, with right to sublicense, with
98 | respect to Brigham's rights in the software, and data, if any,
99 | which is the subject of this Software License (collectively, the
100 | "Software"), a royalty-free, non-exclusive license to use,
101 | reproduce, make derivative works of, display and distribute the
102 | Software, provided that:
103 |
104 | (a) you accept and adhere to all of the terms and conditions of this
105 | Software License;
106 |
107 | (b) in connection with any copy of or sublicense of all or any portion
108 | of the Software, all of the terms and conditions in this Software
109 | License shall appear in and shall apply to such copy and such
110 | sublicense, including without limitation all source and executable
111 | forms and on any user documentation, prefaced with the following
112 | words: "All or portions of this licensed product (such portions are
113 | the "Software") have been obtained under license from The Brigham and
114 | Women's Hospital, Inc. and are subject to the following terms and
115 | conditions:"
116 |
117 | (c) you preserve and maintain all applicable attributions, copyright
118 | notices and licenses included in or applicable to the Software;
119 |
120 | (d) modified versions of the Software must be clearly identified and
121 | marked as such, and must not be misrepresented as being the original
122 | Software; and
123 |
124 | (e) you consider making, but are under no obligation to make, the
125 | source code of any of your modifications to the Software freely
126 | available to others on an open source basis.
127 |
128 | 2. The license granted in this Software License includes without
129 | limitation the right to (i) incorporate the Software into
130 | proprietary programs (subject to any restrictions applicable to
131 | such programs), (ii) add your own copyright statement to your
132 | modifications of the Software, and (iii) provide additional or
133 | different license terms and conditions in your sublicenses of
134 | modifications of the Software; provided that in each case your use,
135 | reproduction or distribution of such modifications otherwise
136 | complies with the conditions stated in this Software License.
137 |
138 | 3. This Software License does not grant any rights with respect to
139 | third party software, except those rights that Brigham has been
140 | authorized by a third party to grant to you, and accordingly you
141 | are solely responsible for (i) obtaining any permissions from third
142 | parties that you need to use, reproduce, make derivative works of,
143 | display and distribute the Software, and (ii) informing your
144 | sublicensees, including without limitation your end-users, of their
145 | obligations to secure any such required permissions.
146 |
147 | 4. The Software has been designed for research purposes only and has
148 | not been reviewed or approved by the Food and Drug Administration
149 | or by any other agency. YOU ACKNOWLEDGE AND AGREE THAT CLINICAL
150 | APPLICATIONS ARE NEITHER RECOMMENDED NOR ADVISED. Any
151 | commercialization of the Software is at the sole risk of the party
152 | or parties engaged in such commercialization. You further agree to
153 | use, reproduce, make derivative works of, display and distribute
154 | the Software in compliance with all applicable governmental laws,
155 | regulations and orders, including without limitation those relating
156 | to export and import control.
157 |
158 | 5. The Software is provided "AS IS" and neither Brigham nor any
159 | contributor to the software (each a "Contributor") shall have any
160 | obligation to provide maintenance, support, updates, enhancements
161 | or modifications thereto. BRIGHAM AND ALL CONTRIBUTORS SPECIFICALLY
162 | DISCLAIM ALL EXPRESS AND IMPLIED WARRANTIES OF ANY KIND INCLUDING,
163 | BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR
164 | A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
165 | BRIGHAM OR ANY CONTRIBUTOR BE LIABLE TO ANY PARTY FOR DIRECT,
166 | INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
167 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ARISING IN ANY WAY
168 | RELATED TO THE SOFTWARE, EVEN IF BRIGHAM OR ANY CONTRIBUTOR HAS
169 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM
170 | EXTENT NOT PROHIBITED BY LAW OR REGULATION, YOU FURTHER ASSUME ALL
171 | LIABILITY FOR YOUR USE, REPRODUCTION, MAKING OF DERIVATIVE WORKS,
172 | DISPLAY, LICENSE OR DISTRIBUTION OF THE SOFTWARE AND AGREE TO
173 | INDEMNIFY AND HOLD HARMLESS BRIGHAM AND ALL CONTRIBUTORS FROM AND
174 | AGAINST ANY AND ALL CLAIMS, SUITS, ACTIONS, DEMANDS AND JUDGMENTS
175 | ARISING THEREFROM.
176 |
177 | 6. None of the names, logos or trademarks of Brigham or any of
178 | Brigham's affiliates or any of the Contributors, or any funding
179 | agency, may be used to endorse or promote products produced in
180 | whole or in part by operation of the Software or derived from or
181 | based on the Software without specific prior written permission
182 | from the applicable party.
183 |
184 | 7. Any use, reproduction or distribution of the Software which is not
185 | in accordance with this Software License shall automatically revoke
186 | all rights granted to you under this Software License and render
187 | Paragraphs 1 and 2 of this Software License null and void.
188 |
189 | 8. This Software License does not grant any rights in or to any
190 | intellectual property owned by Brigham or any Contributor except
191 | those rights expressly granted hereunder.
192 |
193 | PART C. MISCELLANEOUS
194 |
195 | This Agreement shall be governed by and construed in accordance with
196 | the laws of The Commonwealth of Massachusetts without regard to
197 | principles of conflicts of law. This Agreement shall supercede and
198 | replace any license terms that you may have agreed to previously with
199 | respect to Slicer.
200 |
--------------------------------------------------------------------------------
/Logo/SliceTracker.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/Logo/SliceTracker.ai
--------------------------------------------------------------------------------
/Logo/SliceTracker_original.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/Logo/SliceTracker_original.ai
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ### Overview
4 |
5 | SliceTracker is a [3D Slicer](http://slicer.org) extension designed to support the workflow of the in-bore MRI-guided targeted prostate biopsy (MRgBx) (see references below for [clinical context](http://ncigt.org/prostate-biopsy)). SliceTracker was developed and tested to support transperineal MRgBx procedure in the [Advanced Multimodality Image Guided Operating (AMIGO)](http://www.brighamandwomens.org/research/amigo/default.aspx) at the Brigham and Women's Hospital, Boston. Its applicability to other types of procedures has not been evaluated.
6 |
7 | Capabilities of SliceTracker include:
8 | * automatic processing of the patient DICOM images
9 | * planning of the procedure (biopsy target localization)
10 | * calibration of the intra-procedural image frame of reference with the transperineal biopsy template
11 | * automated segmentation of the prostate gland
12 | * deformable intensity-based registration to support
13 | * re-identification of the pre-procedurally defined targets in the intra-procedural images
14 | * automated, continuous tracking of the biopsy targets during the course of the procedure
15 | * structured collection of the relevant data during the course of the procedure (images, results of segmentation and registration, target tracking)
16 | * visualization of the intra-procedural images and support of specialized hanging protocols to facilitate needle tracking, biopsy template and calibration device visualization, assessment of the image registration results, etc.
17 |
18 | For more details, please read [SliceTracker user guide](https://slicerprostate.gitbooks.io/slicetracker).
19 |
20 | 
21 |
22 | ### Disclaimer
23 |
24 | SlicerTracker, same as 3D Slicer, is a research software. **SliceTracker is NOT an FDA-approved medical device**. It is not intended for clinical use. The user assumes full responsibility to comply with the appropriate regulations.
25 |
26 | ### Support
27 |
28 | Please feel free to contact us for questions, feedback, suggestions, bugs, or you can create issues in the issue tracker: https://github.com/SlicerProstate/SliceTracker/issues
29 |
30 | * [Andrey Fedorov](https://github.com/fedorov) fedorov@bwh.harvard.edu
31 |
32 | * Christian Herz cherz@bwh.harvard.edu
33 |
34 | * Peter Behringer peterbehringer@gmx.de
35 |
36 | ### Acknowledgments
37 |
38 | This is the main publication describing SliceTracker:
39 |
40 | Herz, C., MacNeil, K., Behringer, P. A., Tokuda, J., Mehrtash, A., Mousavi, P., Kikinis, R., Fennessy, F. M., Tempany, C. M., Tuncali, K. & Fedorov, A. Open Source Platform for Transperineal In-bore MRI-guided Targeted Prostate Biopsy. IEEE Transactions on Biomedical Engineering 1–1 (2019). DOI: [10.1109/TBME.2019.2918731](https://ieeexplore.ieee.org/document/8721095) PMID: [31135342](https://www.ncbi.nlm.nih.gov/pubmed/31135342)
41 |
42 | Development of SliceTracker is supported in part by the following NIH grants:
43 |
44 | * P41 EB015898 National Center for Image Guided Therapy (NCIGT), http://ncigt.org
45 | * U24 CA180918 Quantitative Image Informatics for Cancer Research (QIICR), http://qiicr.org
46 | * R01 CA111288 Enabling Technologies for MRI-guided prostate interventions
47 |
48 | Several components of SliceTracker were adopted from other open source projects
49 | as follows:
50 | * Needle guidance core functionality was adopted from
51 | https://github.com/ProstateBRP/NeedleGuideTemplate, courtesy Junichi
52 | Tokuda @tokjun
53 | * [Z-frame
54 | calibration](https://github.com/SlicerProstate/SliceTracker/commits/master/ZFrameCalibration)
55 | was adopted from the [ProstateNav module of 3D Slicer version
56 | 3](https://www.slicer.org/slicerWiki/index.php/Modules:ProstateNav-Documentation-3.6) (see source
57 | code [here](https://github.com/SlicerProstate/ProstateNav), no revision
58 | history); although we
59 | do not have the precise record of contribution to that functionality in the
60 | ProstateNav module, we believe main contributors were Junichi Tokuda and
61 | Simon Di Maio (while at BWH, now at [Intuitive
62 | Surgical](http://www.intuitivesurgical.com/))
63 |
64 | ### References
65 |
66 | The following publications cover different aspects of work that led to the development of SliceTracker.
67 |
68 | 1. Behringer P., Herz C., Penzkofer T., Tuncali K., Tempany C., Fedorov A. 2015. Open-source Platform for Prostate Motion Tracking during in-bore Targeted MRI-guided Biopsy. In: MICCAI Workshop on Clinical Image-based Procedures: Translational Research in Medical Imaging. DOI: [10.1007/978-3-319-31808-0_15](http://doi.org/10.1007/978-3-319-31808-0_15) (also see the [accompanying web site](http://slicerprostate.github.io/ProstateMotionStudy/)): **software development, prostate motion tracking**.
69 | 2. Fedorov A., Beichel R., Kalpathy-Cramer J., Finet J., Fillion-Robin J-CC., Pujol S., Bauer C., Jennings D., Fennessy F., Sonka M., Buatti J., Aylward S., Miller J V., Pieper S., Kikinis R. 2012. 3D Slicer as an image computing platform for the Quantitative Imaging Network. Magnetic resonance imaging 30:1323–1341. DOI: [10.1016/j.mri.2012.05.001](http://doi.org/10.1016/j.mri.2012.05.001): **3D Slicer platform**.
70 | 3. Fedorov A., Tuncali K., Fennessy FM., Tokuda J., Hata N., Wells WM., Kikinis R., Tempany CM. 2012. Image registration for targeted MRI-guided transperineal prostate biopsy. Journal of magnetic resonance imaging: JMRI 36:987–992. DOI: [10.1002/jmri.23688](http://doi.org/10.1002/jmri.23688): **deformable registration approach**.
71 | 4. Penzkofer T., Tuncali K., Fedorov A., Song S-E., Tokuda J., Fennessy FM., Vangel MG., Kibel AS., Mulkern RV., Wells WM., Hata N., Tempany CMC. 2015. Transperineal in-bore 3-T MR imaging-guided prostate biopsy: a prospective clinical observational study. Radiology 274:170–180. DOI: [10.1148/radiol.14140221](http://doi.org/10.1148/radiol.14140221): **clinical results**.
72 | 5. Tokuda J., Tuncali K., Iordachita I., Song S-EE., Fedorov A., Oguro S., Lasso A., Fennessy FM., Tempany CM., Hata N. 2012. In-bore setup and software for 3T MRI-guided transperineal prostate biopsy. Physics in medicine and biology 57:5823–5840. DOI: [10.1088/0031-9155/57/18/5823](http://doi.org/10.1088/0031-9155/57/18/5823): **procedure technical setup**.
73 |
74 |
--------------------------------------------------------------------------------
/Screenshots/Slicetracker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/Screenshots/Slicetracker.gif
--------------------------------------------------------------------------------
/Screenshots/needle_tracking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/Screenshots/needle_tracking.png
--------------------------------------------------------------------------------
/SliceTracker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker.png
--------------------------------------------------------------------------------
/SliceTracker/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #-----------------------------------------------------------------------------
2 | set(MODULE_NAME SliceTracker)
3 |
4 | file(GLOB_RECURSE MODULE_PYTHON_SCRIPTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
5 | *.py
6 | )
7 |
8 | file(GLOB_RECURSE MODULE_PYTHON_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
9 | ${PROJECT_SOURCE_DIR}/${MODULE_NAME}/Resources/*
10 | )
11 |
12 | #-----------------------------------------------------------------------------
13 | slicerMacroBuildScriptedModule(
14 | NAME ${MODULE_NAME}
15 | SCRIPTS ${MODULE_PYTHON_SCRIPTS}
16 | RESOURCES ${MODULE_PYTHON_RESOURCES}
17 | )
18 |
19 | slicerMacroBuildScriptedModule(
20 | NAME SliceTrackerRegistration
21 | SCRIPTS SliceTrackerRegistration.py
22 | RESOURCES ""
23 | )
24 |
25 | slicerMacroBuildScriptedModule(
26 | NAME SurfaceCutToLabel
27 | SCRIPTS SurfaceCutToLabel.py
28 | RESOURCES ${MODULE_PYTHON_RESOURCES}
29 | )
30 |
31 | # Get the current working branch
32 | execute_process(
33 | COMMAND git rev-parse --abbrev-ref HEAD
34 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
35 | OUTPUT_VARIABLE GIT_BRANCH
36 | OUTPUT_STRIP_TRAILING_WHITESPACE
37 | )
38 |
39 | # Get the latest abbreviated commit hash of the working branch
40 | execute_process(
41 | COMMAND git log -1 --format=%h
42 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
43 | OUTPUT_VARIABLE GIT_COMMIT_HASH
44 | OUTPUT_STRIP_TRAILING_WHITESPACE
45 | )
46 |
47 | execute_process(COMMAND ${GIT_EXECUTABLE} config --get remote.origin.url
48 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
49 | OUTPUT_VARIABLE GIT_WC_URL
50 | OUTPUT_STRIP_TRAILING_WHITESPACE
51 | )
52 |
53 | configure_file(
54 | ${CMAKE_CURRENT_SOURCE_DIR}/Resources/version.json.in
55 | ${CMAKE_CURRENT_SOURCE_DIR}/Resources/version.json
56 | )
57 |
--------------------------------------------------------------------------------
/SliceTracker/Resources/Colors/mpReviewColors.csv:
--------------------------------------------------------------------------------
1 | Number,Label,R,G,B,A
2 | 0,background,0,0,0,0
3 | 1,WholeGland,51,204,0,1
4 | 2,PeripheralZone,153,90,50,1
5 | 3,TumorROI_PZ_1,255,255,0,1
6 | 4,TumorROI_PZ_2,255,255,160,1
7 | 5,TumorROI_CGTZ_1,250,130,0,1
8 | 6,TumorROI_CGTZ_2,250,180,53,1
9 | 7,BPHROI_1,255,102,102,1
10 | 8,NormalROI_PZ_1,51,204,0,1
11 | 9,NormalROI_CGTZ_1,153,255,102,1
12 | 10,LeftArteryROI,255,0,0,1
13 | 11,RightArteryROI,190,0,0,1
14 | 12,ObturatorMuscleROI,153,102,255,1
15 | 13,CGTZ,99,184,255,1
16 | 14,LeftAntBase,10,10,255,1
17 | 15,LeftAntMid,152,245,255,1
18 | 16,LeftAntApex,0,134,139,1
19 | 17,LeftPostBase,255,153,18,1
20 | 18,LeftPostMid,233,150,122,1
21 | 19,LeftPostApex,255,106,106,1
22 | 20,RightAntBase,255,52,179,1
23 | 21,RightAntMid,205,105,201,1
24 | 22,RightAntApex,137,104,205,1
25 | 23,RightPostBase,154,50,205,1
26 | 24,RightPostMid,240,240,130,1
27 | 25,RightPostApex,255,187,128,1
28 | 26,TumorROI_PZ_3,178,172,35,1
29 | 27,TumorROI_PZ_4,220,200,40,1
30 | 28,TumorROI_CGTZ_3,200,100,0,1
31 | 29,TumorROI_CGTZ_4,200,150,53,1
32 | 30,BPHROI_2,255,102,102,1
33 | 31,NormalROI_PZ_2,0,150,0,1
34 | 32,TumorROI_CGTZ_2,0,100,0,1
35 |
36 |
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/SliceTracker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/SliceTracker.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-needle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-needle.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-newImageData.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-newImageData.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-quickSegmentation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-quickSegmentation.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-revealCursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-revealCursor.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-template.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-track.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-track.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/Icons/icon-zframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/Resources/Icons/icon-zframe.png
--------------------------------------------------------------------------------
/SliceTracker/Resources/default.cfg:
--------------------------------------------------------------------------------
1 | [ZFrame Registration]
2 | class: OpenSourceZFrameRegistration
3 |
4 | [Series Descriptions]
5 | PLANNING_IMAGE_PATTERN: ^(.*?)((a|A)(x|X))+(.*?)((t|T)(2))+
6 | COVER_PROSTATE_PATTERN: COVER PROSTATE
7 | COVER_TEMPLATE_PATTERN: COVER TEMPLATE
8 | NEEDLE_IMAGE_PATTERN: GUIDANCE
9 | VIBE_IMAGE_PATTERN: VIBE
10 |
11 | [Color File]
12 | FileName: mpReviewColors.csv
13 | SegmentedColorName: WholeGland
14 |
15 | [Evaluation]
16 | # possible layouts: LAYOUT_FOUR_UP_QUANTITATIVE, LAYOUT_SIDE_BY_SIDE
17 | Default_Layout: LAYOUT_FOUR_UP_QUANTITATIVE
18 |
19 | [Modes]
20 | Demo_Mode: False
21 |
22 | [Segmentation]
23 | Use_Deep_Learning: True
24 |
25 | [DICOM]
26 | Incoming_Port: 11112
27 |
28 | [General]
29 | CASE_NUMBER_OF_DIGITS: 3
--------------------------------------------------------------------------------
/SliceTracker/Resources/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "GIT_WC_URL": "https://github.com/che85/SliceTracker.git",
3 | "GIT_COMMIT_HASH": "44ec1ee"
4 | }
5 |
--------------------------------------------------------------------------------
/SliceTracker/Resources/version.json.in:
--------------------------------------------------------------------------------
1 | {
2 | "GIT_WC_URL": "@GIT_WC_URL@",
3 | "GIT_COMMIT_HASH": "@GIT_COMMIT_HASH@"
4 | }
--------------------------------------------------------------------------------
/SliceTracker/Resources/zframe/ProstateTemplate.csv:
--------------------------------------------------------------------------------
1 | "Prostate Biopsy Template"
2 | "A",-7,35.0,25.0,30,35.0,25.0,50,170
3 | "A",-6,30.0,25.0,30,30.0,25.0,50,170
4 | "A",-5,25.0,25.0,30,25.0,25.0,50,170
5 | "A",-4,20.0,25.0,30,20.0,25.0,50,170
6 | "A",-3,15.0,25.0,30,15.0,25.0,50,170
7 | "A",-2,10.0,25.0,30,10.0,25.0,50,170
8 | "A",-1,5.0,25.0,30,5.0,25.0,50,170
9 | "A",0,0.0,25.0,30,0.0,25.0,50,170
10 | "A",1,-5.0,25.0,30,-5.0,25.0,50,170
11 | "A",2,-10.0,25.0,30,-10.0,25.0,50,170
12 | "A",3,-15.0,25.0,30,-15.0,25.0,50,170
13 | "A",4,-20.0,25.0,30,-20.0,25.0,50,170
14 | "A",5,-25.0,25.0,30,-25.0,25.0,50,170
15 | "A",6,-30.0,25.0,30,-30.0,25.0,50,170
16 | "A",7,-35.0,25.0,30,-35.0,25.0,50,170
17 | "B",-7,35.0,20.0,30,35.0,20.0,50,170
18 | "B",-6,30.0,20.0,30,30.0,20.0,50,170
19 | "B",-5,25.0,20.0,30,25.0,20.0,50,170
20 | "B",-4,20.0,20.0,30,20.0,20.0,50,170
21 | "B",-3,15.0,20.0,30,15.0,20.0,50,170
22 | "B",-2,10.0,20.0,30,10.0,20.0,50,170
23 | "B",-1,5.0,20.0,30,5.0,20.0,50,170
24 | "B",0,0.0,20.0,30,0.0,20.0,50,170
25 | "B",1,-5.0,20.0,30,-5.0,20.0,50,170
26 | "B",2,-10.0,20.0,30,-10.0,20.0,50,170
27 | "B",3,-15.0,20.0,30,-15.0,20.0,50,170
28 | "B",4,-20.0,20.0,30,-20.0,20.0,50,170
29 | "B",5,-25.0,20.0,30,-25.0,20.0,50,170
30 | "B",6,-30.0,20.0,30,-30.0,20.0,50,170
31 | "B",7,-35.0,20.0,30,-35.0,20.0,50,170
32 | "C",-7,35.0,15.0,30,35.0,15.0,50,170
33 | "C",-6,30.0,15.0,30,30.0,15.0,50,170
34 | "C",-5,25.0,15.0,30,25.0,15.0,50,170
35 | "C",-4,20.0,15.0,30,20.0,15.0,50,170
36 | "C",-3,15.0,15.0,30,15.0,15.0,50,170
37 | "C",-2,10.0,15.0,30,10.0,15.0,50,170
38 | "C",-1,5.0,15.0,30,5.0,15.0,50,170
39 | "C",0,0.0,15.0,30,0.0,15.0,50,170
40 | "C",1,-5.0,15.0,30,-5.0,15.0,50,170
41 | "C",2,-10.0,15.0,30,-10.0,15.0,50,170
42 | "C",3,-15.0,15.0,30,-15.0,15.0,50,170
43 | "C",4,-20.0,15.0,30,-20.0,15.0,50,170
44 | "C",5,-25.0,15.0,30,-25.0,15.0,50,170
45 | "C",6,-30.0,15.0,30,-30.0,15.0,50,170
46 | "C",7,-35.0,15.0,30,-35.0,15.0,50,170
47 | "D",-7,35.0,10.0,30,35.0,10.0,50,170
48 | "D",-6,30.0,10.0,30,30.0,10.0,50,170
49 | "D",-5,25.0,10.0,30,25.0,10.0,50,170
50 | "D",-4,20.0,10.0,30,20.0,10.0,50,170
51 | "D",-3,15.0,10.0,30,15.0,10.0,50,170
52 | "D",-2,10.0,10.0,30,10.0,10.0,50,170
53 | "D",-1,5.0,10.0,30,5.0,10.0,50,170
54 | "D",0,0.0,10.0,30,0.0,10.0,50,170
55 | "D",1,-5.0,10.0,30,-5.0,10.0,50,170
56 | "D",2,-10.0,10.0,30,-10.0,10.0,50,170
57 | "D",3,-15.0,10.0,30,-15.0,10.0,50,170
58 | "D",4,-20.0,10.0,30,-20.0,10.0,50,170
59 | "D",5,-25.0,10.0,30,-25.0,10.0,50,170
60 | "D",6,-30.0,10.0,30,-30.0,10.0,50,170
61 | "D",7,-35.0,10.0,30,-35.0,10.0,50,170
62 | "E",-7,35.0,5.0,30,35.0,5.0,50,170
63 | "E",-6,30.0,5.0,30,30.0,5.0,50,170
64 | "E",-5,25.0,5.0,30,25.0,5.0,50,170
65 | "E",-4,20.0,5.0,30,20.0,5.0,50,170
66 | "E",-3,15.0,5.0,30,15.0,5.0,50,170
67 | "E",-2,10.0,5.0,30,10.0,5.0,50,170
68 | "E",-1,5.0,5.0,30,5.0,5.0,50,170
69 | "E",0,0.0,5.0,30,0.0,5.0,50,170
70 | "E",1,-5.0,5.0,30,-5.0,5.0,50,170
71 | "E",2,-10.0,5.0,30,-10.0,5.0,50,170
72 | "E",3,-15.0,5.0,30,-15.0,5.0,50,170
73 | "E",4,-20.0,5.0,30,-20.0,5.0,50,170
74 | "E",5,-25.0,5.0,30,-25.0,5.0,50,170
75 | "E",6,-30.0,5.0,30,-30.0,5.0,50,170
76 | "E",7,-35.0,5.0,30,-35.0,5.0,50,170
77 | "F",-7,35.0,0.0,30,35.0,0.0,50,170
78 | "F",-6,30.0,0.0,30,30.0,0.0,50,170
79 | "F",-5,25.0,0.0,30,25.0,0.0,50,170
80 | "F",-4,20.0,0.0,30,20.0,0.0,50,170
81 | "F",-3,15.0,0.0,30,15.0,0.0,50,170
82 | "F",-2,10.0,0.0,30,10.0,0.0,50,170
83 | "F",-1,5.0,0.0,30,5.0,0.0,50,170
84 | "F",0,0.0,0.0,30,0.0,0.0,50,170
85 | "F",1,-5.0,0.0,30,-5.0,0.0,50,170
86 | "F",2,-10.0,0.0,30,-10.0,0.0,50,170
87 | "F",3,-15.0,0.0,30,-15.0,0.0,50,170
88 | "F",4,-20.0,0.0,30,-20.0,0.0,50,170
89 | "F",5,-25.0,0.0,30,-25.0,0.0,50,170
90 | "F",6,-30.0,0.0,30,-30.0,0.0,50,170
91 | "F",7,-35.0,0.0,30,-35.0,0.0,50,170
92 | "G",-7,35.0,-5.0,30,35.0,-5.0,50,170
93 | "G",-6,30.0,-5.0,30,30.0,-5.0,50,170
94 | "G",-5,25.0,-5.0,30,25.0,-5.0,50,170
95 | "G",-4,20.0,-5.0,30,20.0,-5.0,50,170
96 | "G",-3,15.0,-5.0,30,15.0,-5.0,50,170
97 | "G",-2,10.0,-5.0,30,10.0,-5.0,50,170
98 | "G",-1,5.0,-5.0,30,5.0,-5.0,50,170
99 | "G",0,0.0,-5.0,30,0.0,-5.0,50,170
100 | "G",1,-5.0,-5.0,30,-5.0,-5.0,50,170
101 | "G",2,-10.0,-5.0,30,-10.0,-5.0,50,170
102 | "G",3,-15.0,-5.0,30,-15.0,-5.0,50,170
103 | "G",4,-20.0,-5.0,30,-20.0,-5.0,50,170
104 | "G",5,-25.0,-5.0,30,-25.0,-5.0,50,170
105 | "G",6,-30.0,-5.0,30,-30.0,-5.0,50,170
106 | "G",7,-35.0,-5.0,30,-35.0,-5.0,50,170
107 | "H",-7,35.0,-10.0,30,35.0,-10.0,50,170
108 | "H",-6,30.0,-10.0,30,30.0,-10.0,50,170
109 | "H",-5,25.0,-10.0,30,25.0,-10.0,50,170
110 | "H",-4,20.0,-10.0,30,20.0,-10.0,50,170
111 | "H",-3,15.0,-10.0,30,15.0,-10.0,50,170
112 | "H",-2,10.0,-10.0,30,10.0,-10.0,50,170
113 | "H",-1,5.0,-10.0,30,5.0,-10.0,50,170
114 | "H",0,0.0,-10.0,30,0.0,-10.0,50,170
115 | "H",1,-5.0,-10.0,30,-5.0,-10.0,50,170
116 | "H",2,-10.0,-10.0,30,-10.0,-10.0,50,170
117 | "H",3,-15.0,-10.0,30,-15.0,-10.0,50,170
118 | "H",4,-20.0,-10.0,30,-20.0,-10.0,50,170
119 | "H",5,-25.0,-10.0,30,-25.0,-10.0,50,170
120 | "H",6,-30.0,-10.0,30,-30.0,-10.0,50,170
121 | "H",7,-35.0,-10.0,30,-35.0,-10.0,50,170
122 | "I",-7,35.0,-15.0,30,35.0,-15.0,50,170
123 | "I",-6,30.0,-15.0,30,30.0,-15.0,50,170
124 | "I",-5,25.0,-15.0,30,25.0,-15.0,50,170
125 | "I",-4,20.0,-15.0,30,20.0,-15.0,50,170
126 | "I",-3,15.0,-15.0,30,15.0,-15.0,50,170
127 | "I",-2,10.0,-15.0,30,10.0,-15.0,50,170
128 | "I",-1,5.0,-15.0,30,5.0,-15.0,50,170
129 | "I",0,0.0,-15.0,30,0.0,-15.0,50,170
130 | "I",1,-5.0,-15.0,30,-5.0,-15.0,50,170
131 | "I",2,-10.0,-15.0,30,-10.0,-15.0,50,170
132 | "I",3,-15.0,-15.0,30,-15.0,-15.0,50,170
133 | "I",4,-20.0,-15.0,30,-20.0,-15.0,50,170
134 | "I",5,-25.0,-15.0,30,-25.0,-15.0,50,170
135 | "I",6,-30.0,-15.0,30,-30.0,-15.0,50,170
136 | "I",7,-35.0,-15.0,30,-35.0,-15.0,50,170
137 | "J",-7,35.0,-20.0,30,35.0,-20.0,50,170
138 | "J",-6,30.0,-20.0,30,30.0,-20.0,50,170
139 | "J",-5,25.0,-20.0,30,25.0,-20.0,50,170
140 | "J",-4,20.0,-20.0,30,20.0,-20.0,50,170
141 | "J",-3,15.0,-20.0,30,15.0,-20.0,50,170
142 | "J",-2,10.0,-20.0,30,10.0,-20.0,50,170
143 | "J",-1,5.0,-20.0,30,5.0,-20.0,50,170
144 | "J",0,0.0,-20.0,30,0.0,-20.0,50,170
145 | "J",1,-5.0,-20.0,30,-5.0,-20.0,50,170
146 | "J",2,-10.0,-20.0,30,-10.0,-20.0,50,170
147 | "J",3,-15.0,-20.0,30,-15.0,-20.0,50,170
148 | "J",4,-20.0,-20.0,30,-20.0,-20.0,50,170
149 | "J",5,-25.0,-20.0,30,-25.0,-20.0,50,170
150 | "J",6,-30.0,-20.0,30,-30.0,-20.0,50,170
151 | "J",7,-35.0,-20.0,30,-35.0,-20.0,50,170
152 | "K",-7,35.0,-25.0,30,35.0,-25.0,50,170
153 | "K",-6,30.0,-25.0,30,30.0,-25.0,50,170
154 | "K",-5,25.0,-25.0,30,25.0,-25.0,50,170
155 | "K",-4,20.0,-25.0,30,20.0,-25.0,50,170
156 | "K",-3,15.0,-25.0,30,15.0,-25.0,50,170
157 | "K",-2,10.0,-25.0,30,10.0,-25.0,50,170
158 | "K",-1,5.0,-25.0,30,5.0,-25.0,50,170
159 | "K",0,0.0,-25.0,30,0.0,-25.0,50,170
160 | "K",1,-5.0,-25.0,30,-5.0,-25.0,50,170
161 | "K",2,-10.0,-25.0,30,-10.0,-25.0,50,170
162 | "K",3,-15.0,-25.0,30,-15.0,-25.0,50,170
163 | "K",4,-20.0,-25.0,30,-20.0,-25.0,50,170
164 | "K",5,-25.0,-25.0,30,-25.0,-25.0,50,170
165 | "K",6,-30.0,-25.0,30,-30.0,-25.0,50,170
166 | "K",7,-35.0,-25.0,30,-35.0,-25.0,50,170
167 | "L",-7,35.0,-30.0,30,35.0,-30.0,50,170
168 | "L",-6,30.0,-30.0,30,30.0,-30.0,50,170
169 | "L",-5,25.0,-30.0,30,25.0,-30.0,50,170
170 | "L",-4,20.0,-30.0,30,20.0,-30.0,50,170
171 | "L",-3,15.0,-30.0,30,15.0,-30.0,50,170
172 | "L",-2,10.0,-30.0,30,10.0,-30.0,50,170
173 | "L",-1,5.0,-30.0,30,5.0,-30.0,50,170
174 | "L",0,0.0,-30.0,30,0.0,-30.0,50,170
175 | "L",1,-5.0,-30.0,30,-5.0,-30.0,50,170
176 | "L",2,-10.0,-30.0,30,-10.0,-30.0,50,170
177 | "L",3,-15.0,-30.0,30,-15.0,-30.0,50,170
178 | "L",4,-20.0,-30.0,30,-20.0,-30.0,50,170
179 | "L",5,-25.0,-30.0,30,-25.0,-30.0,50,170
180 | "L",6,-30.0,-30.0,30,-30.0,-30.0,50,170
181 | "L",7,-35.0,-30.0,30,-35.0,-30.0,50,170
182 | "M",-7,35.0,-35.0,30,35.0,-35.0,50,170
183 | "M",-6,30.0,-35.0,30,30.0,-35.0,50,170
184 | "M",-5,25.0,-35.0,30,25.0,-35.0,50,170
185 | "M",-4,20.0,-35.0,30,20.0,-35.0,50,170
186 | "M",-3,15.0,-35.0,30,15.0,-35.0,50,170
187 | "M",-2,10.0,-35.0,30,10.0,-35.0,50,170
188 | "M",-1,5.0,-35.0,30,5.0,-35.0,50,170
189 | "M",0,0.0,-35.0,30,0.0,-35.0,50,170
190 | "M",1,-5.0,-35.0,30,-5.0,-35.0,50,170
191 | "M",2,-10.0,-35.0,30,-10.0,-35.0,50,170
192 | "M",3,-15.0,-35.0,30,-15.0,-35.0,50,170
193 | "M",4,-20.0,-35.0,30,-20.0,-35.0,50,170
194 | "M",5,-25.0,-35.0,30,-25.0,-35.0,50,170
195 | "M",6,-30.0,-35.0,30,-30.0,-35.0,50,170
196 | "M",7,-35.0,-35.0,30,-35.0,-35.0,50,170
197 | "N",-7,35.0,-40.0,30,35.0,-40.0,50,170
198 | "N",-6,30.0,-40.0,30,30.0,-40.0,50,170
199 | "N",-5,25.0,-40.0,30,25.0,-40.0,50,170
200 | "N",-4,20.0,-40.0,30,20.0,-40.0,50,170
201 | "N",-3,15.0,-40.0,30,15.0,-40.0,50,170
202 | "N",-2,10.0,-40.0,30,10.0,-40.0,50,170
203 | "N",-1,5.0,-40.0,30,5.0,-40.0,50,170
204 | "N",0,0.0,-40.0,30,0.0,-40.0,50,170
205 | "N",1,-5.0,-40.0,30,-5.0,-40.0,50,170
206 | "N",2,-10.0,-40.0,30,-10.0,-40.0,50,170
207 | "N",3,-15.0,-40.0,30,-15.0,-40.0,50,170
208 | "N",4,-20.0,-40.0,30,-20.0,-40.0,50,170
209 | "N",5,-25.0,-40.0,30,-25.0,-40.0,50,170
210 | "N",6,-30.0,-40.0,30,-30.0,-40.0,50,170
211 | "N",7,-35.0,-40.0,30,-35.0,-40.0,50,170
212 |
--------------------------------------------------------------------------------
/SliceTracker/Resources/zframe/zframe-config.csv:
--------------------------------------------------------------------------------
1 | 30.0,30.0,0.0,0.0,0.0,0.0
2 | 30.0,0.0,0.0,0.0,-0.70710678118655,0.70710678118655
3 | 30.0,-30.0,0.0,0.0,0.0,1.0
4 | 0.0,-30.0,0.0,-0.70710678118655,0.0,0.70710678118655
5 | -30.0,-30.0,0.0,0.0,0.0,1.0
6 | -30.0,0.0,0.0,0.0,0.70710678118655,0.70710678118655
7 | -30.0,30.0,0.0,0.0,0.0,1.0
8 |
9 |
--------------------------------------------------------------------------------
/SliceTracker/Resources/zframe/zframe-model.vtk:
--------------------------------------------------------------------------------
1 | # vtk DataFile Version 3.0
2 | vtk output
3 | ASCII
4 | DATASET POLYDATA
5 | POINTS 168 float
6 | 31.5 30 30 31.5 30 -30 30.75 31.299 30
7 | 30.75 31.299 -30 29.25 31.299 30 29.25 31.299 -30
8 | 28.5 30 30 28.5 30 -30 29.25 28.701 30
9 | 29.25 28.701 -30 30.75 28.701 30 30.75 28.701 -30
10 | 31.5 30 30 30.75 31.299 30 29.25 31.299 30
11 | 28.5 30 30 29.25 28.701 30 30.75 28.701 30
12 | 30.75 28.701 -30 29.25 28.701 -30 28.5 30 -30
13 | 29.25 31.299 -30 30.75 31.299 -30 31.5 30 -30
14 | 31.5 -30 30 31.5 30 -30 30.75 -29.0814 30.9186
15 | 30.75 30.9186 -29.0814 29.25 -29.0814 30.9186 29.25 30.9186 -29.0814
16 | 28.5 -30 30 28.5 30 -30 29.25 -30.9186 29.0814
17 | 29.25 29.0814 -30.9186 30.75 -30.9186 29.0814 30.75 29.0814 -30.9186
18 | 31.5 -30 30 30.75 -29.0814 30.9186 29.25 -29.0814 30.9186
19 | 28.5 -30 30 29.25 -30.9186 29.0814 30.75 -30.9186 29.0814
20 | 30.75 29.0814 -30.9186 29.25 29.0814 -30.9186 28.5 30 -30
21 | 29.25 30.9186 -29.0814 30.75 30.9186 -29.0814 31.5 30 -30
22 | 31.5 -30 30 31.5 -30 -30 30.75 -28.701 30
23 | 30.75 -28.701 -30 29.25 -28.701 30 29.25 -28.701 -30
24 | 28.5 -30 30 28.5 -30 -30 29.25 -31.299 30
25 | 29.25 -31.299 -30 30.75 -31.299 30 30.75 -31.299 -30
26 | 31.5 -30 30 30.75 -28.701 30 29.25 -28.701 30
27 | 28.5 -30 30 29.25 -31.299 30 30.75 -31.299 30
28 | 30.75 -31.299 -30 29.25 -31.299 -30 28.5 -30 -30
29 | 29.25 -28.701 -30 30.75 -28.701 -30 31.5 -30 -30
30 | 31.0607 30 28.9393 -28.9393 30 -31.0607 30.5303 31.299 29.4697
31 | -29.4697 31.299 -30.5303 29.4697 31.299 30.5303 -30.5303 31.299 -29.4697
32 | 28.9393 30 31.0607 -31.0607 30 -28.9393 29.4697 28.701 30.5303
33 | -30.5303 28.701 -29.4697 30.5303 28.701 29.4697 -29.4697 28.701 -30.5303
34 | 31.0607 30 28.9393 30.5303 31.299 29.4697 29.4697 31.299 30.5303
35 | 28.9393 30 31.0607 29.4697 28.701 30.5303 30.5303 28.701 29.4697
36 | -29.4697 28.701 -30.5303 -30.5303 28.701 -29.4697 -31.0607 30 -28.9393
37 | -30.5303 31.299 -29.4697 -29.4697 31.299 -30.5303 -28.9393 30 -31.0607
38 | -28.5 -30 30 -28.5 -30 -30 -29.25 -28.701 30
39 | -29.25 -28.701 -30 -30.75 -28.701 30 -30.75 -28.701 -30
40 | -31.5 -30 30 -31.5 -30 -30 -30.75 -31.299 30
41 | -30.75 -31.299 -30 -29.25 -31.299 30 -29.25 -31.299 -30
42 | -28.5 -30 30 -29.25 -28.701 30 -30.75 -28.701 30
43 | -31.5 -30 30 -30.75 -31.299 30 -29.25 -31.299 30
44 | -29.25 -31.299 -30 -30.75 -31.299 -30 -31.5 -30 -30
45 | -30.75 -28.701 -30 -29.25 -28.701 -30 -28.5 -30 -30
46 | -28.5 30 30 -28.5 -30 -30 -29.25 30.9186 29.0814
47 | -29.25 -29.0814 -30.9186 -30.75 30.9186 29.0814 -30.75 -29.0814 -30.9186
48 | -31.5 30 30 -31.5 -30 -30 -30.75 29.0814 30.9186
49 | -30.75 -30.9186 -29.0814 -29.25 29.0814 30.9186 -29.25 -30.9186 -29.0814
50 | -28.5 30 30 -29.25 30.9186 29.0814 -30.75 30.9186 29.0814
51 | -31.5 30 30 -30.75 29.0814 30.9186 -29.25 29.0814 30.9186
52 | -29.25 -30.9186 -29.0814 -30.75 -30.9186 -29.0814 -31.5 -30 -30
53 | -30.75 -29.0814 -30.9186 -29.25 -29.0814 -30.9186 -28.5 -30 -30
54 | -28.5 30 30 -28.5 30 -30 -29.25 31.299 30
55 | -29.25 31.299 -30 -30.75 31.299 30 -30.75 31.299 -30
56 | -31.5 30 30 -31.5 30 -30 -30.75 28.701 30
57 | -30.75 28.701 -30 -29.25 28.701 30 -29.25 28.701 -30
58 | -28.5 30 30 -29.25 31.299 30 -30.75 31.299 30
59 | -31.5 30 30 -30.75 28.701 30 -29.25 28.701 30
60 | -29.25 28.701 -30 -30.75 28.701 -30 -31.5 30 -30
61 | -30.75 31.299 -30 -29.25 31.299 -30 -28.5 30 -30
62 |
63 | POLYGONS 56 308
64 | 4 0 1 3 2
65 | 4 2 3 5 4
66 | 4 4 5 7 6
67 | 4 6 7 9 8
68 | 4 8 9 11 10
69 | 4 10 11 1 0
70 | 6 12 13 14 15 16 17
71 | 6 18 19 20 21 22 23
72 | 4 24 25 27 26
73 | 4 26 27 29 28
74 | 4 28 29 31 30
75 | 4 30 31 33 32
76 | 4 32 33 35 34
77 | 4 34 35 25 24
78 | 6 36 37 38 39 40 41
79 | 6 42 43 44 45 46 47
80 | 4 48 49 51 50
81 | 4 50 51 53 52
82 | 4 52 53 55 54
83 | 4 54 55 57 56
84 | 4 56 57 59 58
85 | 4 58 59 49 48
86 | 6 60 61 62 63 64 65
87 | 6 66 67 68 69 70 71
88 | 4 72 73 75 74
89 | 4 74 75 77 76
90 | 4 76 77 79 78
91 | 4 78 79 81 80
92 | 4 80 81 83 82
93 | 4 82 83 73 72
94 | 6 84 85 86 87 88 89
95 | 6 90 91 92 93 94 95
96 | 4 96 97 99 98
97 | 4 98 99 101 100
98 | 4 100 101 103 102
99 | 4 102 103 105 104
100 | 4 104 105 107 106
101 | 4 106 107 97 96
102 | 6 108 109 110 111 112 113
103 | 6 114 115 116 117 118 119
104 | 4 120 121 123 122
105 | 4 122 123 125 124
106 | 4 124 125 127 126
107 | 4 126 127 129 128
108 | 4 128 129 131 130
109 | 4 130 131 121 120
110 | 6 132 133 134 135 136 137
111 | 6 138 139 140 141 142 143
112 | 4 144 145 147 146
113 | 4 146 147 149 148
114 | 4 148 149 151 150
115 | 4 150 151 153 152
116 | 4 152 153 155 154
117 | 4 154 155 145 144
118 | 6 156 157 158 159 160 161
119 | 6 162 163 164 165 166 167
120 |
121 | POINT_DATA 168
122 | NORMALS Normals float
123 | 1 0 0 1 0 0 0.5 0.866025 -1.92296e-16
124 | 0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16
125 | -1 -4.10207e-10 9.10842e-26 -1 -4.10207e-10 9.10842e-26 -0.5 -0.866025 1.92296e-16
126 | -0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16
127 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
128 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
129 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
130 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
131 | 1 0 0 1 0 0 0.5 0.612372 0.612372
132 | 0.5 0.612372 0.612372 -0.5 0.612372 0.612372 -0.5 0.612372 0.612372
133 | -1 -2.9006e-10 -2.9006e-10 -1 -2.9006e-10 -2.9006e-10 -0.5 -0.612372 -0.612372
134 | -0.5 -0.612372 -0.612372 0.5 -0.612372 -0.612372 0.5 -0.612372 -0.612372
135 | 0 -0.707107 0.707107 0 -0.707107 0.707107 0 -0.707107 0.707107
136 | 0 -0.707107 0.707107 0 -0.707107 0.707107 0 -0.707107 0.707107
137 | 0 0.707107 -0.707107 0 0.707107 -0.707107 0 0.707107 -0.707107
138 | 0 0.707107 -0.707107 0 0.707107 -0.707107 0 0.707107 -0.707107
139 | 1 0 0 1 0 0 0.5 0.866025 -1.92296e-16
140 | 0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16
141 | -1 -4.10207e-10 9.10842e-26 -1 -4.10207e-10 9.10842e-26 -0.5 -0.866025 1.92296e-16
142 | -0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16
143 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
144 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
145 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
146 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
147 | 0.707107 -1.57009e-16 -0.707107 0.707107 -1.57009e-16 -0.707107 0.353553 0.866025 -0.353553
148 | 0.353553 0.866025 -0.353553 -0.353553 0.866025 0.353553 -0.353553 0.866025 0.353553
149 | -0.707107 -4.10207e-10 0.707107 -0.707107 -4.10207e-10 0.707107 -0.353553 -0.866025 0.353553
150 | -0.353553 -0.866025 0.353553 0.353553 -0.866025 -0.353553 0.353553 -0.866025 -0.353553
151 | 0.707107 1.57009e-16 0.707107 0.707107 1.57009e-16 0.707107 0.707107 1.57009e-16 0.707107
152 | 0.707107 1.57009e-16 0.707107 0.707107 1.57009e-16 0.707107 0.707107 1.57009e-16 0.707107
153 | -0.707107 -1.57009e-16 -0.707107 -0.707107 -1.57009e-16 -0.707107 -0.707107 -1.57009e-16 -0.707107
154 | -0.707107 -1.57009e-16 -0.707107 -0.707107 -1.57009e-16 -0.707107 -0.707107 -1.57009e-16 -0.707107
155 | 1 0 0 1 0 0 0.5 0.866025 -1.92296e-16
156 | 0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16
157 | -1 -4.10207e-10 9.10842e-26 -1 -4.10207e-10 9.10842e-26 -0.5 -0.866025 1.92296e-16
158 | -0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16
159 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
160 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
161 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
162 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
163 | 1 0 0 1 0 0 0.5 0.612372 -0.612372
164 | 0.5 0.612372 -0.612372 -0.5 0.612372 -0.612372 -0.5 0.612372 -0.612372
165 | -1 -2.9006e-10 2.9006e-10 -1 -2.9006e-10 2.9006e-10 -0.5 -0.612372 0.612372
166 | -0.5 -0.612372 0.612372 0.5 -0.612372 0.612372 0.5 -0.612372 0.612372
167 | 0 0.707107 0.707107 0 0.707107 0.707107 0 0.707107 0.707107
168 | 0 0.707107 0.707107 0 0.707107 0.707107 0 0.707107 0.707107
169 | 0 -0.707107 -0.707107 0 -0.707107 -0.707107 0 -0.707107 -0.707107
170 | 0 -0.707107 -0.707107 0 -0.707107 -0.707107 0 -0.707107 -0.707107
171 | 1 0 0 1 0 0 0.5 0.866025 -1.92296e-16
172 | 0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16 -0.5 0.866025 -1.92296e-16
173 | -1 -4.10207e-10 9.10842e-26 -1 -4.10207e-10 9.10842e-26 -0.5 -0.866025 1.92296e-16
174 | -0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16 0.5 -0.866025 1.92296e-16
175 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
176 | 0 2.22045e-16 1 0 2.22045e-16 1 0 2.22045e-16 1
177 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
178 | 0 -2.22045e-16 -1 0 -2.22045e-16 -1 0 -2.22045e-16 -1
179 |
180 | TEXTURE_COORDINATES TCoords 2 float
181 | 1 0 1 1 0.666667 0 0.666667 1 0.333333
182 | 0 0.333333 1 0 0 0 1 0.333333 0
183 | 0.333333 1 0.666667 0 0.666667 1 1.5 -0 0.75
184 | -1.29904 -0.75 -1.29904 -1.5 6.1531e-10 -0.75 1.29904 0.75 1.29904
185 | 0.75 1.29904 -0.75 1.29904 -1.5 6.1531e-10 -0.75 -1.29904 0.75
186 | -1.29904 1.5 -0 1 0 1 1 0.666667 0
187 | 0.666667 1 0.333333 0 0.333333 1 0 0 0
188 | 1 0.333333 0 0.333333 1 0.666667 0 0.666667 1
189 | 1.5 -0 0.75 -1.29904 -0.75 -1.29904 -1.5 6.1531e-10 -0.75
190 | 1.29904 0.75 1.29904 0.75 1.29904 -0.75 1.29904 -1.5 6.1531e-10
191 | -0.75 -1.29904 0.75 -1.29904 1.5 -0 1 0 1
192 | 1 0.666667 0 0.666667 1 0.333333 0 0.333333 1
193 | 0 0 0 1 0.333333 0 0.333333 1 0.666667
194 | 0 0.666667 1 1.5 -0 0.75 -1.29904 -0.75 -1.29904
195 | -1.5 6.1531e-10 -0.75 1.29904 0.75 1.29904 0.75 1.29904 -0.75
196 | 1.29904 -1.5 6.1531e-10 -0.75 -1.29904 0.75 -1.29904 1.5 -0
197 | 1 0 1 1 0.666667 0 0.666667 1 0.333333
198 | 0 0.333333 1 0 0 0 1 0.333333 0
199 | 0.333333 1 0.666667 0 0.666667 1 1.5 -0 0.75
200 | -1.29904 -0.75 -1.29904 -1.5 6.1531e-10 -0.75 1.29904 0.75 1.29904
201 | 0.75 1.29904 -0.75 1.29904 -1.5 6.1531e-10 -0.75 -1.29904 0.75
202 | -1.29904 1.5 -0 1 0 1 1 0.666667 0
203 | 0.666667 1 0.333333 0 0.333333 1 0 0 0
204 | 1 0.333333 0 0.333333 1 0.666667 0 0.666667 1
205 | 1.5 -0 0.75 -1.29904 -0.75 -1.29904 -1.5 6.1531e-10 -0.75
206 | 1.29904 0.75 1.29904 0.75 1.29904 -0.75 1.29904 -1.5 6.1531e-10
207 | -0.75 -1.29904 0.75 -1.29904 1.5 -0 1 0 1
208 | 1 0.666667 0 0.666667 1 0.333333 0 0.333333 1
209 | 0 0 0 1 0.333333 0 0.333333 1 0.666667
210 | 0 0.666667 1 1.5 -0 0.75 -1.29904 -0.75 -1.29904
211 | -1.5 6.1531e-10 -0.75 1.29904 0.75 1.29904 0.75 1.29904 -0.75
212 | 1.29904 -1.5 6.1531e-10 -0.75 -1.29904 0.75 -1.29904 1.5 -0
213 | 1 0 1 1 0.666667 0 0.666667 1 0.333333
214 | 0 0.333333 1 0 0 0 1 0.333333 0
215 | 0.333333 1 0.666667 0 0.666667 1 1.5 -0 0.75
216 | -1.29904 -0.75 -1.29904 -1.5 6.1531e-10 -0.75 1.29904 0.75 1.29904
217 | 0.75 1.29904 -0.75 1.29904 -1.5 6.1531e-10 -0.75 -1.29904 0.75
218 | -1.29904 1.5 -0
219 |
--------------------------------------------------------------------------------
/SliceTracker/SliceTracker.py:
--------------------------------------------------------------------------------
1 | import ast
2 | import os
3 | import logging
4 | from slicer.ScriptedLoadableModule import *
5 |
6 | from SliceTrackerUtils.configuration import SliceTrackerConfiguration
7 | from SliceTrackerUtils.constants import SliceTrackerConstants
8 | from SliceTrackerUtils.session import SliceTrackerSession
9 |
10 | from SliceTrackerUtils.steps.base import SliceTrackerStep
11 | from SliceTrackerUtils.steps.overview import SliceTrackerOverviewStep
12 | from SliceTrackerUtils.steps.zFrameRegistration import SliceTrackerZFrameRegistrationStep
13 | from SliceTrackerUtils.steps.evaluation import SliceTrackerEvaluationStep
14 | from SliceTrackerUtils.steps.segmentation import SliceTrackerSegmentationStep
15 |
16 | from SlicerDevelopmentToolboxUtils.buttons import *
17 | from SlicerDevelopmentToolboxUtils.events import SlicerDevelopmentToolboxEvents
18 | from SlicerDevelopmentToolboxUtils.constants import DICOMTAGS
19 | from SlicerDevelopmentToolboxUtils.decorators import logmethod
20 | from SlicerDevelopmentToolboxUtils.helpers import WatchBoxAttribute
21 | from SlicerDevelopmentToolboxUtils.mixins import ModuleWidgetMixin, ModuleLogicMixin
22 | from SlicerDevelopmentToolboxUtils.widgets import CustomStatusProgressbar, DICOMBasedInformationWatchBox
23 |
24 |
25 | class SliceTracker(ScriptedLoadableModule):
26 |
27 | def __init__(self, parent):
28 | ScriptedLoadableModule.__init__(self, parent)
29 | self.parent.title = "SliceTracker"
30 | self.parent.categories = ["Radiology"]
31 | self.parent.dependencies = ["SlicerDevelopmentToolbox", "mpReview", "mpReviewPreprocessor",
32 | "SegmentEditorSurfaceCut", "DeepInfer", "ZFrameRegistration"]
33 | self.parent.contributors = ["Christian Herz (SPL)", "Peter Behringer (SPL)",
34 | "Kyle MacNeil (Med-i Lab, Queen's; SPL)", "Andriy Fedorov (SPL)"]
35 |
36 | self.parent.helpText = """ SliceTracker facilitates support of MRI-guided targeted prostate biopsy.
37 | See the documentation for
38 | details."""
39 | self.parent.acknowledgementText = """Surgical Planning Laboratory, Brigham and Women's Hospital, Harvard
40 | Medical School, Boston, USA This work was supported in part by the National
41 | Institutes of Health through grants U24 CA180918,
42 | R01 CA111288 and P41 EB015898."""
43 |
44 |
45 | class SliceTrackerWidget(ModuleWidgetMixin, SliceTrackerConstants, ScriptedLoadableModuleWidget):
46 |
47 | def __init__(self, parent=None):
48 | ScriptedLoadableModuleWidget.__init__(self, parent)
49 | self.modulePath = os.path.dirname(slicer.util.modulePath(self.moduleName))
50 | SliceTrackerConfiguration(self.moduleName, os.path.join(self.modulePath, 'Resources', "default.cfg"))
51 | self.logic = SliceTrackerLogic()
52 |
53 | self.session = SliceTrackerSession()
54 | self.session.steps = []
55 | self.session.removeEventObservers()
56 | self.session.addEventObserver(self.session.CloseCaseEvent, lambda caller, event: self.cleanup())
57 | self.session.addEventObserver(SlicerDevelopmentToolboxEvents.NewFileIndexedEvent, self.onNewFileIndexed)
58 | self.demoMode = str(self.getSetting("Demo_Mode", moduleName=self.moduleName)).lower() == 'true'
59 |
60 | def enter(self):
61 | if not slicer.dicomDatabase:
62 | slicer.util.errorDisplay("Slicer DICOMDatabase was not found. In order to be able to use SliceTracker, you will "
63 | "need to set a proper location for the Slicer DICOMDatabase.")
64 | self.layout.parent().enabled = slicer.dicomDatabase is not None
65 |
66 | def exit(self):
67 | pass
68 |
69 | def onReload(self):
70 | ScriptedLoadableModuleWidget.onReload(self)
71 |
72 | @logmethod(logging.DEBUG)
73 | def cleanup(self):
74 | ScriptedLoadableModuleWidget.cleanup(self)
75 | self.patientWatchBox.sourceFile = None
76 | self.preopWatchBox.sourceFile = None
77 | self.intraopWatchBox.sourceFile = None
78 |
79 | def setup(self):
80 | ScriptedLoadableModuleWidget.setup(self)
81 |
82 | for step in [SliceTrackerOverviewStep, SliceTrackerZFrameRegistrationStep,
83 | SliceTrackerSegmentationStep, SliceTrackerEvaluationStep]:
84 | self.session.registerStep(step())
85 |
86 | self.customStatusProgressBar = CustomStatusProgressbar()
87 | self.setupPatientWatchBox()
88 | self.setupViewSettingGroupBox()
89 | self.setupTabBarNavigation()
90 | self.setupConnections()
91 | self.setupSessionObservers()
92 | self.layout.addStretch(1)
93 |
94 | def setupPatientWatchBox(self):
95 | WatchBoxAttribute.TRUNCATE_LENGTH = 20
96 | patientWatchBoxInformation = [WatchBoxAttribute('PatientName', "Patient's Name: ", DICOMTAGS.PATIENT_NAME,
97 | masked=self.demoMode),
98 | WatchBoxAttribute('PatientID', 'Patient ID: ', DICOMTAGS.PATIENT_ID,
99 | masked=self.demoMode),
100 | WatchBoxAttribute('DOB', "Patient's Birth Date: ", DICOMTAGS.PATIENT_BIRTH_DATE,
101 | masked=self.demoMode)]
102 | self.patientWatchBox = DICOMBasedInformationWatchBox(patientWatchBoxInformation, title="Patient Information",
103 | columns=2)
104 | self.layout.addWidget(self.patientWatchBox)
105 |
106 | preopWatchBoxInformation = [WatchBoxAttribute('StudyDate', 'Study Date: ', DICOMTAGS.STUDY_DATE)]
107 | self.preopWatchBox = DICOMBasedInformationWatchBox(preopWatchBoxInformation, title="Preop Information", columns=2)
108 | self.layout.addWidget(self.preopWatchBox)
109 |
110 | intraopWatchBoxInformation = [WatchBoxAttribute('CurrentSeries', 'Current Series: ', [DICOMTAGS.SERIES_NUMBER,
111 | DICOMTAGS.SERIES_DESCRIPTION]),
112 | WatchBoxAttribute('StudyDate', 'Study Date: ', DICOMTAGS.STUDY_DATE)]
113 | self.intraopWatchBox = DICOMBasedInformationWatchBox(intraopWatchBoxInformation, title="Intraop Information",
114 | columns=2)
115 | self.registrationDetailsButton = self.createButton("", icon=Icons.settings, styleSheet="border:none;",
116 | maximumWidth=16)
117 | self.layout.addWidget(self.intraopWatchBox)
118 |
119 | def setupViewSettingGroupBox(self):
120 | iconSize = qt.QSize(24, 24)
121 | self.infoButton = self.createButton("", icon=Icons.info, iconSize=iconSize, checkable=True,
122 | toolTip="Display Patient/Study Information", checked=True)
123 | self.redOnlyLayoutButton = RedSliceLayoutButton()
124 | self.sideBySideLayoutButton = SideBySideLayoutButton()
125 | self.fourUpLayoutButton = FourUpLayoutButton()
126 | self.fourUpPlotLayoutButton = FourUpPlotViewLayoutButton()
127 | self.layoutButtons = [self.redOnlyLayoutButton, self.sideBySideLayoutButton, self.fourUpLayoutButton, self.fourUpPlotLayoutButton]
128 | self.crosshairButton = CrosshairButton()
129 | self.settingsButton = ModuleSettingsButton(self.moduleName)
130 | self.dicomConnectionTestButton = DICOMConnectionTestButton()
131 | self.dicomConnectionTestButton.setToolTip("Test DICOM connection")
132 | self.showAnnotationsButton = self.createButton("", icon=Icons.text_info, iconSize=iconSize, checkable=True,
133 | toolTip="Display annotations", checked=True)
134 |
135 | viewSettingButtons = [self.redOnlyLayoutButton, self.sideBySideLayoutButton, self.fourUpPlotLayoutButton,
136 | self.fourUpLayoutButton, self.infoButton, self.crosshairButton,
137 | self.settingsButton, self.dicomConnectionTestButton]
138 |
139 | for step in self.session.steps:
140 | viewSettingButtons += step.viewSettingButtons
141 |
142 | self.layout.addWidget(self.createHLayout(viewSettingButtons))
143 |
144 | self.resetViewSettingButtons()
145 |
146 | def resetViewSettingButtons(self):
147 | for step in self.session.steps:
148 | step.resetViewSettingButtons()
149 | self.crosshairButton.checked = False
150 |
151 | def setupTabBarNavigation(self):
152 | self.tabWidget = SliceTrackerTabWidget()
153 | self.tabWidget.addEventObserver(self.tabWidget.AvailableLayoutsChangedEvent, self.onAvailableLayoutsChanged)
154 | self.layout.addWidget(self.tabWidget)
155 | self.tabWidget.hideTabs()
156 |
157 | def setupConnections(self):
158 | self.infoButton.connect('toggled(bool)', self.onShowInformationToggled)
159 | self.showAnnotationsButton.connect('toggled(bool)', self.onShowAnnotationsToggled)
160 |
161 | def setupSessionObservers(self):
162 | self.session.addEventObserver(self.session.PreprocessingSuccessfulEvent, self.onSuccessfulPreProcessing)
163 | self.session.addEventObserver(self.session.CurrentSeriesChangedEvent, self.onCurrentSeriesChanged)
164 | self.session.addEventObserver(self.session.CloseCaseEvent, self.onCaseClosed)
165 |
166 | def removeSessionObservers(self):
167 | self.session.removeEventObserver(self.session.PreprocessingSuccessfulEvent, self.onSuccessfulPreProcessing)
168 | self.session.removeEventObserver(self.session.CurrentSeriesChangedEvent, self.onCurrentSeriesChanged)
169 | self.session.removeEventObserver(self.session.CloseCaseEvent, self.onCaseClosed)
170 |
171 | def onSuccessfulPreProcessing(self, caller, event):
172 | dicomFileName = self.logic.getFileList(self.session.preopDICOMDirectory)[0]
173 | filename = os.path.join(self.session.preopDICOMDirectory, dicomFileName)
174 | self.patientWatchBox.sourceFile = filename
175 | self.preopWatchBox.sourceFile = filename
176 |
177 | def onShowAnnotationsToggled(self, checked):
178 | allSliceAnnotations = self.sliceAnnotations[:]
179 |
180 | def onShowInformationToggled(self, checked):
181 | self.patientWatchBox.visible = checked
182 | self.preopWatchBox.visible = checked
183 | self.intraopWatchBox.visible = checked
184 |
185 | @vtk.calldata_type(vtk.VTK_STRING)
186 | def onNewFileIndexed(self, caller, event, callData):
187 | text, size, currentIndex = ast.literal_eval(callData)
188 | if not self.customStatusProgressBar.visible:
189 | self.customStatusProgressBar.show()
190 | self.customStatusProgressBar.maximum = size
191 | self.customStatusProgressBar.updateStatus(text, currentIndex)
192 |
193 | @vtk.calldata_type(vtk.VTK_STRING)
194 | def onCurrentSeriesChanged(self, caller, event, callData=None):
195 | receivedFile = self.session.loadableList[callData][0] if callData else None
196 | if not self.session.data.usePreopData and self.patientWatchBox.sourceFile is None:
197 | self.patientWatchBox.sourceFile = receivedFile
198 | self.preopWatchBox.setInformation("StudyDate", "NA")
199 | self.intraopWatchBox.sourceFile = receivedFile
200 |
201 | @vtk.calldata_type(vtk.VTK_STRING)
202 | def onAvailableLayoutsChanged(self, caller, event, callData):
203 | layouts = ast.literal_eval(callData)
204 | for layoutButton in self.layoutButtons:
205 | layoutButton.enabled = layoutButton.LAYOUT in layouts
206 |
207 | @vtk.calldata_type(vtk.VTK_STRING)
208 | def onCaseClosed(self, caller, event, callData):
209 | self.customStatusProgressBar.reset()
210 |
211 |
212 | class SliceTrackerLogic(ModuleLogicMixin):
213 |
214 | def __init__(self):
215 | pass
216 |
217 |
218 | class SliceTrackerTabWidget(qt.QTabWidget, ModuleWidgetMixin):
219 |
220 | AvailableLayoutsChangedEvent = SliceTrackerStep.AvailableLayoutsChangedEvent
221 |
222 | def __init__(self):
223 | super(SliceTrackerTabWidget, self).__init__()
224 | self.session = SliceTrackerSession()
225 | self._createTabs()
226 | self.currentChanged.connect(self.onCurrentTabChanged)
227 | self.onCurrentTabChanged(0)
228 |
229 | def hideTabs(self):
230 | self.tabBar().hide()
231 |
232 | def _createTabs(self):
233 | # TODO: cleanup on reload?
234 | for step in self.session.steps:
235 | logging.debug("Adding tab for %s step" % step.NAME)
236 | self.addTab(step, step.NAME)
237 | step.addEventObserver(step.ActivatedEvent, self.onStepActivated)
238 | step.addEventObserver(self.AvailableLayoutsChangedEvent, self.onStepAvailableLayoutChanged)
239 |
240 | @vtk.calldata_type(vtk.VTK_STRING)
241 | def onStepAvailableLayoutChanged(self, caller, event, callData):
242 | self.invokeEvent(self.AvailableLayoutsChangedEvent, callData)
243 |
244 | def onStepActivated(self, caller, event):
245 | name = caller.GetAttribute("Name")
246 | index = next((i for i, step in enumerate(self.session.steps) if step.NAME == name), None)
247 | if index is not None:
248 | self.setCurrentIndex(index)
249 |
250 | @logmethod(logging.DEBUG)
251 | def onCurrentTabChanged(self, index):
252 | for idx, step in enumerate(self.session.steps):
253 | if index != idx:
254 | if step.active:
255 | self.session.previousStep = step
256 | step.active = False
257 | self.session.steps[index].active = True
258 | self.updateSizes(index)
259 |
260 | def updateSizes(self, index):
261 | for i in range(self.count):
262 | if i != index:
263 | self.widget(i).setSizePolicy(qt.QSizePolicy.Ignored, qt.QSizePolicy.Ignored)
264 |
265 | self.widget(index).setSizePolicy(qt.QSizePolicy.Preferred, qt.QSizePolicy.Preferred)
266 | self.widget(index).resize(self.widget(index).minimumSizeHint)
267 | self.resize(self.minimumSizeHint)
268 | self.adjustSize()
269 |
270 | class SliceTrackerSlicelet(qt.QWidget, ModuleWidgetMixin):
271 |
272 | class MainWindow(qt.QWidget):
273 |
274 | def __init__(self, parent=None):
275 | qt.QWidget.__init__(self)
276 | self.objectName = "qSlicerAppMainWindow"
277 | self.setLayout(qt.QVBoxLayout())
278 | self.mainFrame = qt.QFrame()
279 | self.mainFrame.setLayout(qt.QHBoxLayout())
280 |
281 | self._statusBar = qt.QStatusBar()
282 | self._statusBar.setMaximumHeight(35)
283 |
284 | self.layout().addWidget(self.mainFrame)
285 | self.layout().addWidget(self._statusBar)
286 |
287 | def statusBar(self):
288 | self._statusBar = getattr(self, "_statusBar", None)
289 | if not self._statusBar:
290 | self._statusBar = qt.QStatusBar()
291 | return self._statusBar
292 |
293 | def __init__(self):
294 | qt.QWidget.__init__(self)
295 |
296 | logging.debug(slicer.dicomDatabase)
297 |
298 | self.mainWidget = SliceTrackerSlicelet.MainWindow()
299 |
300 | self.setupLayoutWidget()
301 |
302 | self.moduleFrame = qt.QWidget()
303 | self.moduleFrame.setLayout(qt.QVBoxLayout())
304 | self.widget = SliceTrackerWidget(self.moduleFrame)
305 | self.widget.setup()
306 |
307 | # TODO: resize self.widget.parent to minimum possible width
308 |
309 | self.scrollArea = qt.QScrollArea()
310 | self.scrollArea.setWidget(self.widget.parent)
311 | self.scrollArea.setWidgetResizable(True)
312 | self.scrollArea.setMinimumWidth(self.widget.parent.minimumSizeHint.width())
313 |
314 | self.splitter = qt.QSplitter()
315 | self.splitter.setOrientation(qt.Qt.Horizontal)
316 | self.splitter.addWidget(self.scrollArea)
317 | self.splitter.addWidget(self.layoutWidget)
318 | self.splitter.splitterMoved.connect(self.onSplitterMoved)
319 |
320 | self.splitter.setStretchFactor(0,0)
321 | self.splitter.setStretchFactor(1,1)
322 | self.splitter.handle(1).installEventFilter(self)
323 |
324 | self.mainWidget.mainFrame.layout().addWidget(self.splitter)
325 | self.mainWidget.show()
326 |
327 | def setupLayoutWidget(self):
328 | self.layoutWidget = qt.QWidget()
329 | self.layoutWidget.setLayout(qt.QHBoxLayout())
330 | layoutWidget = slicer.qMRMLLayoutWidget()
331 | layoutManager = slicer.qSlicerLayoutManager()
332 | layoutManager.setMRMLScene(slicer.mrmlScene)
333 | layoutManager.setScriptedDisplayableManagerDirectory(slicer.app.slicerHome + "/bin/Python/mrmlDisplayableManager")
334 | layoutWidget.setLayoutManager(layoutManager)
335 | slicer.app.setLayoutManager(layoutManager)
336 | layoutWidget.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView)
337 | self.layoutWidget.layout().addWidget(layoutWidget)
338 |
339 | def eventFilter(self, obj, event):
340 | if event.type() == qt.QEvent.MouseButtonDblClick:
341 | self.onSplitterClick()
342 |
343 | def onSplitterMoved(self, pos, index):
344 | vScroll = self.scrollArea.verticalScrollBar()
345 | logging.debug(self.moduleFrame.width, self.widget.parent.width, self.scrollArea.width, vScroll.width)
346 | vScrollbarWidth = 4 if not vScroll.isVisible() else vScroll.width + 4 # TODO: find out, what is 4px wide
347 | if self.scrollArea.minimumWidth != self.widget.parent.minimumSizeHint.width() + vScrollbarWidth:
348 | self.scrollArea.setMinimumWidth(self.widget.parent.minimumSizeHint.width() + vScrollbarWidth)
349 |
350 | def onSplitterClick(self):
351 | if self.splitter.sizes()[0] > 0:
352 | self.splitter.setSizes([0, self.splitter.sizes()[1]])
353 | else:
354 | minimumWidth = self.widget.parent.minimumSizeHint.width()
355 | self.splitter.setSizes([minimumWidth, self.splitter.sizes()[1]-minimumWidth])
356 |
357 |
358 | if __name__ == "SliceTrackerSlicelet":
359 | import sys
360 | logging.debug(sys.argv)
361 | slicelet = SliceTrackerSlicelet()
362 |
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'Christian'
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/algorithms/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/SliceTrackerUtils/algorithms/__init__.py
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/algorithms/automaticProstateSegmentation.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 | import json
4 | import vtk
5 | from collections import OrderedDict
6 |
7 | import slicer
8 | import DeepInfer
9 |
10 | from SlicerDevelopmentToolboxUtils.mixins import ModuleLogicMixin
11 |
12 |
13 | class AutomaticSegmentationLogic(ModuleLogicMixin):
14 |
15 | DeepLearningStartedEvent = vtk.vtkCommand.UserEvent + 438
16 | DeepLearningFinishedEvent = vtk.vtkCommand.UserEvent + 439
17 | DeepLearningFailedEvent = vtk.vtkCommand.UserEvent + 441
18 | # DeepLearningStatusChangedEvent = vtk.vtkCommand.UserEvent + 440
19 |
20 | def __init__(self):
21 | self.inputVolume = None
22 | self.colorNode = None
23 |
24 | def cleanup(self):
25 | self.inputVolume = None
26 |
27 | def run(self, inputVolume, domain, colorNode=None):
28 |
29 | self.colorNode = colorNode
30 | if not inputVolume:
31 | raise ValueError("No input volume found for initializing prostate segmentation deep learning.")
32 |
33 | self.inputVolume = inputVolume
34 | self.invokeEvent(self.DeepLearningStartedEvent)
35 | outputLabel = self._runDocker(domain)
36 |
37 | if outputLabel:
38 | self.invokeEvent(self.DeepLearningFinishedEvent, outputLabel)
39 | else:
40 | self.invokeEvent(self.DeepLearningFailedEvent)
41 |
42 | return outputLabel
43 |
44 | def _runDocker(self, domain):
45 | logic = DeepInfer.DeepInferLogic()
46 | parameters = DeepInfer.ModelParameters()
47 | with open(os.path.join(DeepInfer.JSON_LOCAL_DIR, "ProstateSegmenter.json"), "r") as fp:
48 | j = json.load(fp, object_pairs_hook=OrderedDict)
49 |
50 | iodict = parameters.create_iodict(j)
51 | dockerName, modelName, dataPath = parameters.create_model_info(j)
52 | logging.debug(iodict)
53 |
54 | inputs = {
55 | 'InputVolume' : self.inputVolume
56 | }
57 |
58 | outputLabel = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
59 | outputLabel.SetName(self.inputVolume.GetName()+"-label")
60 | outputs = {
61 | 'OutputLabel': outputLabel
62 | }
63 |
64 | params = dict()
65 | params['Domain'] = domain
66 | params['OutputSmoothing'] = 0
67 | params['ProcessingType'] = 'Fast'
68 | params['InferenceType'] = 'Single'
69 | params['verbose'] = 1
70 |
71 | logic.executeDocker(dockerName, modelName, dataPath, iodict, inputs, params)
72 |
73 | if logic.abort:
74 | return None
75 |
76 | logic.updateOutput(iodict, outputs)
77 |
78 | if self.colorNode:
79 | displayNode = outputLabel.GetDisplayNode()
80 | displayNode.SetAndObserveColorNodeID(self.colorNode.GetID())
81 |
82 | return outputLabel
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/algorithms/zFrameRegistration.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | import slicer
5 | from SlicerDevelopmentToolboxUtils.mixins import ModuleLogicMixin
6 |
7 |
8 | class ZFrameRegistrationBase(ModuleLogicMixin):
9 |
10 | ZFRAME_TRANSFORM_NAME = "ZFrameTransform"
11 |
12 | def __init__(self, inputVolume):
13 | self.inputVolume = inputVolume
14 | self.outputTransform = None
15 | self.outputVolume = None
16 |
17 | def getOutputTransformation(self):
18 | return self.outputTransform
19 |
20 | def getOutputVolume(self):
21 | return self.outputVolume
22 |
23 | def runRegistration(self):
24 | raise NotImplementedError
25 |
26 |
27 | class LineMarkerRegistration(ZFrameRegistrationBase):
28 |
29 | def __init__(self, inputVolume):
30 | super(LineMarkerRegistration, self).__init__(inputVolume)
31 | self.markerConfigPath = os.path.join(os.path.dirname(sys.modules[self.__module__].__file__), '..', 'Resources',
32 | 'zframe', 'zframe-config.csv')
33 |
34 | def runRegistration(self):
35 | volumesLogic = slicer.modules.volumes.logic()
36 | self.outputVolume = volumesLogic.CreateAndAddLabelVolume(slicer.mrmlScene, self.inputVolume,
37 | self.inputVolume.GetName() + '-label')
38 | seriesNumber = self.inputVolume.GetName().split(":")[0]
39 | self.outputTransform = self.createLinearTransformNode(seriesNumber + "-" + self.ZFRAME_TRANSFORM_NAME)
40 |
41 | params = {'inputVolume': self.inputVolume, 'markerConfigFile': self.markerConfigPath,
42 | 'outputVolume': self.outputVolume, 'markerTransform': self.outputTransform}
43 | slicer.cli.run(slicer.modules.linemarkerregistration, None, params, wait_for_completion=True)
44 |
45 |
46 | class OpenSourceZFrameRegistration(ZFrameRegistrationBase):
47 |
48 | def __init__(self, inputVolume):
49 | super(OpenSourceZFrameRegistration, self).__init__(inputVolume)
50 |
51 | def runRegistration(self, start, end):
52 | assert start != -1 and end != -1
53 | seriesNumber = self.inputVolume.GetName().split(":")[0]
54 | self.outputTransform = self.createLinearTransformNode(seriesNumber + "-" + self.ZFRAME_TRANSFORM_NAME)
55 |
56 | params = {'inputVolume': self.inputVolume, 'startSlice': start, 'endSlice': end,
57 | 'outputTransform': self.outputTransform}
58 | slicer.cli.run(slicer.modules.zframeregistration, None, params, wait_for_completion=True)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/configuration.py:
--------------------------------------------------------------------------------
1 | import ConfigParser
2 | import inspect, os
3 | from SlicerDevelopmentToolboxUtils.mixins import ModuleWidgetMixin
4 | from constants import SliceTrackerConstants as constants
5 |
6 |
7 | class SliceTrackerConfiguration(ModuleWidgetMixin):
8 |
9 | def __init__(self, moduleName, configFile):
10 | self.moduleName = moduleName
11 | self.configFile = configFile
12 | self.loadConfiguration()
13 |
14 | def loadConfiguration(self):
15 |
16 | config = ConfigParser.RawConfigParser()
17 | config.read(self.configFile)
18 |
19 | if not self.getSetting("ZFrame_Registration_Class_Name"):
20 | self.setSetting("ZFrame_Registration_Class_Name", config.get('ZFrame Registration', 'class'))
21 |
22 | if not self.getSetting("PLANNING_IMAGE_PATTERN"):
23 | self.setSetting("PLANNING_IMAGE_PATTERN", config.get('Series Descriptions', 'PLANNING_IMAGE_PATTERN'))
24 | if not self.getSetting("COVER_PROSTATE_PATTERN"):
25 | self.setSetting("COVER_PROSTATE_PATTERN", config.get('Series Descriptions', 'COVER_PROSTATE_PATTERN'))
26 | if not self.getSetting("COVER_TEMPLATE_PATTERN"):
27 | self.setSetting("COVER_TEMPLATE_PATTERN", config.get('Series Descriptions', 'COVER_TEMPLATE_PATTERN'))
28 | if not self.getSetting("NEEDLE_IMAGE_PATTERN"):
29 | self.setSetting("NEEDLE_IMAGE_PATTERN", config.get('Series Descriptions', 'NEEDLE_IMAGE_PATTERN'))
30 | if not self.getSetting("VIBE_IMAGE_PATTERN"):
31 | self.setSetting("VIBE_IMAGE_PATTERN", config.get('Series Descriptions', 'VIBE_IMAGE_PATTERN'))
32 |
33 | seriesTypes = [constants.COVER_TEMPLATE, constants.COVER_PROSTATE, constants.GUIDANCE_IMAGE,
34 | constants.VIBE_IMAGE, constants.OTHER_IMAGE]
35 |
36 | self.setSetting("SERIES_TYPES", seriesTypes)
37 |
38 | if not self.getSetting("Color_File_Name") or not os.path.exists(self.getSetting("Color_File_Name")):
39 | colorFilename = config.get('Color File', 'FileName')
40 | self.setSetting("Color_File_Name", os.path.join(os.path.dirname(inspect.getfile(self.__class__)),
41 | '../Resources/Colors', colorFilename))
42 |
43 | if not self.getSetting("Segmentation_Color_Name"):
44 | segmentedColorName = config.get('Color File', 'SegmentedColorName')
45 | self.setSetting("Segmentation_Color_Name", segmentedColorName)
46 |
47 | if not self.getSetting("DEFAULT_EVALUATION_LAYOUT"):
48 | self.setSetting("DEFAULT_EVALUATION_LAYOUT", config.get('Evaluation', 'Default_Layout'))
49 |
50 | if not self.getSetting("Demo_Mode"):
51 | self.setSetting("Demo_Mode", config.get('Modes', 'Demo_Mode'))
52 |
53 | if not self.getSetting("Use_Deep_Learning"):
54 | self.setSetting("Use_Deep_Learning", config.get('Segmentation', 'Use_Deep_Learning'))
55 |
56 | if not self.getSetting("Incoming_DICOM_Port"):
57 | self.setSetting("Incoming_DICOM_Port", config.get('DICOM', 'Incoming_Port'))
58 |
59 | if not self.getSetting("CASE_NUMBER_OF_DIGITS"):
60 | self.setSetting("CASE_NUMBER_OF_DIGITS", config.get('General', 'CASE_NUMBER_OF_DIGITS'))
61 |
62 | self.replaceOldValues()
63 |
64 | def replaceOldValues(self):
65 | for setting in ['PLANNING_IMAGE', 'COVER_TEMPLATE', 'COVER_PROSTATE', 'NEEDLE_IMAGE', 'VIBE_IMAGE']:
66 | if self.getSetting(setting):
67 | self.setSetting(setting+"_PATTERN", self.getSetting(setting))
68 | self.removeSetting(setting)
69 |
70 | for other in ['OTHER_IMAGE', 'Rating_Enabled', 'Maximum_Rating_Score']:
71 | if self.getSetting(other):
72 | self.removeSetting(other)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/constants.py:
--------------------------------------------------------------------------------
1 | import slicer
2 | from SlicerDevelopmentToolboxUtils.mixins import ModuleWidgetMixin as helper
3 |
4 | class SliceTrackerConstants(object):
5 |
6 | MODULE_NAME = "SliceTracker"
7 |
8 | PREOP_SAMPLE_DATA_URL = 'https://github.com/SlicerProstate/SliceTracker/releases/download/test-data/Preop-deid.zip'
9 | INTRAOP_SAMPLE_DATA_URL = 'https://github.com/SlicerProstate/SliceTracker/releases/download/test-data/Intraop-deid.zip'
10 |
11 | JSON_FILENAME = "results.json"
12 |
13 | MISSING_PREOP_ANNOTATION_TEXT = "No preop data available"
14 | LEFT_VIEWER_SLICE_ANNOTATION_TEXT = 'BIOPSY PLAN'
15 | RIGHT_VIEWER_SLICE_ANNOTATION_TEXT = 'TRACKED TARGETS'
16 | RIGHT_VIEWER_SLICE_TRANSFORMED_ANNOTATION_TEXT = 'OLD'
17 | RIGHT_VIEWER_SLICE_NEEDLE_IMAGE_ANNOTATION_TEXT = 'NEW'
18 | APPROVED_RESULT_TEXT_ANNOTATION = "approved"
19 | REJECTED_RESULT_TEXT_ANNOTATION = "rejected"
20 | SKIPPED_RESULT_TEXT_ANNOTATION = "skipped"
21 |
22 | LAYOUT_RED_SLICE_ONLY = slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView
23 | LAYOUT_FOUR_UP = slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView
24 | LAYOUT_FOUR_UP_QUANTITATIVE = slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpPlotView
25 | LAYOUT_SIDE_BY_SIDE = slicer.vtkMRMLLayoutNode.SlicerLayoutSideBySideView
26 | ALLOWED_LAYOUTS = [LAYOUT_SIDE_BY_SIDE, LAYOUT_FOUR_UP, LAYOUT_RED_SLICE_ONLY, LAYOUT_FOUR_UP_QUANTITATIVE]
27 |
28 | PLANNING_IMAGE = "PLANNING IMAGE"
29 | COVER_PROSTATE = "COVER PROSTATE"
30 | COVER_TEMPLATE = "COVER TEMPLATE"
31 | GUIDANCE_IMAGE = "GUIDANCE"
32 | VIBE_IMAGE = "VIBE"
33 | OTHER_IMAGE = "OTHER"
34 |
35 | TRACKABLE_IMAGE_TYPES = [COVER_PROSTATE, COVER_TEMPLATE, GUIDANCE_IMAGE]
36 |
37 | ZFrame_INSTRUCTION_STEPS = {1: "Scroll and click into ZFrame center to set ROI center",
38 | 2: "Click outside of upper right ZFrame corner to set ROI border"}
39 |
40 | IntraopSeriesSelectorToolTip = """
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | |
52 |
53 | tracked(registration result available)
54 | |
55 |
56 |
57 |
58 |
59 | |
60 |
61 | untracked(no registration result available)
62 | |
63 |
64 |
65 |
66 |
67 | |
68 |
69 | skipped(no registration result available)
70 | |
71 |
72 |
73 |
74 |
75 | |
76 |
77 | rejected(non satisfactory/approved registration result available)
78 | |
79 |
80 |
81 |
82 |
83 |
84 | """ % (helper.createAndGetRawColoredPixelMap("green"), helper.createAndGetRawColoredPixelMap("yellow"),
85 | helper.createAndGetRawColoredPixelMap("red"), helper.createAndGetRawColoredPixelMap("grey"))
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/helpers.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import datetime
4 | import qt
5 | import vtk
6 | import re
7 | import slicer
8 |
9 | from constants import SliceTrackerConstants as constants
10 |
11 | from SlicerDevelopmentToolboxUtils.decorators import logmethod
12 | from SlicerDevelopmentToolboxUtils.widgets import ExtendedQMessageBox
13 | from SlicerDevelopmentToolboxUtils.module.logic import LogicBase
14 | from SlicerDevelopmentToolboxUtils.module.base import ModuleBase
15 | from SlicerDevelopmentToolboxUtils.mixins import ModuleWidgetMixin
16 | from SlicerDevelopmentToolboxUtils.metaclasses import Singleton
17 | from SlicerDevelopmentToolboxUtils.icons import Icons
18 |
19 |
20 | class NewCaseSelectionNameWidget(qt.QMessageBox, ModuleWidgetMixin):
21 |
22 | PREFIX = "Case"
23 | SUFFIX = "-" + datetime.date.today().strftime("%Y%m%d")
24 | SUFFIX_PATTERN = "-[0-9]{8}"
25 |
26 | def __init__(self, destination, parent=None):
27 | super(NewCaseSelectionNameWidget, self).__init__(parent)
28 | self.CASE_NUMBER_OF_DIGITS = int(self.getSetting("CASE_NUMBER_OF_DIGITS", moduleName="SliceTracker"))
29 | self.PATTERN = self.PREFIX +"[0-9]{" + str(self.CASE_NUMBER_OF_DIGITS - 1) + "}[0-9]{1}" + self.SUFFIX_PATTERN
30 |
31 | if not os.path.exists(destination):
32 | raise OSError("Directory %s doesn't exist" % destination)
33 | self.destinationRoot = destination
34 | self.newCaseDirectory = None
35 | self.minimum = self.getNextCaseNumber()
36 | self.setupUI()
37 | self.setupConnections()
38 | self.onCaseNumberChanged(self.minimum)
39 |
40 | def getNextCaseNumber(self):
41 | caseNumber = 0
42 | for dirName in [dirName for dirName in os.listdir(self.destinationRoot)
43 | if os.path.isdir(os.path.join(self.destinationRoot, dirName)) and re.match(self.PATTERN, dirName)]:
44 | number = int(re.split(self.SUFFIX_PATTERN, dirName)[0].split(self.PREFIX)[1])
45 | caseNumber = caseNumber if caseNumber > number else number
46 | return caseNumber+1
47 |
48 | def setupUI(self):
49 | self.setWindowTitle("Case Number Selection")
50 | self.spinbox = qt.QSpinBox()
51 | self.spinbox.setRange(self.minimum, int("9" * self.CASE_NUMBER_OF_DIGITS))
52 |
53 | self.hideInvisibleUnneededComponents()
54 |
55 | self.textLabel = qt.QLabel("Please select a case number for the new case.")
56 | self.textLabel.setStyleSheet("font-weight: bold;")
57 | self.previewLabel = qt.QLabel("New case directory:")
58 | self.preview = qt.QLabel()
59 | self.notice = qt.QLabel()
60 | self.notice.setStyleSheet("color:red;")
61 |
62 | self.okButton = self.addButton(self.Ok)
63 | self.okButton.enabled = False
64 | self.cancelButton = self.addButton(self.Cancel)
65 | self.setDefaultButton(self.okButton)
66 |
67 | self.groupBox = qt.QGroupBox()
68 | self.groupBox.setLayout(qt.QGridLayout())
69 | self.groupBox.layout().addWidget(self.textLabel, 0, 0, 1, 2)
70 | self.groupBox.layout().addWidget(qt.QLabel("Proposed Case Number"), 1, 0)
71 | self.groupBox.layout().addWidget(self.spinbox, 1, 1)
72 | self.groupBox.layout().addWidget(self.previewLabel, 2, 0, 1, 2)
73 | self.groupBox.layout().addWidget(self.preview, 3, 0, 1, 2)
74 | self.groupBox.layout().addWidget(self.notice, 4, 0, 1, 2)
75 |
76 | self.groupBox.layout().addWidget(self.okButton, 5, 0)
77 | self.groupBox.layout().addWidget(self.cancelButton, 5, 1)
78 |
79 | self.layout().addWidget(self.groupBox, 1, 1)
80 |
81 | def hideInvisibleUnneededComponents(self):
82 | for oName in ["qt_msgbox_label", "qt_msgboxex_icon_label"]:
83 | try:
84 | slicer.util.findChild(self, oName).hide()
85 | except RuntimeError:
86 | pass
87 |
88 | def setupConnections(self):
89 | self.spinbox.valueChanged.connect(self.onCaseNumberChanged)
90 |
91 | def onCaseNumberChanged(self, caseNumber):
92 | formatString = '%0' + str(self.CASE_NUMBER_OF_DIGITS) + 'd'
93 | caseNumber = formatString % caseNumber
94 | directory = self.PREFIX+caseNumber+self.SUFFIX
95 | self.newCaseDirectory = os.path.join(self.destinationRoot, directory)
96 | self.preview.setText( self.newCaseDirectory)
97 | self.okButton.enabled = not os.path.exists(self.newCaseDirectory)
98 | self.notice.text = "" if not os.path.exists(self.newCaseDirectory) else "Note: Directory already exists."
99 |
100 |
101 | class SeriesTypeManager(LogicBase):
102 |
103 | SeriesTypeManuallyAssignedEvent = vtk.vtkCommand.UserEvent + 2334
104 |
105 | MODULE_NAME = constants.MODULE_NAME
106 |
107 | __metaclass__ = Singleton
108 |
109 | assignedSeries = {}
110 |
111 | def __init__(self):
112 | LogicBase.__init__(self)
113 | self.seriesTypes = self.getSetting("SERIES_TYPES")
114 |
115 | def clear(self):
116 | self.assignedSeries = {}
117 |
118 | def getSeriesType(self, series):
119 | try:
120 | return self.assignedSeries[series]
121 | except KeyError:
122 | self.assignedSeries[series] = self.computeSeriesType(series)
123 | return self.assignedSeries[series]
124 |
125 | def computeSeriesType(self, series):
126 | if self.getSetting("COVER_PROSTATE_PATTERN") in series:
127 | seriesType = constants.COVER_PROSTATE
128 | elif self.getSetting("COVER_TEMPLATE_PATTERN") in series:
129 | seriesType = constants.COVER_TEMPLATE
130 | elif self.getSetting("NEEDLE_IMAGE_PATTERN") in series:
131 | seriesType = constants.GUIDANCE_IMAGE
132 | elif self.getSetting("VIBE_IMAGE_PATTERN") in series:
133 | seriesType = constants.VIBE_IMAGE
134 | else:
135 | seriesType = constants.OTHER_IMAGE
136 | return seriesType
137 |
138 | def autoAssign(self, series):
139 | self.assignedSeries[series] = self.getSeriesType(series)
140 |
141 | def assign(self, series, seriesType=None):
142 | if series in self.assignedSeries.keys() and self.assignedSeries[series] == seriesType:
143 | return
144 | if seriesType:
145 | assert seriesType in self.seriesTypes
146 | self.assignedSeries[series] = seriesType
147 | self.invokeEvent(self.SeriesTypeManuallyAssignedEvent)
148 | else:
149 | self.autoAssign(series)
150 |
151 | def isCoverProstate(self, series):
152 | return self._hasSeriesType(series, constants.COVER_PROSTATE)
153 |
154 | def isCoverTemplate(self, series):
155 | return self._hasSeriesType(series, constants.COVER_TEMPLATE)
156 |
157 | def isGuidance(self, series):
158 | return self._hasSeriesType(series, constants.GUIDANCE_IMAGE)
159 |
160 | def isVibe(self, series):
161 | return self._hasSeriesType(series, constants.VIBE_IMAGE)
162 |
163 | def isOther(self, series):
164 | return self._hasSeriesType(series, constants.OTHER_IMAGE) or not (self.isCoverProstate(series) or
165 | self.isCoverTemplate(series) or
166 | self.isGuidance(series) or
167 | self.isVibe(series))
168 |
169 | def _hasSeriesType(self, series, seriesType):
170 | return self.getSeriesType(series) == seriesType
171 |
172 |
173 | class IncomingDataMessageBox(ExtendedQMessageBox):
174 |
175 | def __init__(self, parent=None):
176 | super(IncomingDataMessageBox, self).__init__(parent)
177 | self.setWindowTitle("Incoming image data")
178 | self.setText("New data has been received. What do you want do?")
179 | self.setIcon(qt.QMessageBox.Question)
180 | trackButton = self.addButton(qt.QPushButton('Track targets'), qt.QMessageBox.AcceptRole)
181 | self.addButton(qt.QPushButton('Postpone'), qt.QMessageBox.NoRole)
182 | self.setDefaultButton(trackButton)
183 |
184 |
185 | class SeriesTypeToolButton(qt.QToolButton, ModuleBase, ModuleWidgetMixin):
186 |
187 | MODULE_NAME = constants.MODULE_NAME
188 |
189 | class SeriesTypeListWidget(qt.QListWidget, ModuleWidgetMixin):
190 |
191 | @property
192 | def series(self):
193 | return self._series
194 |
195 | @series.setter
196 | def series(self, value):
197 | self._series = value
198 | self._preselectSeriesType()
199 |
200 | def __init__(self, series=None):
201 | qt.QListWidget.__init__(self)
202 | self.seriesTypeManager = SeriesTypeManager()
203 | self._series = series
204 | self.setup()
205 | self.setupConnections()
206 |
207 | def setup(self):
208 | self.clear()
209 | for index, seriesType in enumerate(self.seriesTypeManager.seriesTypes):
210 | self.addItem(seriesType)
211 | self.setFixedSize(self.sizeHintForColumn(0) + 2 * self.frameWidth,
212 | self.sizeHintForRow(0) * self.count + 2 * self.frameWidth)
213 | self._preselectSeriesType()
214 |
215 | def _preselectSeriesType(self):
216 | if not self._series:
217 | self.setCurrentItem(None)
218 | seriesType = self.seriesTypeManager.getSeriesType(self._series)
219 | for index in range(self.count):
220 | if seriesType == self.item(index).text():
221 | self.setCurrentItem(self.item(index))
222 | return
223 |
224 | def setupConnections(self):
225 | self.itemSelectionChanged.connect(self.onSelectionChanged)
226 |
227 | def onSelectionChanged(self):
228 | currentSeriesType = self.seriesTypeManager.getSeriesType(self._series)
229 | selectedSeriesType = self.selectedItems()[0].text()
230 | if selectedSeriesType != currentSeriesType:
231 | if slicer.util.confirmYesNoDisplay("You are about to change the series type for series:\n\n"
232 | "Name: {0} \n\n"
233 | "Current series type: {1} \n"
234 | "New series type: {2} \n\n"
235 | "This could have significant impact on the workflow.\n\n"
236 | "Do you want to proceed?".format(self._series, currentSeriesType,
237 | selectedSeriesType)):
238 | self.seriesTypeManager.assign(self._series, self.selectedItems()[0].text())
239 | else:
240 | self._preselectSeriesType()
241 |
242 | def __init__(self):
243 | qt.QToolButton.__init__(self)
244 | ModuleBase.__init__(self)
245 | self.setPopupMode(self.InstantPopup)
246 | self.setMenu(qt.QMenu(self))
247 | self.action = qt.QWidgetAction(self)
248 | self.listWidget = None
249 | self.setIcon(Icons.edit)
250 | self.enabled = False
251 |
252 | @logmethod(logging.DEBUG)
253 | def setSeries(self, series):
254 | if not self.listWidget:
255 | self.listWidget = self.SeriesTypeListWidget(series)
256 | self.action.setDefaultWidget(self.listWidget)
257 | self.menu().addAction(self.action)
258 | seriesTypeManager = SeriesTypeManager()
259 | seriesTypeManager.addEventObserver(seriesTypeManager.SeriesTypeManuallyAssignedEvent,
260 | lambda caller, event: self.menu().close())
261 | else:
262 | self.listWidget.series = series
263 | self.updateTooltipAndIcon(series)
264 |
265 | def updateTooltipAndIcon(self, series):
266 | seriesTypeManager = SeriesTypeManager()
267 | currentType = seriesTypeManager.getSeriesType(series)
268 | autoType = seriesTypeManager.computeSeriesType(series)
269 | if currentType != autoType:
270 | icon = Icons.info
271 | tooltip = "Series type assignment changed!\n\n" \
272 | "Auto-calculated series type: {}\n\n" \
273 | "Assigned series type: {}".format(autoType, currentType)
274 | else:
275 | icon = Icons.edit
276 | tooltip = self.listWidget.currentItem().text() if self.listWidget.currentItem() else ""
277 | self.setIcon(icon)
278 | self.setToolTip(tooltip)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/preopHandler.py:
--------------------------------------------------------------------------------
1 | import os
2 | import slicer
3 | import qt
4 | import vtk
5 | import xml
6 | import getpass
7 | import datetime
8 | import logging
9 | import re
10 |
11 | from SlicerDevelopmentToolboxUtils.mixins import ModuleWidgetMixin, ModuleLogicMixin
12 | from SlicerDevelopmentToolboxUtils.widgets import CustomStatusProgressbar, SliceWidgetConfirmYesNoDialog
13 | from SlicerDevelopmentToolboxUtils.constants import FileExtension
14 | from SlicerDevelopmentToolboxUtils.decorators import onReturnProcessEvents
15 | from SlicerDevelopmentToolboxUtils.exceptions import PreProcessedDataError, NoEligibleSeriesFoundError
16 |
17 | from .algorithms.automaticProstateSegmentation import AutomaticSegmentationLogic
18 | from .steps.plugins.segmentationValidator import SliceTrackerSegmentationValidatorPlugin
19 | from .constants import SliceTrackerConstants
20 | from .sessionData import SegmentationData, PreopData
21 |
22 |
23 | class PreopDataHandler(ModuleWidgetMixin, ModuleLogicMixin):
24 |
25 | MODULE_NAME = SliceTrackerConstants.MODULE_NAME
26 |
27 | PreprocessingStartedEvent = vtk.vtkCommand.UserEvent + 434
28 | PreprocessingFinishedEvent = vtk.vtkCommand.UserEvent + 435
29 | PreprocessedDataErrorEvent = vtk.vtkCommand.UserEvent + 436
30 |
31 | @staticmethod
32 | def getFirstMpReviewPreprocessedStudy(directory):
33 | # TODO add check here and selected the one which has targets in it
34 | # TODO: if several studies are available provide a drop down or anything similar for choosing
35 | directoryNames = filter(os.path.isdir, [os.path.join(directory, f) for f in os.listdir(directory)])
36 | return None if not len(directoryNames) else directoryNames[0]
37 |
38 | @staticmethod
39 | def wasDirectoryPreprocessed(directory):
40 | # TODO: this check needs to be more specific with expected sub directories
41 | firstDir = PreopDataHandler.getFirstMpReviewPreprocessedStudy(directory)
42 | if not firstDir:
43 | return False
44 | from mpReview import mpReviewLogic
45 | return mpReviewLogic.wasmpReviewPreprocessed(directory)
46 |
47 | @property
48 | def outputDirectory(self):
49 | self._outputDirectory = getattr(self, "_outputDirectory", None)
50 | return self._outputDirectory
51 |
52 | @outputDirectory.setter
53 | def outputDirectory(self, directory):
54 | if directory and not os.path.exists(directory):
55 | self.createDirectory(directory)
56 | self._outputDirectory = directory
57 |
58 | @property
59 | def segmentationData(self):
60 | try:
61 | return self.data.preopData.segmentation
62 | except AttributeError:
63 | return None
64 |
65 | @segmentationData.setter
66 | def segmentationData(self, value):
67 | assert self.preopData
68 | self.preopData.segmentation = value
69 |
70 | @property
71 | def preopData(self):
72 | return self.data.preopData
73 |
74 | @preopData.setter
75 | def preopData(self, value):
76 | self.data.preopData = value
77 |
78 | def __init__(self, inputDirectory, outputDirectory, data):
79 | self._inputDirectory = inputDirectory
80 | self.outputDirectory = outputDirectory
81 | self.data = data
82 |
83 | def handle(self):
84 | if self._runPreProcessing():
85 | self.runModule()
86 | else:
87 | slicer.util.infoDisplay("No DICOM data could be processed. Please select another directory.",
88 | windowTitle="SliceTracker")
89 |
90 | def _runPreProcessing(self):
91 | from mpReviewPreprocessor import mpReviewPreprocessorLogic
92 | mpReviewPreprocessorLogic = mpReviewPreprocessorLogic()
93 | progress = self.createProgressDialog()
94 | progress.canceled.connect(lambda: mpReviewPreprocessorLogic.cancelProcess())
95 |
96 | @onReturnProcessEvents
97 | def updateProgressBar(**kwargs):
98 | for key, value in kwargs.iteritems():
99 | if hasattr(progress, key):
100 | setattr(progress, key, value)
101 |
102 | success = mpReviewPreprocessorLogic.importAndProcessData(self._inputDirectory, outputDir=self.outputDirectory,
103 | copyDICOM=False,
104 | progressCallback=updateProgressBar)
105 | progress.canceled.disconnect(lambda: mpReviewPreprocessorLogic.cancelProcess())
106 | progress.close()
107 | return success
108 |
109 | def runModule(self, invokeEvent=True):
110 | if invokeEvent:
111 | self.invokeEvent(self.PreprocessingStartedEvent)
112 |
113 | def onModuleReturn():
114 | slicer.modules.mpReviewWidget.saveButton.clicked.disconnect(onModuleReturn)
115 | self.layoutManager.selectModule(self.MODULE_NAME)
116 | slicer.mrmlScene.Clear(0)
117 | self.loadPreProcessedData()
118 |
119 | self.setSetting('InputLocation', None, moduleName="mpReview")
120 | self.layoutManager.selectModule("mpReview")
121 | mpReview = slicer.modules.mpReviewWidget
122 | self.setSetting('InputLocation', self.outputDirectory, moduleName="mpReview")
123 | mpReview.onReload()
124 | slicer.modules.mpReviewWidget.saveButton.clicked.connect(onModuleReturn)
125 | self.layoutManager.selectModule(mpReview.moduleName)
126 |
127 | def loadPreProcessedData(self):
128 | try:
129 | self.loadMpReviewProcessedData()
130 | except PreProcessedDataError as exc:
131 | self.onPreopLoadingFailed(str(exc))
132 | except NoEligibleSeriesFoundError as exc:
133 | slicer.util.errorDisplay(str(exc), windowTitle="SliceTracker")
134 | self.invokeEvent(self.PreprocessedDataErrorEvent)
135 |
136 | def loadMpReviewProcessedData(self):
137 | studyDir = self.getFirstMpReviewPreprocessedStudy(self.outputDirectory)
138 | resourcesDir = os.path.join(studyDir, 'RESOURCES')
139 | if not self.isMpReviewStudyDirectoryValid(resourcesDir):
140 | raise PreProcessedDataError
141 |
142 | self.preopImagePath, self.preopSegmentationPath = self.findPreopImageAndSegmentationPaths(resourcesDir)
143 |
144 | self.data.initialTargetsPath = os.path.join(studyDir, 'Targets')
145 |
146 | loadedPreopVolume = self.loadPreopVolume()
147 | loadedPreopTargets = self.loadPreopTargets()
148 | loadedPreopT2Label = self.loadT2Label() if os.path.exists(self.preopSegmentationPath) else False
149 | success = all(r is True for r in [loadedPreopT2Label, loadedPreopVolume, loadedPreopTargets])
150 |
151 | if success:
152 | if not self.data.usePreopData:
153 | self._createPreopData(algorithm="Manual")
154 | self.segmentationData.note = "mpReview preprocessed"
155 | self.segmentationData._label = self.data.initialLabel
156 | self.invokeEvent(self.PreprocessingFinishedEvent)
157 | else:
158 | useDeepLearning = str(self.getSetting("Use_Deep_Learning", moduleName=self.MODULE_NAME)).lower() == "true"
159 | if loadedPreopTargets and loadedPreopVolume and useDeepLearning:
160 | if slicer.util.confirmYesNoDisplay("No WholeGland segmentation found in preop data. Automatic segmentation is "
161 | "available. Would you like to proceed with the automatic segmentation?",
162 | windowTitle="SliceTracker"):
163 | self._createPreopData(algorithm="Automatic")
164 | self.runAutomaticSegmentation()
165 | return
166 | raise PreProcessedDataError
167 |
168 | def onPreopLoadingFailed(self, detailed=None, offerRevisit=True):
169 | detailed = detailed if detailed else "Make sure that the correct mpReview directory structure is used. " \
170 | "\n\nSliceTracker expects a T2 volume, WholeGland segmentation and target(s)."
171 | message = "Loading preop data failed. Do you want to " \
172 | "open/revisit pre-processing for the current case?"
173 | if slicer.util.confirmYesNoDisplay(message, windowTitle="SliceTracker", detailedText=detailed):
174 | self.runModule()
175 | else:
176 | self.invokeEvent(self.PreprocessedDataErrorEvent)
177 |
178 | def _createPreopData(self, algorithm, segmentationType="Prostate"):
179 | self.preopData = PreopData()
180 | self.segmentationData = SegmentationData(segmentationType=segmentationType, algorithm=algorithm)
181 |
182 | def runAutomaticSegmentation(self):
183 | logic = AutomaticSegmentationLogic()
184 | logic.addEventObserver(logic.DeepLearningFinishedEvent, self.onSegmentationFinished)
185 | logic.addEventObserver(logic.DeepLearningFailedEvent,
186 | lambda c,e: self.onPreopLoadingFailed("Automatic segmentation failed."))
187 | customStatusProgressBar = CustomStatusProgressbar()
188 | customStatusProgressBar.text = "Running DeepInfer for automatic prostate segmentation"
189 |
190 | from mpReview import mpReviewLogic
191 | mpReviewColorNode, _ = mpReviewLogic.loadColorTable(self.getSetting("Color_File_Name", moduleName=self.MODULE_NAME))
192 |
193 | domain = 'BWH_WITHOUT_ERC'
194 | prompt = SliceWidgetConfirmYesNoDialog(self.data.initialVolume,
195 | "Was an endorectal coil used during preop acquisition?").exec_()
196 |
197 | self.preopData.usedERC = False
198 | if prompt == qt.QDialogButtonBox.Yes:
199 | self.preopData.usedERC = True
200 | domain = 'BWH_WITH_ERC'
201 | elif prompt == qt.QDialogButtonBox.Cancel:
202 | raise PreProcessedDataError
203 |
204 | self.segmentationData.startTime = self.getTime()
205 | logic.run(self.data.initialVolume, domain, mpReviewColorNode)
206 |
207 | @vtk.calldata_type(vtk.VTK_OBJECT)
208 | def onSegmentationValidated(self, caller, event, labelNode):
209 | if "_modified" in labelNode.GetName():
210 | self.segmentationData.userModified["endTime"] = self.getTime()
211 | self.segmentationData._modifiedLabel = labelNode
212 | else:
213 | self.segmentationData.userModified = None
214 | if not self.preopSegmentationPath:
215 | self.createDirectory(self.preopSegmentationPath)
216 | segmentedColorName = self.getSetting("Segmentation_Color_Name", moduleName=self.MODULE_NAME)
217 | timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
218 | filename = getpass.getuser() + '-' + segmentedColorName + '-' + timestamp
219 | self.saveNodeData(labelNode, self.preopSegmentationPath, extension=FileExtension.NRRD, name=filename)
220 | self.loadPreProcessedData()
221 | customStatusProgressBar = CustomStatusProgressbar()
222 | customStatusProgressBar.text = "Done automatic prostate segmentation"
223 |
224 | @vtk.calldata_type(vtk.VTK_OBJECT)
225 | def onSegmentationFinished(self, caller, event, labelNode):
226 | self.segmentationData.endTime = self.getTime()
227 | self.segmentationData._label = labelNode
228 | segmentationValidator = SliceTrackerSegmentationValidatorPlugin(self.data.initialVolume, labelNode)
229 | segmentationValidator.addEventObserver(segmentationValidator.StartedEvent, lambda c, e: self.invokeEvent(e))
230 | segmentationValidator.addEventObserver(segmentationValidator.ModifiedEvent, self.onSegmentationModificationStarted)
231 | segmentationValidator.addEventObserver(segmentationValidator.FinishedEvent, self.onSegmentationValidated)
232 | segmentationValidator.addEventObserver(segmentationValidator.CanceledEvent,
233 | lambda c, e: self.onPreopLoadingFailed("Segmentation validation canceled."))
234 | segmentationValidator.run()
235 |
236 | def onSegmentationModificationStarted(self, caller, event):
237 | self.segmentationData.setModified(startTime=self.getTime())
238 |
239 | def isMpReviewStudyDirectoryValid(self, resourcesDir):
240 | if not os.path.exists(resourcesDir):
241 | logging.debug("The selected directory `{}` does not fit the mpReview directory structure. Make sure that you select "
242 | "the study root directory which includes directories RESOURCES".format(resourcesDir))
243 | return False
244 | return True
245 |
246 | def findPreopImageAndSegmentationPaths(self, resourcesDir):
247 | from mpReview import mpReviewLogic
248 | seriesMap, _ = mpReviewLogic.loadMpReviewProcessedData(resourcesDir)
249 |
250 | regex = self.getSetting("PLANNING_IMAGE_PATTERN", moduleName=self.MODULE_NAME)
251 |
252 | for series in seriesMap:
253 | seriesName = str(seriesMap[series]['LongName'])
254 | logging.debug('series Number ' + series + ' ' + seriesName)
255 |
256 | xmlFile = os.path.join(seriesMap[series]['NRRDLocation']).replace(".nrrd", ".xml")
257 | segmentationsPath = os.path.join(os.path.dirname(os.path.dirname(xmlFile)), 'Segmentations')
258 |
259 | dom = xml.dom.minidom.parse(xmlFile)
260 | seriesDescription = self.findElement(dom, "SeriesDescription")
261 | if re.match(regex, seriesDescription) or seriesDescription == regex:
262 | return seriesMap[series]['NRRDLocation'], segmentationsPath
263 |
264 | raise NoEligibleSeriesFoundError("No eligible series found for preop AX T2 segmentation. MpReview might not have "
265 | "processed the right series or series names are different from BWH internally used ones")
266 |
267 | def loadT2Label(self):
268 | if self.data.initialLabel and self.data.initialLabel.GetScene() is not None:
269 | return True
270 | mostRecentFilename = self.getMostRecentWholeGlandSegmentation(self.preopSegmentationPath)
271 | success = False
272 | if mostRecentFilename:
273 | filename = os.path.join(self.preopSegmentationPath, mostRecentFilename)
274 | success, self.data.initialLabel = slicer.util.loadLabelVolume(filename, returnNode=True)
275 | if success:
276 | self.data.initialLabel.SetName('t2-label')
277 | return success
278 |
279 | def loadPreopVolume(self):
280 | if self.data.initialVolume and self.data.initialVolume.GetScene() is not None:
281 | return True
282 | success, self.data.initialVolume = slicer.util.loadVolume(self.preopImagePath, returnNode=True)
283 | if success:
284 | self.data.initialVolume.SetName('VOLUME-PREOP')
285 | return success
286 |
287 | def loadPreopTargets(self):
288 | if self.data.initialTargets and self.data.initialTargets.GetScene() is not None:
289 | return True
290 | if not os.path.exists(self.data.initialTargetsPath):
291 | return False
292 | mostRecentTargets = self.getMostRecentTargetsFile(self.data.initialTargetsPath)
293 | success = False
294 | if mostRecentTargets:
295 | filename = os.path.join(self.data.initialTargetsPath, mostRecentTargets)
296 | success, self.data.initialTargets = slicer.util.loadMarkupsFiducialList(filename, returnNode=True)
297 | if success:
298 | self.data.initialTargets.SetName('initialTargets')
299 | self.data.initialTargets.SetLocked(True)
300 | return success
301 |
302 | def getMostRecentWholeGlandSegmentation(self, path):
303 | return self.getMostRecentFile(path, FileExtension.NRRD, filter="WholeGland")
304 |
305 | def getMostRecentTargetsFile(self, path):
306 | return self.getMostRecentFile(path, FileExtension.FCSV)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/SliceTrackerUtils/steps/__init__.py
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/base.py:
--------------------------------------------------------------------------------
1 | import vtk
2 | from SlicerDevelopmentToolboxUtils.decorators import beforeRunProcessEvents, onModuleSelected
3 | from SlicerDevelopmentToolboxUtils.module.base import WidgetBase
4 | from SlicerDevelopmentToolboxUtils.module.logic import SessionBasedLogicBase
5 |
6 | from ..constants import SliceTrackerConstants as constants
7 | from ..session import SliceTrackerSession
8 |
9 |
10 | class SliceTrackerWidgetBase(WidgetBase):
11 |
12 | MODULE_NAME = constants.MODULE_NAME
13 |
14 | SessionClass = SliceTrackerSession
15 |
16 | AvailableLayoutsChangedEvent = vtk.vtkCommand.UserEvent + 4233
17 |
18 | def __init__(self):
19 | super(SliceTrackerWidgetBase, self).__init__()
20 |
21 | def setup(self):
22 | self.setupSliceWidgets()
23 | self.setupAdditionalViewSettingButtons()
24 |
25 | def setupSliceWidgets(self):
26 | self.createSliceWidgetClassMembers("Red")
27 | self.createSliceWidgetClassMembers("Yellow")
28 | self.createSliceWidgetClassMembers("Green")
29 |
30 | def setupAdditionalViewSettingButtons(self):
31 | pass
32 |
33 | def addSessionObservers(self):
34 | super(SliceTrackerWidgetBase, self).addSessionObservers()
35 | self.session.addEventObserver(self.session.NewImageSeriesReceivedEvent, self.onNewImageSeriesReceived)
36 | self.session.addEventObserver(self.session.CurrentSeriesChangedEvent, self.onCurrentSeriesChanged)
37 | self.session.addEventObserver(self.session.LoadingMetadataSuccessfulEvent, self.onLoadingMetadataSuccessful)
38 | self.session.addEventObserver(self.session.PreprocessingSuccessfulEvent, self.onPreprocessingSuccessful)
39 |
40 | def removeSessionEventObservers(self):
41 | super(SliceTrackerWidgetBase, self).removeSessionEventObservers()
42 | self.session.removeEventObserver(self.session.NewImageSeriesReceivedEvent, self.onNewImageSeriesReceived)
43 | self.session.removeEventObserver(self.session.CurrentSeriesChangedEvent, self.onCurrentSeriesChanged)
44 | self.session.removeEventObserver(self.session.LoadingMetadataSuccessfulEvent, self.onLoadingMetadataSuccessful)
45 | self.session.removeEventObserver(self.session.PreprocessingSuccessfulEvent, self.onPreprocessingSuccessful)
46 |
47 | def onActivation(self):
48 | self.layoutManager.layoutChanged.connect(self.onLayoutChanged)
49 | self.session.addEventObserver(self.session.CurrentResultChangedEvent, self.onCurrentResultChanged)
50 | super(SliceTrackerWidgetBase, self).onActivation()
51 |
52 | def onDeactivation(self):
53 | self.layoutManager.layoutChanged.disconnect(self.onLayoutChanged)
54 | self.session.removeEventObserver(self.session.CurrentResultChangedEvent, self.onCurrentResultChanged)
55 | super(SliceTrackerWidgetBase, self).onDeactivation()
56 |
57 | def onCurrentResultChanged(self, caller, event):
58 | pass
59 |
60 | @onModuleSelected(constants.MODULE_NAME)
61 | def onLayoutChanged(self, layout=None):
62 | pass
63 |
64 | def setAvailableLayouts(self, layouts):
65 | if not all([l in constants.ALLOWED_LAYOUTS for l in layouts]):
66 | raise ValueError("Not all of the delivered layouts are allowed to be used in SliceTracker")
67 | self.invokeEvent(self.AvailableLayoutsChangedEvent, str(layouts))
68 |
69 | def addPlugin(self, plugin):
70 | super(SliceTrackerWidgetBase, self).addPlugin(plugin)
71 | plugin.addEventObserver(self.AvailableLayoutsChangedEvent, self.onPluginAvailableLayoutChanged)
72 |
73 | @vtk.calldata_type(vtk.VTK_STRING)
74 | def onPluginAvailableLayoutChanged(self, caller, event, callData):
75 | self.invokeEvent(self.AvailableLayoutsChangedEvent, callData)
76 |
77 | def resetViewSettingButtons(self):
78 | pass
79 |
80 | @vtk.calldata_type(vtk.VTK_STRING)
81 | def onNewImageSeriesReceived(self, caller, event, callData):
82 | pass
83 |
84 | @vtk.calldata_type(vtk.VTK_STRING)
85 | def onCurrentSeriesChanged(self, caller, event, callData=None):
86 | pass
87 |
88 | def onLoadingMetadataSuccessful(self, caller, event):
89 | pass
90 |
91 | def onPreprocessingSuccessful(self, caller, event):
92 | pass
93 |
94 | def setupFourUpView(self, volume, clearLabels=True):
95 | self.layoutManager.setLayout(constants.LAYOUT_FOUR_UP)
96 | self.setBackgroundToVolumeID(volume, clearLabels)
97 |
98 | def setBackgroundToVolumeID(self, volume, clearLabels=True, showLabelOutline=True):
99 | super(SliceTrackerWidgetBase, self).setBackgroundToVolumeID(volume, clearLabels, showLabelOutline)
100 | self.setDefaultOrientation()
101 |
102 | def setDefaultOrientation(self):
103 | self.redSliceNode.SetOrientationToAxial()
104 | self.yellowSliceNode.SetOrientationToSagittal()
105 | self.greenSliceNode.SetOrientationToCoronal()
106 | self.updateFOV() # TODO: shall not be called here
107 |
108 | def setAxialOrientation(self):
109 | for sliceNode in self._sliceNodes:
110 | sliceNode.SetOrientationToAxial()
111 | self.updateFOV() # TODO: shall not be called here
112 |
113 | def updateFOV(self):
114 | # if self.getSetting("COVER_TEMPLATE_PATTERN") in self.intraopSeriesSelector.currentText:
115 | # self.setDefaultFOV(self.redSliceLogic, 1.0)
116 | # self.setDefaultFOV(self.yellowSliceLogic, 1.0)
117 | # self.setDefaultFOV(self.greenSliceLogic, 1.0)
118 | # el
119 | if self.layoutManager.layout == constants.LAYOUT_RED_SLICE_ONLY:
120 | self.setDefaultFOV(self.redSliceLogic)
121 | elif self.layoutManager.layout == constants.LAYOUT_SIDE_BY_SIDE:
122 | self.setDefaultFOV(self.redSliceLogic)
123 | self.setDefaultFOV(self.yellowSliceLogic)
124 | elif self.layoutManager.layout in [constants.LAYOUT_FOUR_UP_QUANTITATIVE, constants.LAYOUT_FOUR_UP]:
125 | self.setDefaultFOV(self.redSliceLogic)
126 | self.yellowSliceLogic.FitSliceToAll()
127 | self.greenSliceLogic.FitSliceToAll()
128 |
129 | @beforeRunProcessEvents
130 | def setDefaultFOV(self, sliceLogic, factor=0.5):
131 | sliceLogic.FitSliceToAll()
132 | FOV = sliceLogic.GetSliceNode().GetFieldOfView()
133 | self.setFOV(sliceLogic, [FOV[0] * factor, FOV[1] * factor, FOV[2]])
134 |
135 | def setupRedSlicePreview(self, selectedSeries):
136 | self.layoutManager.setLayout(constants.LAYOUT_RED_SLICE_ONLY)
137 | self.hideAllFiducialNodes()
138 | try:
139 | result = self.session.data.getResultsBySeries(selectedSeries)[0]
140 | volume = result.volumes.fixed
141 | except IndexError:
142 | volume = self.session.getOrCreateVolumeForSeries(selectedSeries)
143 | self.setBackgroundToVolumeID(volume)
144 |
145 |
146 | class SliceTrackerStep(SliceTrackerWidgetBase):
147 |
148 | def __init__(self):
149 | self.viewSettingButtons = []
150 | self.parameterNode.SetAttribute("Name", self.NAME)
151 | super(SliceTrackerStep, self).__init__()
152 |
153 |
154 | class SliceTrackerLogicBase(SessionBasedLogicBase):
155 |
156 | MODULE_NAME = constants.MODULE_NAME
157 | SessionClass = SliceTrackerSession
158 |
159 | def __init__(self):
160 | super(SliceTrackerLogicBase, self).__init__()
161 |
162 |
163 | class SliceTrackerPlugin(SliceTrackerWidgetBase):
164 |
165 | def __init__(self):
166 | super(SliceTrackerPlugin, self).__init__()
167 |
168 | def clearData(self):
169 | pass
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/evaluation.py:
--------------------------------------------------------------------------------
1 | import os
2 | import qt
3 | import vtk
4 | import slicer
5 | from base import SliceTrackerLogicBase, SliceTrackerStep
6 | from ..constants import SliceTrackerConstants as constants
7 | from plugins.results import SliceTrackerRegistrationResultsPlugin
8 | from plugins.targets import SliceTrackerTargetTablePlugin
9 | from plugins.charts import SliceTrackerDisplacementChartPlugin
10 |
11 | from SlicerDevelopmentToolboxUtils.icons import Icons
12 |
13 |
14 | class SliceTrackerEvaluationStepLogic(SliceTrackerLogicBase):
15 |
16 | def __init__(self):
17 | super(SliceTrackerEvaluationStepLogic, self).__init__()
18 |
19 |
20 | class SliceTrackerEvaluationStep(SliceTrackerStep):
21 |
22 | NAME = "Evaluation"
23 | LogicClass = SliceTrackerEvaluationStepLogic
24 | LayoutClass = qt.QVBoxLayout
25 |
26 | def __init__(self):
27 | self.modulePath = os.path.dirname(slicer.util.modulePath(self.MODULE_NAME)).replace(".py", "")
28 | super(SliceTrackerEvaluationStep, self).__init__()
29 | self.consentGivenBy = None
30 |
31 | def setup(self):
32 | super(SliceTrackerEvaluationStep, self).setup()
33 | self.setupRegistrationValidationButtons()
34 |
35 | self.regResultsPlugin = SliceTrackerRegistrationResultsPlugin()
36 | self.addPlugin(self.regResultsPlugin)
37 | self.regResultsPlugin.addEventObserver(self.regResultsPlugin.RegistrationTypeSelectedEvent,
38 | self.onRegistrationTypeSelected)
39 | self.regResultsPlugin.addEventObserver(self.regResultsPlugin.NoRegistrationResultsAvailable,
40 | self.onNoRegistrationResultsAvailable)
41 | self.regResultsPlugin.addEventObserver(self.regResultsPlugin.RegistrationResultsAvailable,
42 | self.onRegistrationResultsAvailable)
43 |
44 | self.targetTablePlugin = SliceTrackerTargetTablePlugin(movingEnabled=True)
45 | self.addPlugin(self.targetTablePlugin)
46 |
47 | self.displacementChartPlugin = SliceTrackerDisplacementChartPlugin()
48 | self.addPlugin(self.displacementChartPlugin)
49 |
50 | self.displacementChartPlugin.addEventObserver(self.displacementChartPlugin.ShowEvent, self.onShowDisplacementChart)
51 | self.displacementChartPlugin.addEventObserver(self.displacementChartPlugin.HideEvent, self.onHideDisplacementChart)
52 |
53 | self.layout().addWidget(self.regResultsPlugin)
54 | self.layout().addWidget(self.targetTablePlugin)
55 | self.layout().addWidget(self.registrationEvaluationButtonsGroupBox)
56 | self.layout().addWidget(self.displacementChartPlugin.collapsibleButton)
57 | self.layout().addStretch(1)
58 |
59 | def setupRegistrationValidationButtons(self):
60 | iconSize = qt.QSize(36, 36)
61 | self.approveRegistrationResultButton = self.createButton("", icon=Icons.thumbs_up, iconSize=iconSize,
62 | toolTip="Approve")
63 | self.retryRegistrationButton = self.createButton("", icon=Icons.retry, iconSize=iconSize, toolTip="Retry")
64 | self.rejectRegistrationResultButton = self.createButton("", icon=Icons.thumbs_down, iconSize=iconSize,
65 | toolTip="Reject")
66 | self.registrationEvaluationButtonsGroupBox = self.createHLayout([self.retryRegistrationButton,
67 | self.approveRegistrationResultButton,
68 | self.rejectRegistrationResultButton])
69 |
70 | def setupConnections(self):
71 | self.retryRegistrationButton.clicked.connect(self.onRetryRegistrationButtonClicked)
72 | self.approveRegistrationResultButton.clicked.connect(self.onApproveRegistrationResultButtonClicked)
73 | self.rejectRegistrationResultButton.clicked.connect(self.onRejectRegistrationResultButtonClicked)
74 | # self.registrationDetailsButton.clicked.connect(self.onShowRegistrationDetails)
75 |
76 | def addSessionObservers(self):
77 | super(SliceTrackerEvaluationStep, self).addSessionObservers()
78 | self.session.addEventObserver(self.session.InitiateEvaluationEvent, self.onInitiateEvaluation)
79 |
80 | def removeSessionEventObservers(self):
81 | super(SliceTrackerEvaluationStep, self).removeSessionEventObservers()
82 | self.session.removeEventObserver(self.session.InitiateEvaluationEvent, self.onInitiateEvaluation)
83 |
84 | def onShowDisplacementChart(self, caller, event):
85 | if self.layoutManager.layout != constants.LAYOUT_FOUR_UP_QUANTITATIVE:
86 | self.displacementChartPlugin.collapsibleButton.show()
87 |
88 | def onHideDisplacementChart(self, caller, event):
89 | self.displacementChartPlugin.collapsibleButton.hide()
90 |
91 | def onRetryRegistrationButtonClicked(self):
92 | self.session.retryRegistration()
93 |
94 | def onApproveRegistrationResultButtonClicked(self):
95 | self.consentGivenBy = self.session._getConsent()
96 | if self.consentGivenBy:
97 | results = self.session.data.getResultsBySeriesNumber(self.currentResult.seriesNumber)
98 | for result in [r for r in results if r is not self.currentResult]:
99 | result.reject(self.consentGivenBy)
100 |
101 | if self.session.seriesTypeManager.isCoverProstate(self.session.currentResult.name) and \
102 | self.session.data.getMostRecentApprovedCoverProstateRegistration() is not None:
103 | self.session.data.getMostRecentApprovedCoverProstateRegistration().skip()
104 |
105 | self.currentResult.approve(registrationType=self.regResultsPlugin.registrationButtonGroup.checkedButton().name,
106 | consentedBy=self.consentGivenBy)
107 |
108 | def onRejectRegistrationResultButtonClicked(self):
109 | self.consentGivenBy = self.session._getConsent()
110 | if self.consentGivenBy:
111 | results = self.session.data.getResultsBySeriesNumber(self.currentResult.seriesNumber)
112 | for result in [r for r in results if r is not self.currentResult]:
113 | result.reject(consentedBy=self.consentGivenBy)
114 | self.currentResult.reject(consentedBy=self.consentGivenBy)
115 |
116 | def onInitiateEvaluation(self, caller, event):
117 | self.active = True
118 |
119 | def onActivation(self):
120 | super(SliceTrackerEvaluationStep, self).onActivation()
121 | self.consentGivenBy = None
122 | if not self.currentResult:
123 | return
124 | # self.redOnlyLayoutButton.enabled = False
125 | # self.sideBySideLayoutButton.enabled = True
126 | self.rejectRegistrationResultButton.enabled = not self.session.seriesTypeManager.isCoverProstate(self.currentResult.name)
127 | self.currentResult.save(self.session.outputDirectory)
128 | self.currentResult.printSummary()
129 |
130 | def onDeactivation(self):
131 | super(SliceTrackerEvaluationStep, self).onDeactivation()
132 | self.hideAllLabels()
133 | self.hideAllFiducialNodes()
134 |
135 | @vtk.calldata_type(vtk.VTK_STRING)
136 | def onRegistrationTypeSelected(self, caller, event, callData):
137 | self.targetTablePlugin.currentTargets = getattr(self.currentResult.targets, callData)
138 |
139 | def onNoRegistrationResultsAvailable(self, caller, event):
140 | self.targetTablePlugin.currentTargets = None
141 | self.approveRegistrationResultButton.enabled = False
142 |
143 | def onRegistrationResultsAvailable(self, caller, event):
144 | self.approveRegistrationResultButton.enabled = True
145 | self.targetTablePlugin.enabled = True
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/SliceTrackerUtils/steps/plugins/__init__.py
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/case.py:
--------------------------------------------------------------------------------
1 | import slicer
2 | import os
3 | import ctk
4 | import vtk
5 | import qt
6 |
7 | from ...helpers import NewCaseSelectionNameWidget
8 | from ..base import SliceTrackerPlugin, SliceTrackerLogicBase
9 |
10 | from SlicerDevelopmentToolboxUtils.helpers import WatchBoxAttribute
11 | from SlicerDevelopmentToolboxUtils.widgets import BasicInformationWatchBox
12 | from SlicerDevelopmentToolboxUtils.icons import Icons
13 | from SlicerDevelopmentToolboxUtils.mixins import ModuleLogicMixin
14 |
15 |
16 | class SliceTrackerCaseManagerLogic(SliceTrackerLogicBase):
17 |
18 | def __init__(self):
19 | super(SliceTrackerCaseManagerLogic, self).__init__()
20 |
21 |
22 | class SliceTrackerCaseManagerPlugin(SliceTrackerPlugin):
23 |
24 | LogicClass = SliceTrackerCaseManagerLogic
25 | NAME = "CaseManager"
26 |
27 | @property
28 | def caseRootDir(self):
29 | return self.casesRootDirectoryButton.directory
30 |
31 | @caseRootDir.setter
32 | def caseRootDir(self, path):
33 | try:
34 | exists = os.path.exists(path)
35 | except TypeError:
36 | exists = False
37 | self.setSetting('CasesRootLocation', path if exists else None)
38 | self.casesRootDirectoryButton.text = ModuleLogicMixin.truncatePath(path) if exists else "Choose output directory"
39 | self.casesRootDirectoryButton.toolTip = path
40 | self.openCaseButton.enabled = exists
41 | self.createNewCaseButton.enabled = exists
42 |
43 | def __init__(self):
44 | super(SliceTrackerCaseManagerPlugin, self).__init__()
45 | self.caseRootDir = self.getSetting('CasesRootLocation', self.MODULE_NAME)
46 | slicer.app.connect('aboutToQuit()', self.onSlicerQuits)
47 |
48 | def onSlicerQuits(self):
49 | if self.session.isRunning():
50 | self.onCloseCaseButtonClicked()
51 |
52 | def clearData(self):
53 | self.update()
54 |
55 | def setup(self):
56 | super(SliceTrackerCaseManagerPlugin, self).setup()
57 | iconSize = qt.QSize(36, 36)
58 | self.createNewCaseButton = self.createButton("", icon=Icons.new, iconSize=iconSize, toolTip="Start a new case")
59 | self.openCaseButton = self.createButton("", icon=Icons.open, iconSize=iconSize, toolTip="Open case")
60 | self.closeCaseButton = self.createButton("", icon=Icons.exit, iconSize=iconSize,
61 | toolTip="Close case with resume support", enabled=False)
62 | self.setupCaseWatchBox()
63 | self.casesRootDirectoryButton = self.createDirectoryButton(text="Choose cases root location",
64 | caption="Choose cases root location",
65 | directory=self.getSetting('CasesRootLocation',
66 | self.MODULE_NAME))
67 | self.caseDirectoryInformationArea = ctk.ctkCollapsibleButton()
68 | self.caseDirectoryInformationArea.collapsed = True
69 | self.caseDirectoryInformationArea.text = "Directory Settings"
70 | self.directoryConfigurationLayout = qt.QGridLayout(self.caseDirectoryInformationArea)
71 | self.directoryConfigurationLayout.addWidget(qt.QLabel("Cases Root Directory"), 1, 0, 1, 1)
72 | self.directoryConfigurationLayout.addWidget(self.casesRootDirectoryButton, 1, 1, 1, 1)
73 | self.directoryConfigurationLayout.addWidget(self.caseWatchBox, 2, 0, 1, qt.QSizePolicy.ExpandFlag)
74 |
75 | self.caseGroupBox = qt.QGroupBox("Case")
76 | self.caseGroupBoxLayout = qt.QFormLayout(self.caseGroupBox)
77 | self.caseGroupBoxLayout.addWidget(self.createHLayout([self.createNewCaseButton, self.openCaseButton,
78 | self.closeCaseButton]))
79 | self.caseGroupBoxLayout.addWidget(self.caseDirectoryInformationArea)
80 | self.layout().addWidget(self.caseGroupBox)
81 |
82 | def setupCaseWatchBox(self):
83 | watchBoxInformation = [WatchBoxAttribute('CurrentCaseDirectory', 'Directory'),
84 | WatchBoxAttribute('CurrentPreopDICOMDirectory', 'Preop DICOM Directory: '),
85 | WatchBoxAttribute('CurrentIntraopDICOMDirectory', 'Intraop DICOM Directory: '),
86 | WatchBoxAttribute('mpReviewDirectory', 'mpReview Directory: ')]
87 | self.caseWatchBox = BasicInformationWatchBox(watchBoxInformation, title="Current Case")
88 |
89 | def setupConnections(self):
90 | self.createNewCaseButton.clicked.connect(self.onCreateNewCaseButtonClicked)
91 | self.openCaseButton.clicked.connect(self.onOpenCaseButtonClicked)
92 | self.closeCaseButton.clicked.connect(self.onCloseCaseButtonClicked)
93 | self.casesRootDirectoryButton.directoryChanged.connect(lambda: setattr(self, "caseRootDir",
94 | self.casesRootDirectoryButton.directory))
95 |
96 | def onCreateNewCaseButtonClicked(self):
97 | if not self.checkAndWarnUserIfCaseInProgress():
98 | return
99 | self.caseDialog = NewCaseSelectionNameWidget(self.caseRootDir)
100 | selectedButton = self.caseDialog.exec_()
101 | if selectedButton == qt.QMessageBox.Ok:
102 | self.session.createNewCase(self.caseDialog.newCaseDirectory)
103 |
104 | def onOpenCaseButtonClicked(self):
105 | if not self.checkAndWarnUserIfCaseInProgress():
106 | return
107 | self.session.directory = qt.QFileDialog.getExistingDirectory(self.parent().window(), "Select Case Directory",
108 | self.caseRootDir)
109 |
110 | def onCloseCaseButtonClicked(self):
111 | if not self.session.data.completed:
112 | dialog = qt.QMessageBox(qt.QMessageBox.Question, "SliceTracker", "Do you want to mark this case as completed?", qt.QMessageBox.Yes | qt.QMessageBox.No | qt.QMessageBox.Cancel, slicer.util.mainWindow(), qt.Qt.WindowStaysOnTopHint).exec_()
113 | if dialog == qt.QMessageBox.Yes:
114 | self.session.complete()
115 | elif dialog == qt.QMessageBox.Cancel:
116 | return
117 | if self.session.isRunning():
118 | self.session.close(save=False)
119 |
120 | def onNewCaseStarted(self, caller, event):
121 | self.update()
122 |
123 | def onCaseOpened(self, caller, event):
124 | self.update()
125 |
126 | def update(self):
127 | self.updateCaseButtons()
128 | self.updateCaseWatchBox()
129 |
130 | @vtk.calldata_type(vtk.VTK_STRING)
131 | def onCaseClosed(self, caller, event, callData):
132 | self.clearData()
133 |
134 | def onLoadingMetadataSuccessful(self, caller, event):
135 | self.updateCaseButtons()
136 |
137 | def updateCaseWatchBox(self):
138 | if not self.session.isRunning():
139 | self.caseWatchBox.reset()
140 | return
141 | self.caseWatchBox.setInformation("CurrentCaseDirectory", os.path.relpath(self.session.directory, self.caseRootDir),
142 | toolTip=self.session.directory)
143 | self.caseWatchBox.setInformation("CurrentPreopDICOMDirectory", os.path.relpath(self.session.preopDICOMDirectory,
144 | self.caseRootDir),
145 | toolTip=self.session.preopDICOMDirectory)
146 | self.caseWatchBox.setInformation("CurrentIntraopDICOMDirectory", os.path.relpath(self.session.intraopDICOMDirectory,
147 | self.caseRootDir),
148 | toolTip=self.session.intraopDICOMDirectory)
149 | self.caseWatchBox.setInformation("mpReviewDirectory", os.path.relpath(self.session.preprocessedDirectory,
150 | self.caseRootDir),
151 | toolTip=self.session.preprocessedDirectory)
152 |
153 | def updateCaseButtons(self):
154 | self.closeCaseButton.enabled = self.session.directory is not None
155 |
156 | def checkAndWarnUserIfCaseInProgress(self):
157 | if self.session.isRunning():
158 | if not slicer.util.confirmYesNoDisplay("Current case will be closed. Do you want to proceed?"):
159 | return False
160 | self.session.close()
161 | return True
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/charts.py:
--------------------------------------------------------------------------------
1 | import vtk
2 | import qt
3 | import ctk
4 | import ast
5 | import slicer
6 | import logging
7 |
8 | from ..base import SliceTrackerPlugin, SliceTrackerLogicBase, SliceTrackerStep
9 | from ...session import SliceTrackerSession
10 | from ...sessionData import RegistrationResult
11 | from ...constants import SliceTrackerConstants as constants
12 |
13 | from SlicerDevelopmentToolboxUtils.decorators import onModuleSelected
14 |
15 |
16 | class SliceTrackerDisplacementChartLogic(SliceTrackerLogicBase):
17 |
18 | def __init__(self):
19 | super(SliceTrackerDisplacementChartLogic, self).__init__()
20 | self.session = SliceTrackerSession()
21 |
22 | def calculateTargetDisplacement(self, prevTargets, currTargets, targetIndex):
23 | prevPos = [0.0, 0.0, 0.0]
24 | currPos = [0.0, 0.0, 0.0]
25 | currTargets.GetNthFiducialPosition(targetIndex, currPos)
26 | prevTargets.GetNthFiducialPosition(targetIndex, prevPos)
27 | displacement = [currPos[0] - prevPos[0], currPos[1] - prevPos[1], currPos[2] - prevPos[2]]
28 | return displacement
29 |
30 | def isTargetDisplacementChartDisplayable(self, selectedSeries):
31 | if not selectedSeries or not (self.session.seriesTypeManager.isCoverProstate(selectedSeries) or
32 | self.session.seriesTypeManager.isGuidance(selectedSeries)) or \
33 | self.session.data.registrationResultWasRejected(selectedSeries):
34 | return False
35 | selectedSeriesNumber = RegistrationResult.getSeriesNumberFromString(selectedSeries)
36 | approvedResults = sorted([r for r in self.session.data.registrationResults.values() if r.approved],
37 | key=lambda result: result.seriesNumber)
38 | nonSelectedApprovedResults = filter(lambda x: x.seriesNumber != selectedSeriesNumber, approvedResults)
39 | if len(nonSelectedApprovedResults) == 0 or self.session.currentResult is None:
40 | return False
41 | return True
42 |
43 |
44 | class SliceTrackerDisplacementChartPlugin(SliceTrackerPlugin):
45 |
46 | ShowEvent = vtk.vtkCommand.UserEvent + 561
47 | HideEvent = vtk.vtkCommand.UserEvent + 562
48 |
49 | NAME = "DisplacementChart"
50 | LogicClass = SliceTrackerDisplacementChartLogic
51 |
52 | PLOT_COLOR_LR = [213/255.0, 94/255.0, 0]
53 | PLOT_COLOR_PA = [0, 114/255.0, 178/255.0]
54 | PLOT_COLOR_IS = [204/255.0, 121/255.0, 167/255.0]
55 | PLOT_COLOR_3D = [0, 0, 0]
56 | PLOT_NAMES = ["L/R Displacement", "P/A Displacement", "I/S Displacement", "3-D Distance"]
57 |
58 | @property
59 | def plotWidgetViewNode(self):
60 | if not self._plotWidget:
61 | return None
62 | return self._plotWidget.mrmlPlotViewNode()
63 |
64 | def __init__(self):
65 | super(SliceTrackerDisplacementChartPlugin, self).__init__()
66 |
67 | @onModuleSelected(SliceTrackerStep.MODULE_NAME)
68 | def onMrmlSceneCleared(self, caller=None, event=None, callData=None):
69 | self.resetAndInitializeData()
70 |
71 | def resetChart(self):
72 | for arr in [self.arrX, self.arrXD, self.arrYD, self.arrZD, self.arrD]:
73 | arr.Initialize()
74 |
75 | def setup(self):
76 | super(SliceTrackerDisplacementChartPlugin, self).setup()
77 |
78 | self.collapsibleButton = ctk.ctkCollapsibleButton()
79 | self.collapsibleButton.text = "Plotting"
80 | self.collapsibleButton.collapsed = 0
81 | self.collapsibleButton.setLayout(qt.QGridLayout())
82 |
83 | self.resetAndInitializeData()
84 |
85 | self.showLegendCheckBox = qt.QCheckBox('Show legend')
86 | self.showLegendCheckBox.setChecked(1)
87 |
88 | self.collapsibleButton.layout().addWidget(self.showLegendCheckBox, 0, 0)
89 | self.layout().addWidget(self.collapsibleButton)
90 |
91 | def resetAndInitializeData(self):
92 | self._plotView = None
93 | self._plotWidget = None
94 | self._plotViewNode = None
95 | self._plotSeriesNodes = []
96 |
97 | self._initializeChartTable()
98 | self._initializePlotChartNode()
99 | self._initializePlotView()
100 | self._initializePlotWidgets()
101 |
102 | def _initializeChartTable(self):
103 | self._chartTable = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Target Displacement")
104 |
105 | def _initializePlotChartNode(self):
106 | self._plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
107 | self._plotChartNode.SetXAxisTitle('Series Number')
108 | self._plotChartNode.SetYAxisTitle('Displacement')
109 |
110 | def createFloatArray(name):
111 | floatArray = vtk.vtkFloatArray()
112 | floatArray.SetName(name)
113 | self._chartTable.AddColumn(floatArray)
114 | return floatArray
115 |
116 | self.arrX = createFloatArray('X Axis')
117 | self.arrXD = createFloatArray('L/R Displ')
118 | self.arrYD = createFloatArray('P/A Displ')
119 | self.arrZD = createFloatArray('I/S Displ')
120 | self.arrD = createFloatArray('3-D Dis')
121 |
122 | def _initializePlotView(self):
123 | if self._plotView:
124 | self.collapsibleButton.layout().removeWidget(self._plotView)
125 | self._plotView = slicer.qMRMLPlotView()
126 | self._plotView.setMinimumSize(400, 200)
127 | self._plotView.setMRMLScene(slicer.mrmlScene)
128 | self._plotView.show()
129 | self.collapsibleButton.layout().addWidget(self._plotView, 1, 0)
130 |
131 | def _initializePlotWidgets(self):
132 | if self.layoutManager.layout == constants.LAYOUT_FOUR_UP_QUANTITATIVE:
133 | self._plotWidget = self.layoutManager.plotWidget(0) \
134 | if self.layoutManager.plotWidget(0).isVisible() else self.layoutManager.plotWidget(1)
135 | self._updatePlotViewNodes()
136 |
137 | def _updatePlotViewNodes(self):
138 | self._initializePlotViewNode()
139 | if self.plotWidgetViewNode:
140 | self.plotWidgetViewNode.SetPlotChartNodeID(self._plotChartNode.GetID())
141 |
142 | def _initializePlotViewNode(self):
143 | if self._plotViewNode is None:
144 | self._plotViewNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotViewNode")
145 | self._plotView.setMRMLPlotViewNode(self._plotViewNode)
146 | self._plotViewNode.SetPlotChartNodeID(self._plotChartNode.GetID())
147 |
148 | def _initializeChart(self, coverProstateSeriesNumber):
149 | self.arrX.InsertNextValue(coverProstateSeriesNumber)
150 | for arr in [self.arrXD, self.arrYD, self.arrZD, self.arrD]:
151 | arr.InsertNextValue(0)
152 |
153 | def setupConnections(self):
154 | self.showLegendCheckBox.connect('stateChanged(int)', self.onShowLegendChanged)
155 |
156 | def onActivation(self):
157 | super(SliceTrackerDisplacementChartPlugin, self).onActivation()
158 | defaultLayout = getattr(constants, self.getSetting("DEFAULT_EVALUATION_LAYOUT"), constants.LAYOUT_SIDE_BY_SIDE)
159 | if defaultLayout != self.layoutManager.layout:
160 | self.layoutManager.setLayout(defaultLayout)
161 | else:
162 | self.onLayoutChanged(defaultLayout)
163 |
164 | def onDeactivation(self):
165 | super(SliceTrackerDisplacementChartPlugin, self).onDeactivation()
166 | if self.plotWidgetViewNode:
167 | self.plotWidgetViewNode.SetPlotChartNodeID(None)
168 |
169 | def onShowLegendChanged(self, checked):
170 | self._plotChartNode.SetLegendVisibility(True if checked == 2 else False)
171 |
172 | def addSessionObservers(self):
173 | super(SliceTrackerDisplacementChartPlugin, self).addSessionObservers()
174 | self.session.addEventObserver(self.session.TargetSelectionEvent, self.onTargetSelectionChanged)
175 |
176 | def removeSessionEventObservers(self):
177 | super(SliceTrackerDisplacementChartPlugin, self).removeSessionEventObservers()
178 | self.session.removeEventObserver(self.session.TargetSelectionEvent, self.onTargetSelectionChanged)
179 |
180 | @onModuleSelected(SliceTrackerPlugin.MODULE_NAME)
181 | def onLayoutChanged(self, layout=None):
182 | self.collapsibleButton.visible = layout != constants.LAYOUT_FOUR_UP_QUANTITATIVE
183 | if not self.collapsibleButton.visible:
184 | self._initializePlotWidgets()
185 |
186 | @vtk.calldata_type(vtk.VTK_STRING)
187 | def onTargetSelectionChanged(self, caller, event, callData):
188 | info = ast.literal_eval(callData)
189 | targetsAvailable = info['nodeId'] and info['index'] != -1
190 | if targetsAvailable:
191 | self.targetIndex = info['index']
192 | self.currResultTargets = slicer.mrmlScene.GetNodeByID(info['nodeId'])
193 | logging.debug(info)
194 | self.updateTargetDisplacementChart(targetsAvailable)
195 |
196 | def updateTargetDisplacementChart(self, targetsAvailable):
197 | if self.logic.isTargetDisplacementChartDisplayable(self.session.currentSeries) and targetsAvailable:
198 | self.resetChart()
199 | results = sorted([r for r in self.session.data.registrationResults.values() if r.approved],
200 | key=lambda result: result.seriesNumber)
201 | if not self.session.currentResult.wasEvaluated():
202 | results.append(self.session.currentResult)
203 | for currIndex, currResult in enumerate(results[1:], 1):
204 | prevTargets = results[currIndex - 1].targets.approved
205 | if not self.session.currentResult.wasEvaluated() and currIndex == len(results[1:]):
206 | currTargets = self.currResultTargets
207 | else:
208 | currTargets = currResult.targets.approved
209 | displacement = self.logic.calculateTargetDisplacement(prevTargets, currTargets, self.targetIndex)
210 | self.invokeEvent(self.ShowEvent)
211 | self.addPlotPoints([displacement], currResult.seriesNumber)
212 | self.invokeEvent(self.ShowEvent)
213 | else:
214 | self.invokeEvent(self.HideEvent)
215 |
216 | def addPlotPoints(self, displacement, seriesNumber):
217 | numCurrentRows = self._chartTable.GetNumberOfRows()
218 | self._plotViewNode.SetPlotChartNodeID(self._plotChartNode.GetID())
219 |
220 | for i in range(len(displacement)):
221 | if numCurrentRows == 0:
222 | self._initializeChart(self.session.data.getMostRecentApprovedCoverProstateRegistration().seriesNumber)
223 | self.arrX.InsertNextValue(seriesNumber)
224 | for component, arr in enumerate([self.arrXD, self.arrYD, self.arrZD]):
225 | arr.InsertNextValue(displacement[i][component])
226 | distance = (displacement[i][0] ** 2 + displacement[i][1] ** 2 + displacement[i][2] ** 2) ** 0.5
227 | self.arrD.InsertNextValue(distance)
228 | if self._plotChartNode.GetNumberOfPlotSeriesNodes() == 0:
229 | for index, plot in enumerate([self.PLOT_COLOR_LR, self.PLOT_COLOR_PA, self.PLOT_COLOR_IS, self.PLOT_COLOR_3D], start=1):
230 | self.createPlot(plot, index)
231 | else:
232 | self._plotChartNode.RemoveAllPlotSeriesNodeIDs()
233 | for plotSeriesNode in self._plotSeriesNodes:
234 | self._plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
235 |
236 | def createPlot(self, color, plotNumber):
237 | plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", self.PLOT_NAMES[plotNumber - 1])
238 | plotSeriesNode.SetAndObserveTableNodeID(self._chartTable.GetID())
239 | plotSeriesNode.SetXColumnName(self._chartTable.GetColumnName(0))
240 | plotSeriesNode.SetYColumnName(self._chartTable.GetColumnName(plotNumber))
241 | plotSeriesNode.SetColor(color)
242 | plotSeriesNode.SetPlotType(slicer.vtkMRMLPlotSeriesNode.PlotTypeScatter)
243 | plotSeriesNode.SetMarkerStyle(4)
244 | plotSeriesNode.SetMarkerSize(3 * plotSeriesNode.GetLineWidth())
245 |
246 | self._plotSeriesNodes.append(plotSeriesNode)
247 | self._plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/needle.py:
--------------------------------------------------------------------------------
1 | # predefined python file for needle segmentation
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/segmentation/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/SliceTrackerUtils/steps/plugins/segmentation/__init__.py
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/segmentation/automatic.py:
--------------------------------------------------------------------------------
1 | import vtk
2 |
3 | from base import SliceTrackerSegmentationPluginBase
4 | from ....algorithms.automaticProstateSegmentation import AutomaticSegmentationLogic
5 |
6 | class SliceTrackerAutomaticSegmentationPlugin(SliceTrackerSegmentationPluginBase):
7 |
8 | NAME = "AutomaticSegmentation"
9 | ALGORITHM_TYPE ="Automatic"
10 | LogicClass = AutomaticSegmentationLogic
11 |
12 | def __init__(self):
13 | super(SliceTrackerAutomaticSegmentationPlugin, self).__init__()
14 | self.logic.addEventObserver(self.logic.DeepLearningStartedEvent, self._onSegmentationStarted)
15 | self.logic.addEventObserver(self.logic.DeepLearningFinishedEvent, self._onSegmentationFinished)
16 | # self.logic.addEventObserver(self.logic.DeepLearningStatusChangedEvent, self.onStatusChanged)
17 | self.logic.addEventObserver(self.logic.DeepLearningFailedEvent, self._onSegmentationFailed)
18 |
19 | def cleanup(self):
20 | super(SliceTrackerAutomaticSegmentationPlugin, self).cleanup()
21 | self.logic.cleanup()
22 |
23 | def setup(self):
24 | super(SliceTrackerAutomaticSegmentationPlugin, self).setup()
25 |
26 | def onActivation(self):
27 | if str(self.getSetting("Use_Deep_Learning")).lower() == 'true':
28 | self.startSegmentation()
29 |
30 | def startSegmentation(self):
31 | self.logic.run(self.session.fixedVolume, domain='BWH_WITHOUT_ERC', colorNode=self.session.mpReviewColorNode)
32 |
33 | @vtk.calldata_type(vtk.VTK_OBJECT)
34 | def _onSegmentationFinished(self, caller, event, labelNode):
35 | # self.onStatusChanged(None, None, str({'text': "Labelmap prediction created", 'value': 100}))
36 | super(SliceTrackerAutomaticSegmentationPlugin, self)._onSegmentationFinished(caller, event, labelNode)
37 |
38 | # @vtk.calldata_type(vtk.VTK_STRING)
39 | # def onStatusChanged(self, caller, event, callData):
40 | # from SlicerDevelopmentToolboxUtils.widgets import CustomStatusProgressbar
41 | # statusBar = CustomStatusProgressbar()
42 | # if not statusBar.visible:
43 | # statusBar.show()
44 | # import ast
45 | # status = ast.literal_eval(str(callData))
46 | # self.updateProgressBar(progress=statusBar, text=status["text"].replace("\n", ""), value=status["value"],
47 | # maximum = 100)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/segmentation/base.py:
--------------------------------------------------------------------------------
1 | import vtk
2 | from ...base import SliceTrackerPlugin
3 |
4 |
5 | class SliceTrackerSegmentationPluginBase(SliceTrackerPlugin):
6 |
7 | SegmentationStartedEvent = vtk.vtkCommand.UserEvent + 435
8 | SegmentationFinishedEvent = vtk.vtkCommand.UserEvent + 436
9 | SegmentationFailedEvent = vtk.vtkCommand.UserEvent + 437
10 |
11 | def __init__(self):
12 | super(SliceTrackerSegmentationPluginBase, self).__init__()
13 | self.reset()
14 |
15 | def reset(self):
16 | self.startTime = None
17 | self.endTime = None
18 |
19 | def startSegmentation(self):
20 | raise NotImplementedError
21 |
22 | def _onSegmentationStarted(self, caller, event):
23 | self.reset()
24 | self.startTime = self.getTime()
25 | self.invokeEvent(self.SegmentationStartedEvent)
26 |
27 | @vtk.calldata_type(vtk.VTK_OBJECT)
28 | def _onSegmentationFinished(self, caller, event, labelNode):
29 | self.endTime = self.getTime()
30 | self.invokeEvent(self.SegmentationFinishedEvent, labelNode)
31 |
32 | def _onSegmentationFailed(self, caller, event):
33 | self.reset()
34 | self.invokeEvent(self.SegmentationFailedEvent)
35 |
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/segmentation/manual.py:
--------------------------------------------------------------------------------
1 | import qt
2 | import vtk
3 | import slicer
4 |
5 | from ...base import SliceTrackerPlugin, SliceTrackerLogicBase
6 | from ....constants import SliceTrackerConstants as constants
7 | from base import SliceTrackerSegmentationPluginBase
8 | from SurfaceCutToLabel import SurfaceCutToLabelWidget
9 |
10 | from SlicerDevelopmentToolboxUtils.decorators import onModuleSelected
11 |
12 |
13 | class SliceTrackerManualSegmentationLogic(SliceTrackerLogicBase):
14 |
15 | def __init__(self):
16 | super(SliceTrackerManualSegmentationLogic, self).__init__()
17 |
18 |
19 | class SliceTrackerManualSegmentationPlugin(SliceTrackerSegmentationPluginBase):
20 |
21 | NAME = "ManualSegmentation"
22 | ALGORITHM_TYPE ="Manual"
23 | LogicClass = SliceTrackerManualSegmentationLogic
24 |
25 | SegmentationCanceledEvent = SliceTrackerSegmentationPluginBase.SegmentationFailedEvent
26 |
27 | @property
28 | def segmentModelNode(self):
29 | return self.surfaceCutToLabelWidget.logic.segmentModelNode
30 |
31 | @property
32 | def inputMarkupNode(self):
33 | return self.surfaceCutToLabelWidget.logic.inputMarkupNode
34 |
35 | def __init__(self):
36 | super(SliceTrackerManualSegmentationPlugin, self).__init__()
37 |
38 | def setup(self):
39 | super(SliceTrackerManualSegmentationPlugin, self).setup()
40 |
41 | self.surfaceCutGroupBox = qt.QWidget()
42 | self.surfaceCutGroupBox.setLayout(qt.QVBoxLayout())
43 | self.surfaceCutToLabelWidget = SurfaceCutToLabelWidget(self.surfaceCutGroupBox)
44 | self.surfaceCutToLabelWidget.setup()
45 | self.surfaceCutToLabelWidget.selectorsGroupBoxVisible = False
46 | self.surfaceCutToLabelWidget.colorGroupBoxVisible = False
47 |
48 | if str(self.getSetting('DeveloperMode', 'Developer')).lower() == 'true':
49 | self.surfaceCutToLabelWidget.reloadCollapsibleButton.hide()
50 |
51 | self.segmentationGroupBox = qt.QGroupBox("SurfaceCut Segmentation")
52 | self.segmentationGroupBoxLayout = qt.QGridLayout()
53 | self.segmentationGroupBox.setLayout(self.segmentationGroupBoxLayout)
54 | self.segmentationGroupBoxLayout.addWidget(self.surfaceCutGroupBox, 0, 0)
55 | self.layout().addWidget(self.segmentationGroupBox)
56 |
57 | @onModuleSelected(SliceTrackerPlugin.MODULE_NAME)
58 | def onLayoutChanged(self, layout=None):
59 | self._refreshSegmentModelViewNodes()
60 |
61 | def _refreshSegmentModelViewNodes(self):
62 | sliceNodes = [self.yellowSliceNode] if self.layoutManager.layout == constants.LAYOUT_SIDE_BY_SIDE else \
63 | [self.redSliceNode, self.yellowSliceNode, self.greenSliceNode]
64 | nodes = [self.surfaceCutToLabelWidget.logic.segmentModelNode, self.surfaceCutToLabelWidget.logic.inputMarkupNode]
65 | for node in [n for n in nodes if n]:
66 | self.refreshViewNodeIDs(node, sliceNodes)
67 |
68 | def onActivation(self):
69 | super(SliceTrackerManualSegmentationPlugin, self).onActivation()
70 | self.surfaceCutToLabelWidget.logic.colorNode = self.session.mpReviewColorNode
71 | self.surfaceCutToLabelWidget.colorSpin.setValue(self.session.segmentedLabelValue)
72 | self.surfaceCutToLabelWidget.imageVolumeSelector.setCurrentNode(self.session.fixedVolume)
73 | self._addSurfaceCutEventObservers()
74 | if str(self.getSetting("Use_Deep_Learning")).lower() == 'false':
75 | self.surfaceCutToLabelWidget.quickSegmentationButton.checked = True
76 |
77 | def onDeactivation(self):
78 | super(SliceTrackerManualSegmentationPlugin, self).onDeactivation()
79 | self._removeSurfaceCutEventObservers()
80 |
81 | def _addSurfaceCutEventObservers(self):
82 | self.surfaceCutToLabelWidget.addEventObserver(self.surfaceCutToLabelWidget.SegmentationStartedEvent,
83 | self._onSegmentationStarted)
84 | self.surfaceCutToLabelWidget.addEventObserver(self.surfaceCutToLabelWidget.SegmentationCanceledEvent,
85 | self._onSegmentationFailed)
86 | self.surfaceCutToLabelWidget.addEventObserver(self.surfaceCutToLabelWidget.SegmentationFinishedEvent,
87 | self._onSegmentationFinished)
88 |
89 | def _removeSurfaceCutEventObservers(self):
90 | self.surfaceCutToLabelWidget.removeEventObserver(self.surfaceCutToLabelWidget.SegmentationStartedEvent,
91 | self._onSegmentationStarted)
92 | self.surfaceCutToLabelWidget.removeEventObserver(self.surfaceCutToLabelWidget.SegmentationCanceledEvent,
93 | self._onSegmentationFailed)
94 | self.surfaceCutToLabelWidget.removeEventObserver(self.surfaceCutToLabelWidget.SegmentationFinishedEvent,
95 | self._onSegmentationFinished)
96 |
97 | def _onSegmentationStarted(self, caller, event):
98 | if str(self.getSetting("Use_Deep_Learning")).lower() == 'true':
99 | if not self._preCheckExistingSegmentation():
100 | return
101 | else:
102 | labelVolume = self.surfaceCutToLabelWidget.labelVolume
103 | if labelVolume and not labelVolume.GetName().endswith("_modified"):
104 | clonedLabelNode = self.logic.volumesLogic.CloneVolume(slicer.mrmlScene, labelVolume,
105 | labelVolume.GetName()+"_modified")
106 | self.surfaceCutToLabelWidget.labelVolume = clonedLabelNode
107 | self.setupFourUpView(self.session.fixedVolume)
108 | self.setDefaultOrientation()
109 | super(SliceTrackerManualSegmentationPlugin, self)._onSegmentationStarted(caller, event)
110 |
111 | def _preCheckExistingSegmentation(self):
112 | if slicer.util.confirmYesNoDisplay("The automatic segmentation will be overwritten. Do you want to proceed?",
113 | windowTitle="SliceTracker"):
114 | return True
115 | self.surfaceCutToLabelWidget.deactivateQuickSegmentationMode(cancelled=True)
116 | return False
117 |
118 | @vtk.calldata_type(vtk.VTK_OBJECT)
119 | def _onSegmentationFinished(self, caller, event, labelNode):
120 | displayNode = labelNode.GetDisplayNode()
121 | displayNode.SetAndObserveColorNodeID(self.session.mpReviewColorNode.GetID())
122 | self.surfaceCutToLabelWidget.colorSpin.setValue(self.session.segmentedLabelValue)
123 | super(SliceTrackerManualSegmentationPlugin, self)._onSegmentationFinished(caller, event, labelNode)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/segmentationValidator.py:
--------------------------------------------------------------------------------
1 | import ctk
2 | import qt
3 | import slicer
4 | import logging
5 |
6 | from SlicerDevelopmentToolboxUtils.events import SlicerDevelopmentToolboxEvents
7 | from SlicerDevelopmentToolboxUtils.mixins import ModuleWidgetMixin, ModuleLogicMixin
8 | from SlicerDevelopmentToolboxUtils.icons import Icons
9 | from SlicerDevelopmentToolboxUtils.decorators import logmethod
10 |
11 |
12 | class SliceTrackerSegmentationValidatorPlugin(qt.QDialog, ModuleWidgetMixin):
13 |
14 | StartedEvent = SlicerDevelopmentToolboxEvents.StartedEvent
15 | ModifiedEvent = SlicerDevelopmentToolboxEvents.StatusChangedEvent
16 | FinishedEvent = SlicerDevelopmentToolboxEvents.FinishedEvent
17 | CanceledEvent = SlicerDevelopmentToolboxEvents.CanceledEvent
18 |
19 | def __init__(self, inputVolume, labelNode):
20 | qt.QDialog.__init__(self)
21 | self.className = self.__class__.__name__
22 | self.setWindowTitle("Validate Segmentation")
23 | self.setWindowFlags(qt.Qt.WindowStaysOnTopHint)
24 | self.volumeNode = inputVolume
25 | self.labelNode = labelNode
26 | self._initializeMembers()
27 | self.setup()
28 |
29 | def _initializeMembers(self):
30 | self.segmentationModified = False
31 | self.observedSegmentation = None
32 |
33 | def setup(self):
34 | self.setLayout(qt.QGridLayout())
35 | self.setupSliceWidget()
36 |
37 | iconSize = qt.QSize(36, 36)
38 | self.modifySegmentButton = self.createButton("Modify Segmentation", icon=slicer.modules.segmenteditor.icon,
39 | iconSize=iconSize)
40 | self.confirmSegmentButton = self.createButton("Confirm Segmentation", icon=Icons.apply, iconSize=iconSize)
41 | self.cancelButton = self.createButton("Cancel", icon=Icons.cancel, iconSize=iconSize)
42 |
43 | self.setupSegmentEditor()
44 | self.layout().addWidget(self.segmentEditorWidget, 0, 0, 2, 1)
45 | self.layout().addWidget(self.sliceWidget, 0, 1)
46 | self.layout().addWidget(self.createHLayout([self.modifySegmentButton,self.confirmSegmentButton,self.cancelButton]),
47 | 1, 1)
48 | self.setupConnections()
49 |
50 | def setupConnections(self):
51 | self.modifySegmentButton.clicked.connect(self.onModifySegmentButtonClicked)
52 | self.confirmSegmentButton.clicked.connect(self.onConfirmSegmentButtonClicked)
53 | self.cancelButton.clicked.connect(lambda pushed: self.close())
54 |
55 | def setupSliceWidget(self):
56 | self.sliceNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceNode')
57 | self.sliceNode.SetName("Black")
58 | self.sliceNode.SetLayoutName("Black")
59 | self.sliceNode.SetLayoutLabel("BL")
60 | self.sliceNode.SetOrientationToAxial()
61 | slicer.mrmlScene.AddNode(self.sliceNode)
62 | self.sliceWidget = self.layoutManager.viewWidget(self.sliceNode)
63 |
64 | self.sliceLogic = slicer.app.applicationLogic().GetSliceLogic(self.sliceNode)
65 | self.sliceNode.SetMappedInLayout(1)
66 |
67 | def setupSegmentEditor(self):
68 | self.segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
69 | self.segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
70 | self.segmentEditorWidget.visible = False
71 | self.segmentEditorWidget.setSegmentationNodeSelectorVisible(False)
72 | self.segmentEditorWidget.setMasterVolumeNodeSelectorVisible(False)
73 | self.segmentEditorWidget.setSwitchToSegmentationsButtonVisible(False)
74 | self.segmentEditorWidget.findChild(qt.QPushButton, "AddSegmentButton").hide()
75 | self.segmentEditorWidget.findChild(qt.QPushButton, "RemoveSegmentButton").hide()
76 | self.segmentEditorWidget.findChild(ctk.ctkMenuButton, "Show3DButton").hide()
77 | self.segmentEditorWidget.findChild(ctk.ctkExpandableWidget, "SegmentsTableResizableFrame").hide()
78 | self.segmentEditorWidget.setSizePolicy(qt.QSizePolicy.Maximum, qt.QSizePolicy.Expanding)
79 |
80 | def _initializeSegmentationNode(self):
81 | self.segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
82 | self.segmentationNode.CreateDefaultDisplayNodes()
83 | self.segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode)
84 |
85 | def _initializeSegmentEditorNode(self):
86 | self.segmentEditorNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentEditorNode")
87 | self.segmentEditorWidget.setMRMLSegmentEditorNode(self.segmentEditorNode)
88 |
89 | @logmethod(logging.DEBUG)
90 | def run(self):
91 | self.invokeEvent(self.StartedEvent)
92 | self.segmentationModified = False
93 | self._initializeSegmentationNode()
94 | self._initializeSegmentEditorNode()
95 |
96 | self.sliceNode.SetMappedInLayout(1)
97 | self.sliceLogic.GetSliceCompositeNode().SetBackgroundVolumeID(self.volumeNode.GetID())
98 | self.sliceLogic.GetSliceCompositeNode().SetLabelVolumeID(self.labelNode.GetID())
99 | self.sliceLogic.FitSliceToAll()
100 | self.sliceNode.RotateToVolumePlane(self.volumeNode)
101 | self.sliceNode.SetUseLabelOutline(True)
102 | self.resize(int(slicer.util.mainWindow().width/3*2), int(slicer.util.mainWindow().height/3*2))
103 | result = self.exec_()
104 | if result == qt.QDialog.Rejected:
105 | logging.debug("{}: Dialog got rejected.".format(self.className))
106 | self.invokeEvent(self.CanceledEvent)
107 | elif result == qt.QDialog.Accepted:
108 | logging.debug("{}: Dialog got approved.".format(self.className))
109 | self.invokeEvent(self.FinishedEvent, self.labelNode)
110 | self.cleanup()
111 |
112 | def onModifySegmentButtonClicked(self):
113 | self.invokeEvent(self.ModifiedEvent)
114 | slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(self.labelNode, self.segmentationNode)
115 | segmentID = self.segmentationNode.GetSegmentation().GetNthSegment(0).GetName()
116 | segmentationDisplayNode = self.segmentationNode.GetDisplayNode()
117 | segmentationDisplayNode.SetSegmentVisibility2DFill(segmentID, False)
118 | self.segmentEditorWidget.setSegmentationNode(self.segmentationNode)
119 | self.segmentEditorWidget.setMasterVolumeNode(self.volumeNode)
120 | self.addSegmentationObserver(self.segmentationNode)
121 | self.segmentEditorWidget.show()
122 | self.modifySegmentButton.hide()
123 |
124 | def onConfirmSegmentButtonClicked(self):
125 | if self.segmentationModified is True:
126 | volumesLogic = slicer.modules.volumes.logic()
127 | clonedLabelNode = volumesLogic.CloneVolume(slicer.mrmlScene, self.labelNode,
128 | self.labelNode.GetName() + "_modified")
129 | self.labelNode = clonedLabelNode
130 | slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(self.segmentationNode, self.labelNode)
131 | ModuleLogicMixin.runBRAINSResample(inputVolume=self.labelNode, referenceVolume=self.volumeNode,
132 | outputVolume=self.labelNode)
133 | self.accept()
134 |
135 | @logmethod(logging.DEBUG)
136 | def addSegmentationObserver(self, segmentation):
137 | import vtkSegmentationCorePython as vtkSegmentationCore
138 | self.observedSegmentation = segmentation
139 | self.segmentObserver = self.observedSegmentation.AddObserver(
140 | vtkSegmentationCore.vtkSegmentation.RepresentationModified,
141 | self.onSegmentModified)
142 |
143 | @logmethod(logging.DEBUG)
144 | def removeSegmentationObserver(self):
145 | if self.observedSegmentation:
146 | self.observedSegmentation.RemoveObserver(self.segmentObserver)
147 | self.segmentObserver = None
148 |
149 | def onSegmentModified(self, caller, event):
150 | self.segmentationModified = True
151 |
152 | @logmethod(logging.DEBUG)
153 | def cleanup(self):
154 | self.removeSegmentationObserver()
155 | if self.observedSegmentation:
156 | slicer.mrmlScene.RemoveNode(self.segmentationNode)
157 | slicer.mrmlScene.RemoveNode(self.segmentEditorNode)
158 | self.observedSegmentation = None
159 | self.segmentEditorWidget.hide()
160 | self.modifySegmentButton.show()
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/targeting.py:
--------------------------------------------------------------------------------
1 | import qt
2 | from ...constants import SliceTrackerConstants as constants
3 | from ..base import SliceTrackerPlugin, SliceTrackerLogicBase
4 | from targets import SliceTrackerTargetTablePlugin
5 |
6 | from SlicerDevelopmentToolboxUtils.helpers import SliceAnnotation
7 | from SlicerDevelopmentToolboxUtils.widgets import TargetCreationWidget
8 |
9 |
10 | class SliceTrackerTargetingLogic(SliceTrackerLogicBase):
11 |
12 | def __init__(self):
13 | super(SliceTrackerTargetingLogic, self).__init__()
14 |
15 |
16 | class SliceTrackerTargetingPlugin(SliceTrackerPlugin):
17 |
18 | LogicClass = SliceTrackerTargetingLogic
19 |
20 | NAME = "Targeting"
21 | TargetingStartedEvent = TargetCreationWidget.StartedEvent
22 | TargetingFinishedEvent = TargetCreationWidget.FinishedEvent
23 |
24 | @property
25 | def title(self):
26 | return self.targetingGroupBox.title
27 |
28 | @title.setter
29 | def title(self, value):
30 | self.targetingGroupBox.setTitle(value)
31 |
32 | def __init__(self, **kwargs):
33 | super(SliceTrackerTargetingPlugin, self).__init__()
34 | self._processKwargs(**kwargs)
35 |
36 | def setup(self):
37 | super(SliceTrackerTargetingPlugin, self).setup()
38 |
39 | self._setupTargetCreationWidget()
40 |
41 | self.preopTargetTableGroupBox, self.preopTargetTablePlugin = \
42 | self._createTargetTableGroupBox("Pre-operative Targets")
43 | self.intraopTargetTableGroupBox, self.intraopTargetTablePlugin = \
44 | self._createTargetTableGroupBox("Intra-operative Targets",
45 | additionalComponents=[self.targetCreationWidget,
46 | self.targetCreationWidget.buttons])
47 |
48 | self.targetingGroupBox = qt.QGroupBox("Target Placement")
49 | self.targetingGroupBox.setLayout(qt.QFormLayout())
50 | self.targetingGroupBox.layout().addRow(self.preopTargetTableGroupBox)
51 | self.targetingGroupBox.layout().addRow(self.intraopTargetTableGroupBox)
52 | self.layout().addWidget(self.targetingGroupBox, 1, 0, 2, 2)
53 |
54 | def _createTargetTableGroupBox(self, title, additionalComponents=None):
55 | additionalComponents = additionalComponents if additionalComponents else []
56 | groupbox = qt.QGroupBox(title)
57 | groupbox.setAlignment(qt.Qt.AlignCenter)
58 | groupbox.setLayout(qt.QFormLayout())
59 | groupbox.setAlignment(qt.Qt.AlignCenter)
60 | targetTablePlugin = SliceTrackerTargetTablePlugin()
61 | self.addPlugin(targetTablePlugin)
62 | groupbox.layout().addRow(targetTablePlugin)
63 | for c in additionalComponents:
64 | groupbox.layout().addRow(c)
65 | return groupbox, targetTablePlugin
66 |
67 | def _setupTargetCreationWidget(self):
68 | self.targetCreationWidget = TargetCreationWidget(DEFAULT_FIDUCIAL_LIST_NAME="IntraopTargets",
69 | ICON_SIZE=qt.QSize(36, 36))
70 | self.targetCreationWidget.addEventObserver(self.TargetingStartedEvent, self._onTargetingStarted)
71 | self.targetCreationWidget.addEventObserver(self.TargetingFinishedEvent, self._onTargetingFinished)
72 |
73 | def preopAvailableAndTargetsDefined(self):
74 | return self.session.data.usePreopData and self.session.movingTargets and not self.session.retryMode
75 |
76 | def onActivation(self):
77 | super(SliceTrackerTargetingPlugin, self).onActivation()
78 | self._setFiducialWidgetVisible(True)
79 |
80 | if self.preopAvailableAndTargetsDefined():
81 | self._setFiducialWidgetVisible(False)
82 | self.targetCreationWidget.currentNode = None
83 | self.preopTargetTablePlugin.currentTargets = self.session.movingTargets
84 | else:
85 | approvedCoverProstate = self.session.data.getMostRecentApprovedCoverProstateRegistration()
86 | if (approvedCoverProstate is not None
87 | and self.session.seriesTypeManager.isCoverProstate(self.session.currentSeries)):
88 | clone = self.logic.cloneFiducials(approvedCoverProstate.targets.approved, "IntraopTargets")
89 | self.targetCreationWidget.currentNode = clone
90 | self.intraopTargetTablePlugin.currentTargets = self.session.movingTargets
91 | self.session.movingTargets = clone
92 | self.setFiducialNodeVisibility(clone, True)
93 | self.session.applyDefaultTargetDisplayNode(clone)
94 | self.preopTargetTableGroupBox.visible = False
95 |
96 | self.targetingGroupBox.visible = not self.session.retryMode
97 |
98 | def onDeactivation(self):
99 | super(SliceTrackerTargetingPlugin, self).onDeactivation()
100 | self.targetCreationWidget.reset()
101 | self._removeSliceAnnotations()
102 |
103 | def startTargeting(self):
104 | self.targetCreationWidget.startPlacing()
105 |
106 | def _setFiducialWidgetVisible(self, visible):
107 | self.targetCreationWidget.visible = visible
108 | self.preopTargetTableGroupBox.visible = not visible and self.preopAvailableAndTargetsDefined()
109 | self.intraopTargetTablePlugin.visible = not visible
110 |
111 | def _onTargetingStarted(self, caller, event):
112 | self._addSliceAnnotations()
113 | self.targetCreationWidget.show()
114 | self.intraopTargetTablePlugin.visible = False
115 | self.setupFourUpView(self.session.currentSeriesVolume, clearLabels=False)
116 | self.invokeEvent(self.TargetingStartedEvent)
117 |
118 | def _addSliceAnnotations(self):
119 | self._removeSliceAnnotations()
120 | widgets = [self.yellowWidget] if self.layoutManager.layout == constants.LAYOUT_SIDE_BY_SIDE else \
121 | [self.redWidget, self.yellowWidget, self.greenWidget]
122 | for widget in widgets:
123 | self.sliceAnnotations.append(SliceAnnotation(widget, "Targeting Mode", opacity=0.5,
124 | verticalAlign="top", horizontalAlign="center"))
125 |
126 | def _removeSliceAnnotations(self):
127 | self.sliceAnnotations = getattr(self, "sliceAnnotations", [])
128 | for annotation in self.sliceAnnotations:
129 | annotation.remove()
130 | self.sliceAnnotations = []
131 |
132 | def _onTargetingFinished(self, caller, event):
133 | self._removeSliceAnnotations()
134 | if self.targetCreationWidget.hasTargetListAtLeastOneTarget():
135 | if not self.preopAvailableAndTargetsDefined():
136 | self.session.movingTargets = self.targetCreationWidget.currentNode
137 | self.session.setupPreopLoadedTargets()
138 | else:
139 | self.session.temporaryIntraopTargets = self.targetCreationWidget.currentNode
140 | self._setFiducialWidgetVisible(False)
141 | self.intraopTargetTablePlugin.currentTargets = self.targetCreationWidget.currentNode
142 | else:
143 | if not self.preopAvailableAndTargetsDefined():
144 | self.session.movingTargets = None
145 |
146 | self.invokeEvent(self.TargetingFinishedEvent)
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/plugins/training.py:
--------------------------------------------------------------------------------
1 | import os
2 | import ast
3 | import shutil
4 | import qt
5 | import vtk
6 | import ctk
7 | import slicer
8 |
9 | from ...constants import SliceTrackerConstants
10 | from ..base import SliceTrackerPlugin
11 |
12 | from SlicerDevelopmentToolboxUtils.helpers import SampleDataDownloader
13 | from SlicerDevelopmentToolboxUtils.decorators import *
14 |
15 |
16 | class SliceTrackerTrainingPlugin(SliceTrackerPlugin):
17 |
18 | NAME = "Training"
19 |
20 | def __init__(self):
21 | super(SliceTrackerTrainingPlugin, self).__init__()
22 | self.sampleDownloader = SampleDataDownloader(True)
23 |
24 | def setup(self):
25 | super(SliceTrackerTrainingPlugin, self).setup()
26 | self.collapsibleTrainingArea = ctk.ctkCollapsibleButton()
27 | self.collapsibleTrainingArea.collapsed = True
28 | self.collapsibleTrainingArea.text = "Training Incoming Data Simulation"
29 |
30 | self.simulatePreopPhaseButton = self.createButton("Simulate preop reception", enabled=False)
31 | self.simulateIntraopPhaseButton = self.createButton("Simulate intraop reception", enabled=False)
32 |
33 | self.trainingsAreaLayout = qt.QGridLayout(self.collapsibleTrainingArea)
34 | self.trainingsAreaLayout.addWidget(self.createHLayout([self.simulatePreopPhaseButton,
35 | self.simulateIntraopPhaseButton]))
36 | self.layout().addWidget(self.collapsibleTrainingArea)
37 |
38 | def setupConnections(self):
39 | self.simulatePreopPhaseButton.clicked.connect(self.startPreopPhaseSimulation)
40 | self.simulateIntraopPhaseButton.clicked.connect(self.startIntraopPhaseSimulation)
41 |
42 | def addSessionObservers(self):
43 | super(SliceTrackerTrainingPlugin, self).addSessionObservers()
44 | self.session.addEventObserver(self.session.IncomingDataSkippedEvent, self.onIncomingDataSkipped)
45 |
46 | def removeSessionEventObservers(self):
47 | super(SliceTrackerTrainingPlugin, self).removeSessionEventObservers()
48 | self.session.removeEventObserver(self.session.IncomingDataSkippedEvent, self.onIncomingDataSkipped)
49 |
50 | def startPreopPhaseSimulation(self):
51 | self.session.trainingMode = True
52 | if self.session.preopDICOMReceiver.dicomReceiver.isRunning():
53 | self.session.preopDICOMReceiver.dicomReceiver.stopStoreSCP()
54 | self.simulatePreopPhaseButton.enabled = False
55 | preopZipFile = self.initiateSampleDataDownload(SliceTrackerConstants.PREOP_SAMPLE_DATA_URL)
56 | if not self.sampleDownloader.wasCanceled() and preopZipFile:
57 | self.unzipFileAndCopyToDirectory(preopZipFile, self.session.preopDICOMDirectory)
58 |
59 | def startIntraopPhaseSimulation(self):
60 | self.simulateIntraopPhaseButton.enabled = False
61 | intraopZipFile = self.initiateSampleDataDownload(SliceTrackerConstants.INTRAOP_SAMPLE_DATA_URL)
62 | if not self.sampleDownloader.wasCanceled() and intraopZipFile:
63 | self.unzipFileAndCopyToDirectory(intraopZipFile, self.session.intraopDICOMDirectory)
64 |
65 | def initiateSampleDataDownload(self, url):
66 | filename = os.path.basename(url)
67 | self.sampleDownloader.resetAndInitialize()
68 | self.sampleDownloader.addEventObserver(self.sampleDownloader.StatusChangedEvent, self.onDownloadProgressUpdated)
69 | # self.customStatusProgressBar.show()
70 | downloadedFile = self.sampleDownloader.downloadFileIntoCache(url, filename)
71 | # self.customStatusProgressBar.hide()
72 | return None if self.sampleDownloader.wasCanceled() else downloadedFile
73 |
74 | @onReturnProcessEvents
75 | @vtk.calldata_type(vtk.VTK_STRING)
76 | def onDownloadProgressUpdated(self, caller, event, callData):
77 | message, percent = ast.literal_eval(callData)
78 | logging.info("%s, %s" %(message, percent))
79 | # self.customStatusProgressBar.updateStatus(message, percent)
80 |
81 | def unzipFileAndCopyToDirectory(self, filepath, copyToDirectory):
82 | import zipfile
83 | try:
84 | zip_ref = zipfile.ZipFile(filepath, 'r')
85 | destination = filepath.replace(os.path.basename(filepath), "")
86 | logging.debug("extracting to %s " % destination)
87 | zip_ref.extractall(destination)
88 | zip_ref.close()
89 | self.copyDirectory(filepath.replace(".zip", ""), copyToDirectory)
90 | except zipfile.BadZipfile as exc:
91 | if self.session.preopDICOMReceiver:
92 | self.session.preopDICOMReceiver.hide()
93 | slicer.util.errorDisplay("An error appeared while extracting %s. If the file is corrupt, please delete it and try "
94 | "again." % filepath, detailedText=str(exc.message))
95 | self.clearData()
96 |
97 | def copyDirectory(self, source, destination, recursive=True):
98 | print source
99 | assert os.path.isdir(source)
100 | for listObject in os.listdir(source):
101 | current = os.path.join(source, listObject)
102 | if os.path.isdir(current) and recursive:
103 | self.copyDirectory(current, destination, recursive)
104 | else:
105 | shutil.copy(current, destination)
106 |
107 | @logmethod(logging.DEBUG)
108 | def onNewCaseStarted(self, caller, event):
109 | self.simulatePreopPhaseButton.enabled = True
110 |
111 | def onIncomingDataSkipped(self, caller, event):
112 | self.simulatePreopPhaseButton.enabled = False
113 | self.simulateIntraopPhaseButton.enabled = True
114 |
115 | @vtk.calldata_type(vtk.VTK_STRING)
116 | def onCaseClosed(self, caller, event, callData):
117 | self.simulatePreopPhaseButton.enabled = False
118 | self.simulateIntraopPhaseButton.enabled = False
119 |
120 | def onPreprocessingSuccessful(self, caller, event):
121 | self.simulateIntraopPhaseButton.enabled = self.session.trainingMode
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/steps/segmentation.py:
--------------------------------------------------------------------------------
1 | import ast
2 | import qt
3 | import slicer
4 | import vtk
5 |
6 | from base import SliceTrackerLogicBase, SliceTrackerStep
7 | from ..constants import SliceTrackerConstants as constants
8 | from ..sessionData import SegmentationData
9 |
10 | from plugins.targeting import SliceTrackerTargetingPlugin
11 | from plugins.segmentation.manual import SliceTrackerManualSegmentationPlugin
12 | from plugins.segmentation.automatic import SliceTrackerAutomaticSegmentationPlugin
13 |
14 | from SlicerDevelopmentToolboxUtils.helpers import SliceAnnotation
15 | from SlicerDevelopmentToolboxUtils.decorators import onModuleSelected
16 | from SlicerDevelopmentToolboxUtils.icons import Icons
17 |
18 |
19 | class SliceTrackerSegmentationStepLogic(SliceTrackerLogicBase):
20 |
21 | def __init__(self):
22 | super(SliceTrackerSegmentationStepLogic, self).__init__()
23 |
24 | def inputsAreSet(self):
25 | if self.session.data.usePreopData:
26 | return self.session.movingVolume and self.session.fixedVolume and self.session.movingLabel and \
27 | self.session.fixedLabel and self.session.movingTargets
28 | else:
29 | return self.session.fixedVolume and self.session.fixedLabel and self.session.movingTargets
30 |
31 | def loadInitialData(self):
32 | self.session.movingLabel = self.session.data.initialLabel
33 | self.session.movingVolume = self.session.data.initialVolume
34 | self.session.movingTargets = self.session.data.initialTargets
35 |
36 |
37 | class SliceTrackerSegmentationStep(SliceTrackerStep):
38 |
39 | NAME = "Segmentation"
40 | LogicClass = SliceTrackerSegmentationStepLogic
41 | LayoutClass = qt.QVBoxLayout
42 |
43 | def __init__(self):
44 | super(SliceTrackerSegmentationStep, self).__init__()
45 | self.session.retryMode = False
46 | self.segmentationData = None
47 |
48 | def setup(self):
49 | super(SliceTrackerSegmentationStep, self).setup()
50 | self._setupTargetingPlugin()
51 | self._setupManualSegmentationPlugin()
52 | self._setupAutomaticSegmentationPlugin()
53 | self._setupNavigationButtons()
54 | self.layout().addWidget(self.manualSegmentationPlugin)
55 | self.layout().addWidget(self.createHLayout([self.backButton, self.finishStepButton]))
56 | self.layout().addWidget(self.targetingPlugin)
57 | self.layout().addStretch(1)
58 |
59 | def _setupTargetingPlugin(self):
60 | self.targetingPlugin = SliceTrackerTargetingPlugin()
61 | self.targetingPlugin.addEventObserver(self.targetingPlugin.TargetingStartedEvent, self._onTargetingStarted)
62 | self.targetingPlugin.addEventObserver(self.targetingPlugin.TargetingFinishedEvent, self._onTargetingFinished)
63 | self.addPlugin(self.targetingPlugin)
64 |
65 | def _setupManualSegmentationPlugin(self):
66 | self.manualSegmentationPlugin = SliceTrackerManualSegmentationPlugin()
67 | self.manualSegmentationPlugin.addEventObserver(self.manualSegmentationPlugin.SegmentationStartedEvent,
68 | self._onSegmentationStarted)
69 | self.manualSegmentationPlugin.addEventObserver(self.manualSegmentationPlugin.SegmentationCanceledEvent,
70 | self._onSegmentationCanceled)
71 | self.manualSegmentationPlugin.addEventObserver(self.manualSegmentationPlugin.SegmentationFinishedEvent,
72 | self._onManualSegmentationFinished)
73 | self.manualSegmentationPlugin.surfaceCutToLabelWidget.segmentEditorButtonVisible = False
74 | self.addPlugin(self.manualSegmentationPlugin)
75 |
76 | def _setupAutomaticSegmentationPlugin(self):
77 | self.automaticSegmentationPlugin = SliceTrackerAutomaticSegmentationPlugin()
78 | self.automaticSegmentationPlugin.addEventObserver(self.automaticSegmentationPlugin.SegmentationStartedEvent,
79 | self._onAutomaticSegmentationStarted)
80 | self.automaticSegmentationPlugin.addEventObserver(self.automaticSegmentationPlugin.SegmentationFailedEvent,
81 | self._onSegmentationFailed)
82 | self.automaticSegmentationPlugin.addEventObserver(self.automaticSegmentationPlugin.SegmentationFinishedEvent,
83 | self._onAutomaticSegmentationFinished)
84 | self.addPlugin(self.automaticSegmentationPlugin)
85 |
86 | def _setupNavigationButtons(self):
87 | iconSize = qt.QSize(36, 36)
88 | self.backButton = self.createButton("", icon=Icons.back, iconSize=iconSize,
89 | toolTip="Return to last step")
90 | self.finishStepButton = self.createButton("", icon=Icons.start, iconSize=iconSize,
91 | toolTip="Run Registration/Finish Step")
92 |
93 | def setupConnections(self):
94 | super(SliceTrackerSegmentationStep, self).setupConnections()
95 | self.backButton.clicked.connect(self._onBackButtonClicked)
96 | self.finishStepButton.clicked.connect(self._onFinishStepButtonClicked)
97 |
98 | def addSessionObservers(self):
99 | super(SliceTrackerSegmentationStep, self).addSessionObservers()
100 | self.session.addEventObserver(self.session.InitiateSegmentationEvent, self.onInitiateSegmentation)
101 |
102 | def removeSessionEventObservers(self):
103 | super(SliceTrackerSegmentationStep, self).removeSessionEventObservers()
104 | self.session.removeEventObserver(self.session.InitiateSegmentationEvent, self.onInitiateSegmentation)
105 |
106 | def onActivation(self):
107 | self.segmentationData = None
108 | self.finishStepButton.enabled = False
109 | self.session.fixedVolume = self.session.currentSeriesVolume
110 | if not self.session.fixedVolume:
111 | return
112 | self._updateAvailableLayouts()
113 | super(SliceTrackerSegmentationStep, self).onActivation()
114 |
115 | def onDeactivation(self):
116 | super(SliceTrackerSegmentationStep, self).onDeactivation()
117 | self._removeMissingPreopDataAnnotation()
118 |
119 | @onModuleSelected(SliceTrackerStep.MODULE_NAME)
120 | def onLayoutChanged(self, layout=None):
121 | if self.layoutManager.layout == constants.LAYOUT_SIDE_BY_SIDE:
122 | self._setupSideBySideSegmentationView()
123 | elif self.layoutManager.layout in [constants.LAYOUT_FOUR_UP, constants.LAYOUT_RED_SLICE_ONLY]:
124 | self._removeMissingPreopDataAnnotation()
125 | self.setBackgroundToVolumeID(self.session.currentSeriesVolume, clearLabels=False)
126 |
127 | @vtk.calldata_type(vtk.VTK_STRING)
128 | def onInitiateSegmentation(self, caller, event, callData):
129 | self._initiateSegmentation(ast.literal_eval(callData))
130 |
131 | def _initiateSegmentation(self, retryMode=False):
132 | self.session.retryMode = retryMode
133 | self.finishStepButton.setEnabled(True if self.logic.inputsAreSet() else False)
134 | if self.session.seriesTypeManager.isCoverProstate(self.session.currentSeries):
135 | self.initializeCoverProstate()
136 | else:
137 | self._loadLatestCoverProstateResultData()
138 | self.active = True
139 |
140 | def initializeCoverProstate(self):
141 | if self.session.data.usePreopData:
142 | if self.session.retryMode:
143 | if not self._loadLatestCoverProstateResultData():
144 | self.logic.loadInitialData()
145 | else:
146 | self.logic.loadInitialData()
147 | else:
148 | self.session.movingVolume = self.session.currentSeriesVolume
149 |
150 | def _loadLatestCoverProstateResultData(self):
151 | coverProstate = self.session.data.getMostRecentApprovedCoverProstateRegistration()
152 | if coverProstate:
153 | self.session.movingVolume = coverProstate.volumes.fixed
154 | self.session.movingLabel = coverProstate.labels.fixed
155 | self.session.movingTargets = coverProstate.targets.approved
156 | return True
157 | return False
158 |
159 | def _updateAvailableLayouts(self):
160 | layouts = [constants.LAYOUT_RED_SLICE_ONLY, constants.LAYOUT_FOUR_UP]
161 | if self.session.data.usePreopData or self.session.retryMode:
162 | layouts.append(constants.LAYOUT_SIDE_BY_SIDE)
163 | self.setAvailableLayouts(layouts)
164 |
165 | def _setupSideBySideSegmentationView(self):
166 | # TODO: red slice view should not be possible to set target
167 | coverProstate = self.session.data.getMostRecentApprovedCoverProstateRegistration()
168 | redVolume = coverProstate.volumes.fixed if coverProstate and self.session.retryMode else self.session.data.initialVolume
169 | redLabel = coverProstate.labels.fixed if coverProstate and self.session.retryMode else self.session.data.initialLabel
170 |
171 | if redVolume and redLabel:
172 | self.redCompositeNode.SetBackgroundVolumeID(redVolume.GetID())
173 | self.redCompositeNode.SetLabelVolumeID(redLabel.GetID())
174 | self.redCompositeNode.SetLabelOpacity(1)
175 | else:
176 | self.redCompositeNode.SetBackgroundVolumeID(None)
177 | self.redCompositeNode.SetLabelVolumeID(None)
178 | self._addMissingPreopDataAnnotation(self.redWidget)
179 | self.yellowCompositeNode.SetBackgroundVolumeID(self.session.currentSeriesVolume.GetID())
180 | self.setAxialOrientation()
181 |
182 | if redVolume and redLabel:
183 | self.redSliceNode.SetUseLabelOutline(True)
184 | self.redSliceNode.RotateToVolumePlane(redVolume)
185 |
186 | def _addMissingPreopDataAnnotation(self, widget):
187 | self._removeMissingPreopDataAnnotation()
188 | self.noPreopSegmentationAnnotation = SliceAnnotation(widget, constants.MISSING_PREOP_ANNOTATION_TEXT,
189 | opacity=0.7, color=(1, 0, 0))
190 |
191 | def _removeMissingPreopDataAnnotation(self):
192 | self.noPreopSegmentationAnnotation = getattr(self, "noPreopSegmentationAnnotation", None)
193 | if self.noPreopSegmentationAnnotation:
194 | self.noPreopSegmentationAnnotation.remove()
195 | self.noPreopSegmentationAnnotation = None
196 |
197 | def _onBackButtonClicked(self):
198 | if self.session.retryMode:
199 | self.session.retryMode = False
200 | if self.session.previousStep:
201 | self.session.previousStep.active = True
202 |
203 | def _onFinishStepButtonClicked(self):
204 | self.session.data.segmentModelNode = self.manualSegmentationPlugin.segmentModelNode
205 | self.session.data.inputMarkupNode = self.manualSegmentationPlugin.inputMarkupNode
206 | if not self.session.data.usePreopData and not self.session.retryMode:
207 | self._createCoverProstateRegistrationResultManually()
208 | else:
209 | self.session.onInvokeRegistration(initial=True, retryMode=self.session.retryMode,
210 | segmentationData=self.segmentationData)
211 |
212 | def _createCoverProstateRegistrationResultManually(self):
213 | fixedVolume = self.session.currentSeriesVolume
214 | result = self.session.generateNameAndCreateRegistrationResult(fixedVolume)
215 | approvedRegistrationType = "bSpline"
216 | result.targets.original = self.session.movingTargets
217 | targetName = str(result.seriesNumber) + '-TARGETS-' + approvedRegistrationType + result.suffix
218 | clone = self.logic.cloneFiducials(self.session.movingTargets, targetName)
219 | self.session.applyDefaultTargetDisplayNode(clone)
220 | result.setTargets(approvedRegistrationType, clone)
221 | result.volumes.fixed = fixedVolume
222 | result.labels.fixed = self.session.fixedLabel
223 | result.receivedTime = self.session.seriesTimeStamps[result.name.replace(result.suffix, "")]
224 | result.segmentationData = self.segmentationData
225 |
226 | if self.session.seriesTypeManager.isCoverProstate(self.session.currentResult.name) and \
227 | self.session.data.getMostRecentApprovedCoverProstateRegistration() is not None:
228 | self.session.data.getMostRecentApprovedCoverProstateRegistration().skip()
229 |
230 | result.approve(approvedRegistrationType, consentedBy="Clinician")
231 |
232 | def _onAutomaticSegmentationStarted(self, caller, event):
233 | self.manualSegmentationPlugin.enabled = False
234 | self._onSegmentationStarted(caller, event)
235 |
236 | def _onSegmentationStarted(self, caller, event):
237 | # self.setAvailableLayouts([constants.LAYOUT_RED_SLICE_ONLY, constants.LAYOUT_SIDE_BY_SIDE, constants.LAYOUT_FOUR_UP])
238 | self.targetingPlugin.enabled = False
239 | self.backButton.enabled = False
240 | self.finishStepButton.enabled = False
241 |
242 | def _onSegmentationCanceled(self, caller, event):
243 | # self.setAvailableLayouts([constants.LAYOUT_FOUR_UP])
244 | self.layoutManager.setLayout(constants.LAYOUT_FOUR_UP)
245 | self.backButton.enabled = True
246 | self.targetingPlugin.enabled = True
247 | if self.logic.inputsAreSet():
248 | self._displaySegmentationComparison()
249 | self.finishStepButton.setEnabled(1 if self.logic.inputsAreSet() else 0) # TODO: need to revise that
250 |
251 | def _onSegmentationFailed(self, caller, event):
252 | import logging
253 | logging.debug("Segmentation failed")
254 | self.setAvailableLayouts([constants.LAYOUT_FOUR_UP])
255 | self.layoutManager.setLayout(constants.LAYOUT_FOUR_UP)
256 | self.backButton.enabled = True
257 | self.finishStepButton.setEnabled(False)
258 |
259 | @vtk.calldata_type(vtk.VTK_STRING)
260 | def onNewImageSeriesReceived(self, caller, event, callData):
261 | if not self.active:
262 | return
263 | newImageSeries = ast.literal_eval(callData)
264 | for series in reversed(newImageSeries):
265 | if self.session.seriesTypeManager.isCoverProstate(series):
266 | if series != self.session.currentSeries:
267 | if slicer.util.confirmYesNoDisplay("Another %s was received. Do you want to use this one?"
268 | % self.getSetting("COVER_PROSTATE_PATTERN")):
269 | self.session.currentSeries = series
270 | self.active = False
271 | self._initiateSegmentation()
272 |
273 | @vtk.calldata_type(vtk.VTK_OBJECT)
274 | def _onAutomaticSegmentationFinished(self, caller, event, labelNode):
275 | self.manualSegmentationPlugin.enabled = True
276 | surfaceCutToLabelWidget = self.manualSegmentationPlugin.surfaceCutToLabelWidget
277 |
278 | # segmentationsLogic = slicer.modules.segmentations.logic()
279 | #
280 | # segmentationNode = surfaceCutToLabelWidget.segmentationNode
281 | # map(lambda x: segmentationNode.RemoveSegment(x), surfaceCutToLabelWidget.getSegmentIDs())
282 | # segmentationsLogic.ImportLabelmapToSegmentationNode(labelNode, segmentationNode)
283 | # surfaceCutToLabelWidget.configureSegmentVisibility()
284 | # surfaceCutToLabelWidget.segmentEditorWidgetButton.enabled = True
285 |
286 | surfaceCutToLabelWidget.imageVolume = self.session.currentSeriesVolume
287 | surfaceCutToLabelWidget.labelVolume = labelNode
288 |
289 | self.createSegmentationDataOrSetModified(self.automaticSegmentationPlugin, labelNode)
290 | self._onSegmentationFinished(caller, event, labelNode)
291 |
292 | def createSegmentationDataOrSetModified(self, plugin, labelNode):
293 | if not self.segmentationData or self.segmentationData.algorithm=="Manual":
294 | self.segmentationData = SegmentationData(segmentationType="Prostate",algorithm=plugin.ALGORITHM_TYPE,
295 | startTime=plugin.startTime, endTime=plugin.endTime, label=labelNode)
296 | else:
297 | self.segmentationData.setModified(startTime=plugin.startTime, endTime=plugin.endTime, label=labelNode)
298 |
299 | @vtk.calldata_type(vtk.VTK_OBJECT)
300 | def _onManualSegmentationFinished(self, caller, event, labelNode):
301 | self.createSegmentationDataOrSetModified(self.manualSegmentationPlugin, labelNode)
302 | self._onSegmentationFinished(caller, event, labelNode)
303 |
304 | @vtk.calldata_type(vtk.VTK_OBJECT)
305 | def _onSegmentationFinished(self, caller, event, labelNode):
306 | _, suffix = self.session.getRegistrationResultNameAndGeneratedSuffix(self.session.currentSeries)
307 | if labelNode.GetName().find(suffix) == -1 and not labelNode.GetName().endswith("_modified"):
308 | labelNode.SetName(labelNode.GetName() + suffix)
309 | self.session.fixedLabel = labelNode
310 | self.finishStepButton.setEnabled(1 if self.logic.inputsAreSet() else 0)
311 | self.backButton.enabled = True
312 | self._displaySegmentationComparison()
313 |
314 | def _displaySegmentationComparison(self):
315 | self.setAvailableLayouts([constants.LAYOUT_SIDE_BY_SIDE])
316 | if self.session.data.usePreopData or self.session.retryMode:
317 | self.setAxialOrientation()
318 | self._removeMissingPreopDataAnnotation()
319 | self.targetingPlugin.enabled = True
320 | if self.session.data.usePreopData or self.session.retryMode:
321 | self.layoutManager.setLayout(constants.LAYOUT_SIDE_BY_SIDE)
322 | self._setBackgroundAndLabel("red", self.session.movingVolume, self.session.movingLabel)
323 | self._setBackgroundAndLabel("yellow", self.session.fixedVolume, self.session.fixedLabel)
324 | self._centerLabelsOnVisibleSliceWidgets()
325 | elif not self.session.movingTargets:
326 | self.targetingPlugin.startTargeting()
327 | else:
328 | for compositeNode, sliceNode in zip(self._compositeNodes, self._sliceNodes):
329 | compositeNode.SetLabelVolumeID(self.session.fixedLabel.GetID())
330 | compositeNode.SetLabelOpacity(1)
331 | sliceNode.SetUseLabelOutline(True)
332 |
333 | def _setBackgroundAndLabel(self, viewName, volume, label):
334 | compositeNode = getattr(self, viewName+"CompositeNode")
335 | compositeNode.SetReferenceBackgroundVolumeID(volume.GetID())
336 | compositeNode.SetLabelVolumeID(label.GetID())
337 | compositeNode.SetLabelOpacity(1)
338 | sliceNode = getattr(self, viewName+"SliceNode")
339 | sliceNode.SetOrientationToAxial()
340 | sliceNode.RotateToVolumePlane(volume)
341 | sliceNode.SetUseLabelOutline(True)
342 |
343 | def _centerLabelsOnVisibleSliceWidgets(self):
344 | for widget in self.getAllVisibleWidgets():
345 | compositeNode = widget.mrmlSliceCompositeNode()
346 | sliceNode = widget.sliceLogic().GetSliceNode()
347 | labelID = compositeNode.GetLabelVolumeID()
348 | if labelID:
349 | label = slicer.mrmlScene.GetNodeByID(labelID)
350 | centroid = self.logic.getCentroidForLabel(label, self.session.segmentedLabelValue)
351 | if centroid:
352 | sliceNode.JumpSliceByCentering(centroid[0], centroid[1], centroid[2])
353 |
354 | def _onTargetingStarted(self, caller, event):
355 | self.manualSegmentationPlugin.enabled = False
356 | self.backButton.enabled = False
357 |
358 | def _onTargetingFinished(self, caller, event):
359 | self.finishStepButton.setEnabled(1 if self.logic.inputsAreSet() else 0)
360 | self.manualSegmentationPlugin.enabled = True
361 | self.backButton.enabled = True
--------------------------------------------------------------------------------
/SliceTracker/SliceTrackerUtils/watch.py:
--------------------------------------------------------------------------------
1 | __author__ = 'Christian'
2 |
3 | import sys, getopt, os, dicom
4 | import time
5 |
6 | class NotDirectoryError(Exception):
7 | pass
8 |
9 | class DICOMDirectoryObserver(object):
10 |
11 | def __init__(self, directory, host, port):
12 | if not os.path.isdir(directory):
13 | raise NotDirectoryError("The directory is actually no directory")
14 | self.directory = directory
15 | self.host = host
16 | self.port = port
17 | self.files = set()
18 |
19 | def listdirRecursive(self, rootDir):
20 | files = []
21 | for root, subFolders, dirFiles in os.walk(rootDir):
22 | for f in dirFiles:
23 | try:
24 | fileName = os.path.join(root,f)
25 | dicom.read_file(fileName)
26 | files.append(fileName)
27 | except:
28 | pass
29 | return files
30 |
31 | def watch(self, secondsToWait=1):
32 | while True:
33 | currentFiles = self.listdirRecursive(self.directory)
34 | if len(self.files) < len(currentFiles):
35 | print "Number of files changed"
36 | for newFile in self.getNewFiles(currentFiles):
37 | self.storeSCU(newFile)
38 | time.sleep(secondsToWait)
39 |
40 | def getNewFiles(self, files):
41 | newFiles = []
42 | for currentFile in files:
43 | if currentFile not in self.files:
44 | newFiles.append(currentFile)
45 | return newFiles
46 |
47 | def storeSCU(self, fileName):
48 | cmd = ('storescu ' + self.host + ' ' + self.port + ' ' + fileName)
49 | print cmd
50 | os.system(cmd)
51 | self.files.add(fileName)
52 |
53 | def main(argv):
54 | watchDirectory = ''
55 | host = 'localhost'
56 | port = '11112'
57 | interval = 1
58 | try:
59 | opts, args = getopt.getopt(argv,"i:d:h:p:?",["help","directory=","host=","port=","interval="])
60 | except getopt.GetoptError:
61 | print 'watch.py -d -h -p -i '
62 | sys.exit(2)
63 | for opt, arg in opts:
64 | if opt in ("-?", "--help"):
65 | print 'watch.py -d -h -p '
66 | sys.exit()
67 | elif opt in ("-d", "--directory"):
68 | watchDirectory = arg
69 | elif opt in ("-h", "--host"):
70 | host = arg
71 | elif opt in ("-p", "--port"):
72 | port = arg
73 | elif opt in ("-i", "--interval"):
74 | interval = int(arg)
75 | if watchDirectory and host and port:
76 | print 'Directory to watch is: ', watchDirectory
77 | print 'Host to send DICOM files to is: ', host
78 | print 'Port to send DICOM files to is: ', port
79 |
80 | watcher = DICOMDirectoryObserver(directory=watchDirectory, host=host, port=port)
81 | print "Will watch!"
82 | watcher.watch(interval)
83 |
84 | if __name__ == "__main__":
85 | main(sys.argv[1:])
86 |
87 |
88 | #client use: $ sudo storescp -v -p 104
89 | #python watch.py -d "/Users/Christian/Documents/TEST1" -h localhost -p 104 -i 1
90 |
--------------------------------------------------------------------------------
/SliceTracker/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SlicerProstate/SliceTracker/c5c341d2c3a204275194d7f3c2720b96177fe117/SliceTracker/__init__.py
--------------------------------------------------------------------------------
/Testing/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | macro(getExtensions subdirs directory)
2 | file(GLOB children RELATIVE ${directory} ${directory}/*)
3 | set(dirList "")
4 | foreach(child ${children})
5 | if(IS_DIRECTORY ${directory}/${child})
6 | list(APPEND dirList ${child})
7 | endif()
8 | endforeach()
9 | set(${subdirs} ${dirList})
10 | endmacro()
11 |
12 | if(NOT "$ENV{SLICER_EXTENSIONS_DIR}" STREQUAL "")
13 | getExtensions(possibleExtensions $ENV{SLICER_EXTENSIONS_DIR})
14 |
15 | set(ADDITIONAL_MODULE_PATHS "")
16 |
17 | foreach(subdir ${possibleExtensions})
18 | #message("${EXTENSION_DEPENDS}")
19 | string(FIND "${EXTENSION_DEPENDS}" "${subdir}" pos)
20 | if(${pos} GREATER -1)
21 | message("$ENV{SLICER_EXTENSIONS_DIR}/${subdir}/${Slicer_QTSCRIPTEDMODULES_LIB_DIR}")
22 | list(APPEND ADDITIONAL_MODULE_PATHS "$ENV{SLICER_EXTENSIONS_DIR}/${subdir}/${Slicer_QTSCRIPTEDMODULES_LIB_DIR}")
23 | endif()
24 | #ADD_SUBDIRECTORY(${subdir})
25 | endforeach()
26 |
27 | slicer_add_python_unittest(
28 | SCRIPT SliceTrackerTests.py
29 | SLICER_ARGS
30 | --additional-module-paths ${ADDITIONAL_MODULE_PATHS}
31 | )
32 | else()
33 | slicer_add_python_unittest(SCRIPT SliceTrackerTests.py)
34 | endif()
--------------------------------------------------------------------------------
/Testing/SliceTrackerTests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import os, inspect, slicer
3 | from SliceTrackerUtils.session import SliceTrackerSession
4 | from SliceTrackerUtils.sessionData import SessionData
5 |
6 | __all__ = ['SliceTrackerSessionTests', 'RegistrationResultsTest']
7 |
8 | tempDir = os.path.join(slicer.app.temporaryPath, "SliceTrackerResults")
9 |
10 | # class SliceTrackerSessionTests(unittest.TestCase):
11 | #
12 | # @classmethod
13 | # def setUpClass(cls):
14 | # cls.session = SliceTrackerSession()
15 | #
16 | # def runTest(self):
17 | # self.test_SliceTrackerSessionEvents()
18 | # self.test_SliceTrackerSessionSingleton()
19 | #
20 | # def test_SliceTrackerSessionEvents(self):
21 | # self.directoryChangedEventCalled = False
22 | # self.session.addEventObserver(self.session.DirectoryChangedEvent,
23 | # lambda event,caller:setattr(self, "directoryChangedEventCalled", True))
24 | #
25 | # self.assertFalse(self.directoryChangedEventCalled)
26 | # self.session.directory = tempDir
27 | # self.assertTrue(self.directoryChangedEventCalled)
28 | #
29 | # def test_SliceTrackerSessionSingleton(self):
30 | # session = SliceTrackerSession()
31 | # self.assertTrue(self.session is session)
32 | # self.assertTrue(session.directory == self.session.directory)
33 |
34 |
35 | class RegistrationResultsTest(unittest.TestCase):
36 |
37 | @classmethod
38 | def setUpClass(cls):
39 | cls.registrationResults = SessionData()
40 |
41 | def runTest(self):
42 | self.test_Reading_json()
43 | self.test_Writing_json()
44 |
45 | def test_Reading_json(self):
46 | directory = os.path.join(os.path.dirname(inspect.getfile(self.__class__)), "..", "doc")
47 | inputFileName = os.path.join(directory, "output_example.json")
48 | self.registrationResults.load(inputFileName)
49 |
50 | def test_Writing_json(self):
51 | self.registrationResults.resumed = True
52 | self.registrationResults.completed = True
53 | self.registrationResults.save(tempDir)
--------------------------------------------------------------------------------
/doc/output_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "usedPreopData": true,
3 | "biasCorrected": true,
4 | "preop": {
5 | "segmentation": {
6 | "algorithm": "automatic",
7 | "startTime": "2017-03-07T16:39:20.38Z",
8 | "endTime": "2017-03-07T16:39:20.38Z",
9 | "original": "t2-label.nrrd",
10 | "userModified": {
11 | "startTime": "2017-03-07T16:39:20.38Z",
12 | "endTime": "2017-03-07T16:39:20.38Z",
13 | "fileName": "t2-label_modified.nrrd"
14 | }
15 | }
16 | },
17 | "initialVolume": "VOLUME-PREOP-N4.nrrd",
18 | "initialTargets": "PreopTargets.fcsv",
19 | "zFrameRegistration": {
20 | "name": "2: AX TSE T2 COVER TEMPLATE",
21 | "startTime": "2017-03-07T16:39:20.38Z",
22 | "endTime": "2017-03-07T16:39:20.38Z",
23 | "seriesType": "COVER TEMPLATE",
24 | "volume": "2-CoverTemplate.nrrd",
25 | "transform": "2-ZFrameTransform.h5"
26 | },
27 | "procedureEvents": {
28 | "caseStarted": "2017-03-07T15:23:20.38Z",
29 | "caseClosed": [
30 | {
31 | "time": "2017-03-07T16:33:20.38Z",
32 | "logfile": "Slicer_25707_20170308_144138.log"
33 | }
34 | ]
35 | },
36 | "results": [
37 | {
38 | "name": "3: AXIAL T2 COVER PROSTATE 3mm 0gap at iso",
39 | "seriesType": "COVER PROSTATE",
40 | "receivedTime": "2017-03-07T15:38:20.38Z",
41 | "status": {
42 | "state": "approved",
43 | "time": "2017-03-07T16:39:20.38Z"
44 | },
45 | "registrationType": "bSpline",
46 | "labels": {
47 | "fixed": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso-label.nrrd",
48 | "moving": "t2-label.nrrd"
49 | },
50 | "volumes": {
51 | "fixed": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso.nrrd",
52 | "moving": "VOLUME-PREOP-N4_1.nrrd",
53 | "bSpline": "3-VOLUME-bSpline.nrrd",
54 | "affine": "3-VOLUME-affine.nrrd",
55 | "rigid": "3-VOLUME-rigid.nrrd"
56 | },
57 | "transforms": {
58 | "bSpline": "3-TRANSFORM-bSpline.h5",
59 | "affine": "3-TRANSFORM-affine.h5",
60 | "rigid": "3-TRANSFORM-rigid.h5"
61 | },
62 | "targets": {
63 | "original": "targets-PREOP.fcsv",
64 | "bSpline": "3-TARGETS-bSpline.fcsv",
65 | "affine": "3-TARGETS-affine.fcsv",
66 | "rigid": "3-TARGETS-rigid.fcsv",
67 | "approved": {
68 | "userModified": [
69 | false
70 | ],
71 | "fileName": "3-TARGETS-approved.fcsv"
72 | }
73 | },
74 | "segmentation": {
75 | "algorithm": "automatic",
76 | "startTime": "2017-03-07T16:39:20.38Z",
77 | "endTime": "2017-03-07T16:39:20.38Z",
78 | "original": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso-label.nrrd",
79 | "userModified": {
80 | "startTime": "2017-03-07T16:39:20.38Z",
81 | "endTime": "2017-03-07T16:39:20.38Z",
82 | "fileName": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso-label_modified.nrrd"
83 | }
84 | },
85 | "registration": {
86 | "startTime": "2017-03-07T16:39:20.38Z",
87 | "endTime": "2017-03-07T16:39:20.38Z"
88 | },
89 | "score": 5
90 | },
91 | {
92 | "name": "5: AX TSE T2 GUIDANCE FOR NEEDLE at FIX _H0",
93 | "seriesType": "COVER TEMPLATE",
94 | "receivedTime": "2017-03-07T15:43:20.38Z",
95 | "status": {
96 | "state": "approved",
97 | "time": "2017-03-07T16:44:20.38Z"
98 | },
99 | "registration": {
100 | "startTime": "2017-03-07T16:39:20.38Z",
101 | "endTime": "2017-03-07T16:39:20.38Z"
102 | },
103 | "registrationType": "bSpline",
104 | "labels": {
105 | "fixed": "5-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0-label.nrrd",
106 | "moving": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso-label.nrrd"
107 | },
108 | "transforms": {
109 | "bSpline": "5-TRANSFORM-bSpline.h5",
110 | "affine": "5-TRANSFORM-affine.h5",
111 | "rigid": "5-TRANSFORM-rigid.h5"
112 | },
113 | "targets": {
114 | "original": "3-TARGETS-approved.fcsv",
115 | "bSpline": "5-TARGETS-bSpline.fcsv",
116 | "affine": "5-TARGETS-affine.fcsv",
117 | "rigid": "5-TARGETS-rigid.fcsv",
118 | "approved": {
119 | "userModified": [
120 | true
121 | ],
122 | "fileName": "5-TARGETS-approved.fcsv"
123 | }
124 | },
125 | "volumes": {
126 | "bSpline": "5-VOLUME-bSpline.nrrd",
127 | "affine": "5-VOLUME-affine.nrrd",
128 | "rigid": "5-VOLUME-rigid.nrrd",
129 | "moving": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso.nrrd",
130 | "fixed": "5-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0.nrrd"
131 | }
132 | },
133 | {
134 | "name": "6: AX TSE T2 GUIDANCE FOR NEEDLE at FIX _H0",
135 | "receivedTime": "2017-03-07T15:48:20.38Z",
136 | "status": {
137 | "state": "rejected",
138 | "time": "2017-03-07T16:49:20.38Z"
139 | },
140 | "registration": {
141 | "startTime": "2017-03-07T16:39:20.38Z",
142 | "endTime": "2017-03-07T16:39:20.38Z"
143 | },
144 | "labels": {
145 | "fixed": "6-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0-label.nrrd",
146 | "moving": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso-label.nrrd"
147 | },
148 | "transforms": {
149 | "bSpline": "6-TRANSFORM-bSpline.h5",
150 | "affine": "6-TRANSFORM-affine.h5",
151 | "rigid": "6-TRANSFORM-rigid.h5"
152 | },
153 | "targets": {
154 | "bSpline": "6-TARGETS-bSpline.fcsv",
155 | "affine": "6-TARGETS-affine.fcsv",
156 | "rigid": "6-TARGETS-rigid.fcsv",
157 | "original": "3-TARGETS-approved.fcsv"
158 | },
159 | "volumes": {
160 | "bSpline": "6-VOLUME-bSpline.nrrd",
161 | "affine": "6-VOLUME-affine.nrrd",
162 | "rigid": "6-VOLUME-rigid.nrrd",
163 | "moving": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso.nrrd",
164 | "fixed": "6-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0.nrrd"
165 | }
166 | },
167 | {
168 | "name": "9: AX TSE T2 GUIDANCE FOR NEEDLE at FIX _H0",
169 | "receivedTime": "2017-03-07T15:51:20.38Z",
170 | "status": {
171 | "state": "skipped",
172 | "time": "2017-03-07T16:51:43.38Z"
173 | },
174 | "volumes": {
175 | "fixed": "9-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0.nrrd"
176 | }
177 | },
178 | {
179 | "name": "10: AX TSE T2 GUIDANCE FOR NEEDLE at FIX _H0_Retry_1",
180 | "receivedTime": "2017-03-07T15:53:20.38Z",
181 | "status": {
182 | "state": "approved",
183 | "time": "2017-03-07T16:54:43.38Z"
184 | },
185 | "suffix": "_Retry_1",
186 | "registration": {
187 | "startTime": "2017-03-07T16:39:20.38Z",
188 | "endTime": "2017-03-07T16:39:20.38Z"
189 | },
190 | "registrationType": "bSpline",
191 | "labels": {
192 | "fixed": "10-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0-label_Retry_1.nrrd",
193 | "moving": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso-label.nrrd"
194 | },
195 | "transforms": {
196 | "bSpline": "10-TRANSFORM-bSpline_Retry_1.h5",
197 | "affine": "10-TRANSFORM-affine_Retry_1.h5",
198 | "rigid": "10-TRANSFORM-rigid_Retry_1.h5"
199 | },
200 | "targets": {
201 | "original": "3-TARGETS-approved.fcsv",
202 | "bSpline": "10-TARGETS-bSpline_Retry_1.fcsv",
203 | "affine": "10-TARGETS-affine_Retry_1.fcsv",
204 | "rigid": "10-TARGETS-rigid_Retry_1.fcsv",
205 | "approved": {
206 | "userModified": [
207 | false
208 | ],
209 | "fileName": "10-TARGETS-approved.fcsv"
210 | }
211 | },
212 | "segmentation": {
213 | "algorithm": "manual",
214 | "startTime": "2017-03-07T16:39:20.38Z",
215 | "endTime": "2017-03-07T16:39:20.38Z",
216 | "original": "10-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0-label_Retry_1.nrrd"
217 | },
218 | "score": 5,
219 | "volumes": {
220 | "fixed": "10-AX-TSE-T2-GUIDANCE-FOR-NEEDLE-at-FIX-_H0.nrrd",
221 | "moving": "3-AXIAL-T2-COVER-PROSTATE-3mm-0gap-at-iso.nrrd",
222 | "bSpline": "10-VOLUME-bSpline_Retry_1.nrrd",
223 | "affine": "10-VOLUME-affine_Retry_1.nrrd",
224 | "rigid": "10-VOLUME-rigid_Retry_1.nrrd"
225 | }
226 | }
227 | ]
228 | }
--------------------------------------------------------------------------------
/doc/output_schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" :"https://raw.githubusercontent.com/slicerprostate/slicetracker/master/doc/output_schema.json#",
3 | "$schema": "http://json-schema.org/draft-04/schema#",
4 | "additionalProperties": false,
5 | "properties": {
6 | "procedureEvents": {
7 | "type": "object",
8 | "additionalProperties": false,
9 | "properties": {
10 | "caseStarted": { "$ref": "#/definitions/TIMESTAMP" },
11 | "caseClosed": { "$ref": "#/definitions/TIMESTAMP_LOGFILE_LIST" },
12 | "caseResumed": { "$ref": "#/definitions/TIMESTAMP_LIST" },
13 | "caseCompleted": { "$ref": "#/definitions/TIMESTAMP_LOGFILE" }
14 | }, "require": ["caseStarted", "caseClosed"]
15 | },
16 | "usedPreopData": { "type": "boolean" },
17 | "biasCorrected": { "type": "boolean" },
18 | "preop": {
19 | "segmentation": { "$ref": "#/definitions/SEGMENTATION" }
20 | },
21 | "initialVolume": { "type": "string" },
22 | "initialTargets": { "type": "string" },
23 | "zFrameRegistration": {
24 | "type": "object",
25 | "additionalProperties": false,
26 | "properties": {
27 | "name": { "type": "string" },
28 | "startTime": { "$ref": "#/definitions/TIMESTAMP" },
29 | "endTime": { "$ref": "#/definitions/TIMESTAMP" },
30 | "seriesType": {"type": "string" },
31 | "volume": { "$ref": "#/definitions/NRRD_FILE" },
32 | "transform": { "$ref": "#/definitions/TRANSFORM_FILE" }
33 | }, "require": ["name", "startTime", "endTime", "volume", "transform"]
34 | },
35 | "results": { "$ref": "#/definitions/RESULTS" }
36 | },
37 | "required": ["procedureEvents", "usedPreopData", "initialVolume", "initialTargets", "zFrameRegistration", "results"],
38 | "definitions": {
39 | "RESULTS": {
40 | "type": "array",
41 | "items": { "$ref": "#/definitions/RESULT" }
42 | },
43 | "RESULT": {
44 | "type": "object",
45 | "additionalProperties": false,
46 | "properties": {
47 | "name": { "type": "string" },
48 | "seriesType": {"type": "string" },
49 | "receivedTime": { "$ref": "#/definitions/TIMESTAMP" },
50 | "status": {
51 | "type": "object",
52 | "additionalProperties": false,
53 | "properties": {
54 | "state": {"type": "string",
55 | "enum": [ "approved", "skipped", "rejected"]
56 | },
57 | "time": { "$ref": "#/definitions/TIMESTAMP" }
58 | }, "required": ["state", "time"]
59 | },
60 | "segmentation": { "$ref": "#/definitions/SEGMENTATION" },
61 | "registration": { "$ref": "#/definitions/REGISTRATION" },
62 | "registrationType": { "$ref": "#/definitions/REGISTRATION_TYPE" },
63 | "score": { "type": "number" },
64 | "suffix": {
65 | "type": "string",
66 | "pattern": "^_Retry_([0-9])+$"
67 | },
68 | "labels": { "$ref": "#/definitions/VOLUME_TYPES" },
69 | "transforms": { "$ref": "#/definitions/REGISTRATION_TYPES" },
70 | "volumes": {
71 | "allOf": [
72 | { "$ref": "#/definitions/REGISTRATION_TYPES" },
73 | { "$ref": "#/definitions/VOLUME_TYPES" }
74 | ]
75 | },
76 | "targets": { "$ref": "#/definitions/TARGETS" }
77 | },
78 | "required": ["name", "status", "receivedTime"]
79 | },
80 | "REGISTRATION_TYPE": {
81 | "type": "string",
82 | "enum": ["rigid", "affine", "bSpline"
83 | ]
84 | },
85 | "REGISTRATION_TYPES": {
86 | "type": "object",
87 | "properties": {
88 | "rigid": { "type": "string" },
89 | "affine": { "type": "string" },
90 | "bSpline": { "type": "string" }
91 | }
92 | },
93 | "VOLUME_TYPES": {
94 | "type": "object",
95 | "properties": {
96 | "fixed": { "type": "string" },
97 | "moving": { "type": "string" }
98 | }
99 | },
100 | "TARGETS" : {
101 | "allOf": [
102 | { "$ref": "#/definitions/REGISTRATION_TYPES" },
103 | {
104 | "properties": {
105 | "original": { "type": "string" },
106 | "approved": {
107 | "type": "object",
108 | "additionalProperties": false,
109 | "properties": {
110 | "userModified": {
111 | "type": "array",
112 | "items": {
113 | "type": "boolean"
114 | }
115 | },
116 | "fileName": { "$ref": "#/definitions/FIDUCIALS_FILE" }
117 | }, "required": ["userModified"]
118 | }
119 | }, "required": ["original", "rigid", "affine", "bSpline"]
120 | }
121 | ],
122 | "required": ["original"]
123 | },
124 | "TRANSFORM_FILE": {
125 | "type": "string",
126 | "pattern": "^([0-9])+-([a-z,A-Z])+.h5$"
127 | },
128 | "FIDUCIALS_FILE": {
129 | "type": "string",
130 | "pattern": "^([0-9])+-([a-z,A-Z])+-approved.fcsv$"
131 | },
132 | "NRRD_FILE": {
133 | "type": "string",
134 | "pattern": ".nrrd$"
135 | },
136 | "TIMESTAMP": {
137 | "type": "string",
138 | "format": "date-time"
139 | },
140 | "TIMESTAMP_LIST": {
141 | "type": "array",
142 | "items": { "$ref": "#/definitions/TIMESTAMP" }
143 | },
144 | "TIMESTAMP_LOGFILE_LIST": {
145 | "type": "array",
146 | "items": { "$ref": "#/definitions/TIMESTAMP_LOGFILE" }
147 | },
148 | "TIMESTAMP_LOGFILE": {
149 | "type": "object",
150 | "additionalProperties": false,
151 | "properties": {
152 | "time": { "$ref": "#/definitions/TIMESTAMP" },
153 | "logfile": { "type": "string" }
154 | }, "required": ["time", "logfile"]
155 | },
156 | "SEGMENTATION": {
157 | "type": "object",
158 | "additionalProperties": false,
159 | "properties": {
160 | "algorithm": {
161 | "type": "string",
162 | "enum": [ "manual", "automatic"]
163 | },
164 | "startTime": { "$ref": "#/definitions/TIMESTAMP" },
165 | "endTime": { "$ref": "#/definitions/TIMESTAMP" },
166 | "original": { "$ref": "#/definitions/NRRD_FILE" },
167 | "userModified": {
168 | "type": "object",
169 | "additionalProperties": false,
170 | "properties": {
171 | "startTime": { "$ref": "#/definitions/TIMESTAMP" },
172 | "endTime": { "$ref": "#/definitions/TIMESTAMP" },
173 | "fileName": { "$ref": "#/definitions/NRRD_FILE" }
174 | }
175 | }
176 | }, "required": ["algorithm", "startTime", "endTime", "original"]
177 | },
178 | "REGISTRATION": {
179 | "type": "object",
180 | "additionalProperties": false,
181 | "properties": {
182 | "startTime": { "$ref": "#/definitions/TIMESTAMP" },
183 | "endTime": { "$ref": "#/definitions/TIMESTAMP" }
184 | }, "required": ["startTime", "endTime"]
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/puml/RegistrationResults.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | package "RegistrationResults" #DDDDDD {
4 |
5 | class SessionData {
6 | .. events ..
7 | + NewResultCreatedEvent
8 | .. properties ..
9 | + resumed
10 | + completed
11 | __
12 | + startTimeStamp: localtime
13 | + resumeTimeStamps: list(localtime)
14 | + closedTimeStamps: list(localtime)
15 | * completedTimeStamp
16 | + usePreopData: boolean
17 | + biasCorrectionDone: boolean
18 |
19 | + segmentgModelNode
20 | + inputMarkupNode
21 |
22 | + initialVolume
23 | + initialTargets
24 |
25 | # _savedRegistrationResults: list
26 | # _registrationResults: OrderedDict
27 | __
28 | + resetAndInitializeData()
29 | + load(directory, filename)
30 | + save(outputDir)
31 |
32 | # _loadResultFileData(directory, filename, loadFunction, setFunction)
33 | # _loadOrGetFileData(directory, filename, loadFunction)
34 |
35 | }
36 |
37 | ModuleLogicMixin <|-- SessionData
38 |
39 | class RegistrationStatus {
40 | + UNDEFINED_STATUS = "undefined"
41 | + SKIPPED_STATUS = "skipped"
42 | + APPROVED_STATUS = "approved"
43 | + REJECTED_STATUS = "rejected"
44 | .. properties ..
45 | + approved
46 | + skipped
47 | + rejected
48 | --
49 | + status
50 | --
51 | + hasStatus(expected): bool
52 | + approve()
53 | + skip()
54 | + reject()
55 | }
56 |
57 | ModuleLogicMixin <|-- RegistrationStatus
58 |
59 |
60 | class RegistrationResult {
61 | + {static} getSeriesNumberFromString
62 | # _targets
63 | # _volumes
64 | # _labels
65 | # _transforms
66 | }
67 |
68 | RegistrationStatus <|-- RegistrationResult
69 |
70 | class AbstractRegistrationData {
71 | + FILE_EXTENSION
72 | --
73 | + {abstract} initializeMembers()
74 | + {abstract} asList(): list
75 | + {abstract} asDict(): dict
76 | --
77 | + getFileName(node): string
78 | + getFileNameByAttributeName(name): string
79 | + getAllFileNames(): string[]
80 | + save(directory)
81 | }
82 |
83 | ModuleLogicMixin <|-- AbstractRegistrationData
84 | AbstractRegistrationData <|-- RegistrationTypeData
85 |
86 | class RegistrationTypeData {
87 | + rigid
88 | + affine
89 | + bSpline
90 | }
91 |
92 | class Labels {
93 | + FILE_EXTENSION: FileExtension.NRRD
94 | + fixed
95 | + moving
96 | }
97 |
98 | class Volumes {
99 | + FILE_EXTENSION: FileExtension.NRRD
100 | + fixed
101 | + moving
102 | }
103 |
104 | class Transforms {
105 | + FILE_EXTENSION: FileExtension.H5
106 | }
107 |
108 | class Targets {
109 | + FILE_EXTENSION: FileExtension.FCSV
110 | + original
111 | + approved
112 | + approve(registrationType)
113 | }
114 |
115 | AbstractRegistrationData <|-- Labels
116 | RegistrationTypeData <|-- Volumes
117 | RegistrationTypeData <|-- Transforms
118 | RegistrationTypeData <|-- Targets
119 |
120 | Volumes <.. RegistrationResult::_volumes
121 | Targets <.. RegistrationResult::_targets
122 | Labels <.. RegistrationResult::_labels
123 | Transforms <.. RegistrationResult::_transforms
124 |
125 | SessionData::_registrationResults ..> RegistrationResult
126 | }
127 |
128 | @enduml
--------------------------------------------------------------------------------
/puml/SliceTracker.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | !include SlicerDevelopmentToolbox.puml
4 | !include RegistrationResults.puml
5 | !include SliceTrackerSession.puml
6 | !include SliceTrackerSteps.puml
7 |
8 |
9 | ModuleWidgetMixin <|-- SliceTrackerWidget
10 | SliceTrackerConstants <|-- SliceTrackerWidget
11 |
12 |
13 | class SliceTrackerWidget {
14 | .. properties ..
15 | + session
16 | + stepManager
17 | --
18 |
19 | + enter(
20 | + setup()
21 | + cleanup()
22 |
23 | }
24 |
25 | SliceTrackerSession <.. SliceTrackerWidget::session: uses
26 | SliceTrackerStepManager <.. SliceTrackerWidget::stepManager
27 |
28 | @enduml
29 |
--------------------------------------------------------------------------------
/puml/SliceTrackerPlugins.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | abstract class SliceTrackerPlugin {
4 | + {abstract} LogicClass
5 | }
6 |
7 | class SliceTrackerLogicBase
8 | SliceTrackerLogicBase <.. SliceTrackerPlugin::LogicClass: uses instance of
9 |
10 |
11 | package "SliceTracker Plugins" #DDDDDD {
12 |
13 | package "Training Management" #FDADDF {
14 |
15 | class SliceTrackerTrainingPlugin {
16 | }
17 |
18 | SliceTrackerPlugin <|-- SliceTrackerTrainingPlugin
19 | }
20 |
21 | package "Registration Results Management" #FDADDF {
22 |
23 | class SliceTrackerRegistrationResultsPlugin {
24 | + LogicClass
25 | + onLayoutChanged()
26 | }
27 |
28 | class SliceTrackerRegistrationResultsLogic
29 |
30 | SliceTrackerPlugin <|-- SliceTrackerRegistrationResultsPlugin
31 | SliceTrackerRegistrationResultsPlugin::LogicClass ..> SliceTrackerRegistrationResultsLogic: uses
32 |
33 | }
34 |
35 | package "Target Management" #FDDDDD {
36 |
37 | class SliceTrackerTargetTablePlugin {
38 | + LogicClass
39 | + targetTableModel
40 | }
41 |
42 | class SliceTrackerTargetTableLogic {
43 | }
44 |
45 | class ZFrameGuidanceComputation {
46 | }
47 |
48 | class CustomTargetTableModel {
49 | # _guidanceComputations
50 | }
51 |
52 | SliceTrackerPlugin <|-- SliceTrackerTargetTablePlugin
53 | SliceTrackerTargetTablePlugin::LogicClass ..> SliceTrackerTargetTableLogic: uses
54 | SliceTrackerTargetTablePlugin::targetTableModel ..> CustomTargetTableModel: uses
55 | ZFrameGuidanceComputation <.. CustomTargetTableModel::_guidanceComputations
56 | }
57 |
58 | package "Targeting" #FDDDFF {
59 |
60 | class SliceTrackerTargetingPlugin {
61 | + LogicClass
62 | }
63 |
64 | class SliceTrackerTargetTargetingLogic {
65 | }
66 |
67 | SliceTrackerPlugin <|-- SliceTrackerTargetingPlugin
68 | SliceTrackerTargetingPlugin::LogicClass ..> SliceTrackerTargetTargetingLogic: uses
69 |
70 | }
71 |
72 | package "Case Management" #DDF0DD {
73 |
74 | class SliceTrackerCaseManagerPlugin {
75 | + LogicClass
76 | }
77 |
78 | class SliceTrackerCaseManagerLogic {
79 | }
80 |
81 | SliceTrackerPlugin <|-- SliceTrackerCaseManagerPlugin
82 | SliceTrackerCaseManagerPlugin::LogicClass ..> SliceTrackerCaseManagerLogic: uses
83 | }
84 |
85 | package "Future Plugins" #DDFFDD {
86 | class SliceTrackerNeedleSegmentationPlugin {
87 | + LogicClass
88 | }
89 |
90 | SliceTrackerPlugin <|-- SliceTrackerNeedleSegmentationPlugin
91 | SliceTrackerNeedleSegmentationPlugin::LogicClass ..> SliceTrackerNeedleSegmentationLogic: uses
92 |
93 | class SliceTrackerTargetDisplacementChartPlugin {
94 | + LogicClass
95 | }
96 |
97 | SliceTrackerPlugin <|-- SliceTrackerTargetDisplacementChartPlugin
98 | SliceTrackerTargetDisplacementChartPlugin::LogicClass ..> SliceTrackerTargetDisplacementChartLogic: uses
99 | }
100 |
101 | package "Segmentation Management" #5555FF {
102 |
103 | class SliceTrackerSegmentationPluginBase {
104 | .. event ..
105 | + SegmentationStartedEvent
106 | + SegmentationFinishedEvent
107 | }
108 |
109 | SliceTrackerPlugin <|-- SliceTrackerSegmentationPluginBase
110 |
111 | class SliceTrackerAutomaticSegmentationPlugin {
112 | + LogicClass
113 | }
114 | class AutomaticSegmentationLogic
115 |
116 | SliceTrackerSegmentationPluginBase <|--SliceTrackerAutomaticSegmentationPlugin
117 | AutomaticSegmentationLogic <.. SliceTrackerAutomaticSegmentationPlugin::LogicClass: uses
118 |
119 |
120 | class SliceTrackerManualSegmentationPlugin {
121 | .. event ..
122 | + SegmentationCancelledEvent
123 | __
124 | + volumeClipToLabelWidget
125 | }
126 |
127 | SliceTrackerSegmentationPluginBase <|--SliceTrackerManualSegmentationPlugin
128 | SurfaceCutToLabelWidget <.. SliceTrackerManualSegmentationPlugin::volumeClipToLabelWidget: uses
129 |
130 | class SurfaceCutToLabelWidget {
131 | .. event ..
132 | + SegmentationFinishedEvent
133 | + SegmentationStartedEvent
134 | + SegmentationCanceledEvent
135 | --
136 | + logic
137 | }
138 |
139 | class SurfaceCutToLabelLogic {
140 | }
141 |
142 | SurfaceCutToLabelWidget::logic ..> SurfaceCutToLabelLogic: uses
143 |
144 |
145 | }
146 |
147 | }
148 |
149 | SliceTrackerWidgetBase <|-- SliceTrackerPlugin
150 |
151 |
152 | @enduml
--------------------------------------------------------------------------------
/puml/SliceTrackerSession.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | package "Session" #DDDDDD {
4 |
5 | abstract class SessionBase {
6 | .. events ..
7 | DirectoryChangedEvent: vtk.vtkCommand.UserEvent
8 | .. properties ..
9 | + directory
10 | --
11 | + {abstract} load()
12 | + {abstract} save()
13 | }
14 |
15 |
16 | Singleton <-- SliceTrackerSession
17 | SessionBase <-- SliceTrackerSession
18 |
19 | class SliceTrackerSession {
20 | .. events ..
21 | --
22 | .. properties ..
23 | + preopDICOMDirectory
24 | + intraopDICOMDirectory
25 | + preprocessedDirectory
26 | + outputDirectory
27 | + currentSeries
28 | --
29 | + steps
30 | + trainingMode: boolean
31 | --
32 | + intraopLabel
33 | + regResults: RegistrationResults
34 | __
35 | + createNewCase(destination)
36 | + openCase(directory)
37 | + closeCase()
38 | + completeCase()
39 | + registerStep(step)
40 | + startIntraopDICOMReceiver()
41 | + stopIntraopDICOMReceiver()
42 | + importDICOMSeries(newFileList)
43 | + makeSeriesNumberDescription(dcmFile): string
44 | + createLoadableFileListForSeries(series): list
45 | }
46 | }
47 |
48 | SliceTrackerStep <.. SliceTrackerSession::steps: has [0..n]
49 | RegistrationResults <.. SliceTrackerSession::regResults : use
50 |
51 | @enduml
--------------------------------------------------------------------------------
/puml/SliceTrackerSteps.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | !include SliceTrackerPlugins.puml
4 |
5 | package "SliceTrackerSteps" #DDDDDD {
6 |
7 | class SliceTrackerTabWidget {
8 | + session
9 | + __init__(session)
10 | # _createTabs()
11 | + onCurrentTabChanged(step)
12 | }
13 |
14 | abstract class StepBase {
15 | + MODULE_NAME = "SliceTracker"
16 | __
17 | + session
18 | + modulePath: string
19 | + resourcesPath
20 | __
21 | + getModulePath(): string
22 | + getSetting(self, setting, moduleName=None, default=None)
23 | + setSetting(self, setting, value, moduleName=None)
24 | }
25 |
26 | abstract class SliceTrackerWidgetBase {
27 | + {abstract} NAME: string
28 | + {abstract} LogicClass
29 | + {abstract} setup()
30 | + {abstract} cleanup()
31 | + {abstract} setupConnections()
32 | + {abstract} onLayoutChanged()
33 | + setupSessionObservers()
34 | + setupSliceWidgets()
35 | + setupAdditionalViewSettingButtons()
36 | + resetViewSettingButtons()
37 | + onNewCaseStarted()
38 | + onCaseClosed()
39 | + onIncomingDataSkipped()
40 | + onNewImageDataReceived()
41 | + onCoverTemplateReceived()
42 | + onZFrameRegistrationSuccessful()
43 | }
44 |
45 | StepBase <|-- SliceTrackerWidgetBase
46 |
47 | abstract class SliceTrackerStep {
48 | # _plugins
49 | .. events ..
50 | ActivatedEvent: vtk.vtkCommand.UserEvent
51 | DeactivatedEvent: vtk.vtkCommand.UserEvent
52 | .. properties ..
53 | + active: bool
54 | __
55 | + onActivation()
56 | + onDeactivation()
57 | + removeSessionEventObservers()
58 | }
59 |
60 | SliceTrackerWidgetBase <|-- SliceTrackerStep
61 | SliceTrackerPlugin <.. SliceTrackerStep::_plugins: has [0..n]
62 |
63 |
64 | abstract class SliceTrackerLogicBase {
65 | .. abstract methods ..
66 | + {abstract} cleanup()
67 | __
68 | + getOrCreateVolumeForSeries(series)
69 | }
70 |
71 | StepBase <|-- SliceTrackerLogicBase
72 |
73 | class SliceTrackerOverviewStep {
74 | + onLayoutChanged()
75 | + trainingPlugin
76 | + regResultPlugin
77 | + targetTablePlugin
78 | }
79 |
80 | SliceTrackerTrainingPlugin <.. SliceTrackerOverviewStep::trainingPlugin
81 | SliceTrackerTargetTablePlugin <.. SliceTrackerOverviewStep::targetTablePlugin
82 |
83 | class SliceTrackerSegmentationStep {
84 | + onLayoutChanged()
85 | }
86 |
87 | class SliceTrackerZFrameRegistrationStep {
88 | + onLayoutChanged()
89 | }
90 |
91 | class SliceTrackerEvaluationStep {
92 | + regResultPlugin
93 | __
94 | + onLayoutChanged()
95 | }
96 |
97 | SliceTrackerRegistrationResultsPlugin <.. SliceTrackerEvaluationStep::regResultPlugin
98 | SliceTrackerRegistrationResultsPlugin <.. SliceTrackerOverviewStep::regResultPlugin
99 |
100 |
101 | 'class OverViewStepLogic
102 | 'class SegmentationStepLogic
103 | 'class ZFrameRegistrationStepLogic
104 | 'class EvaluationStepLogic
105 | '
106 | SliceTrackerLogicBase <|-- OverViewStepLogic
107 | SliceTrackerLogicBase <|-- SegmentationStepLogic
108 | SliceTrackerLogicBase <|-- ZFrameRegistrationStepLogic
109 | SliceTrackerLogicBase <|-- EvaluationStepLogic
110 |
111 | SliceTrackerStep <|-- SliceTrackerOverviewStep
112 | SliceTrackerStep <|-- SliceTrackerSegmentationStep
113 | SliceTrackerStep <|-- SliceTrackerZFrameRegistrationStep
114 | SliceTrackerStep <|-- SliceTrackerEvaluationStep
115 |
116 | OverViewStepLogic <.. SliceTrackerOverviewStep: uses
117 | SegmentationStepLogic <.. SliceTrackerSegmentationStep: uses
118 | ZFrameRegistrationStepLogic <.. SliceTrackerZFrameRegistrationStep: uses
119 | EvaluationStepLogic <.. SliceTrackerEvaluationStep: uses
120 |
121 | SliceTrackerLogicBase <.. SliceTrackerWidgetBase::LogicClass : uses
122 | }
123 |
124 | QTabWidget <|-- SliceTrackerTabWidget
125 | QWidget <|-- SliceTrackerWidgetBase
126 | ModuleWidgetMixin <|-- SliceTrackerWidgetBase
127 | ModuleLogicMixin <|-- SliceTrackerLogicBase
128 |
129 | SliceTrackerSession <.. StepBase::session : uses
130 | 'SliceTrackerSession "0" --> "*" SliceTrackerStep
131 |
132 | @enduml
--------------------------------------------------------------------------------
/puml/SlicerDevelopmentToolbox.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | package "SlicerDevelopmentToolbox" #DDDDDD {
4 | class ModuleWidgetMixin {
5 | .. properties ..
6 | + layoutManager
7 | --
8 | + {static} truncatePath(path)
9 | + createSliceWidgetClassMembers(name)
10 | + getAllVisibleWidgets()
11 | + getOrCreateCustomProgressBar()
12 | + setFOV(sliceLogic, FOV)
13 | + removeNodeFromMRMLScene(node)
14 | + refreshViewNodeIDs(node, sliceNodes)
15 | + removeViewNodeIDs(node, sliceNodes)
16 | + jumpSliceNodeToTarget(sliceNode, targetNode, index)
17 | + resetToRegularViewMode()
18 | + confirmOrSaveDialog(message)
19 | ...
20 | }
21 | class ModuleLogicMixin
22 |
23 | }
24 |
25 | @enduml
--------------------------------------------------------------------------------
/puml/components.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | package "SliceTracker" {
4 | [OverView]
5 | [ZFrameCalibration]
6 | [Segmentation]
7 | [Evaluation]
8 |
9 | [Session]
10 |
11 | database "RegistrationResults" {
12 |
13 | }
14 | }
15 |
16 | @enduml
--------------------------------------------------------------------------------
/puml/intraop_reception.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | participant "Session" as A
3 | participant "SmartDICOMReceiver" as B
4 | participant "ParameterNodeObservationMixin" as C
5 | participant "ZFrameRegistrationStep" as D
6 | participant "OverviewStep" as E
7 | participant "Operator" as O
8 |
9 | A --> B: start storecp
10 | B --> B: receiving DICOM
11 | B --> A: DICOM reception finished
12 | A --> A: import DICOM into database
13 |
14 | alt Depending on series that has been received
15 |
16 | else COVER_TEMPLATE and not zFrameRegistratationSuccessful
17 | A --> C: invoke event CoverTemplateReceivedEvent
18 | C --> D: onCoverTemplateReceived
19 | D -> D: activate
20 | O --> D: initialize template
21 | O --> D: run calibration
22 | O --> D: approve calibration result
23 | D --> A: zFrameRegistratationSuccessful = True
24 | A --> C: invoke event ZFrameCalibrationSuccessfulEvent
25 | E -> E: activate
26 | E -> E: select eligible series
27 | else COVER_PROSTATE and zFrameRegistratationSuccessful
28 | A --> C: invoke event CoverProstateReceivedEvent
29 | A --> E:
30 | else NEEDLE_IMAGE
31 | A --> C: invoke event NeedleImageReceivedEvent
32 | else VIBE_IMAGE
33 | A --> C: invoke event VibeImageReceivedEvent
34 | else OTHERS
35 | end
36 |
37 | @enduml
--------------------------------------------------------------------------------
/startSlicelet.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | SLICER="/Applications/Slicer.app/Contents/MacOS/Slicer"
3 |
4 | $SLICER --python-code "from SliceTracker import SliceTrackerSlicelet; slicelet=SliceTrackerSlicelet();" --no-splash --no-main-window
--------------------------------------------------------------------------------
/startSlicelet_Ubuntu.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | SLICER="/home/parallels/sources/cpp/Slicer/Build/Slicer-build/Slicer"
3 |
4 | $SLICER --python-code "from SliceTracker import SliceTrackerSlicelet; slicelet=SliceTrackerSlicelet();" --no-splash --no-main-window
--------------------------------------------------------------------------------