├── .gitignore
├── CMakeLists.txt
├── CTestConfig.cmake
├── README.md
├── Screenshot01.jpg
├── Screenshot02.gif
├── SegmentMesher
├── CMakeLists.txt
├── Resources
│ ├── Icons
│ │ └── SegmentMesher.png
│ └── UI
│ │ └── SegmentMesher.ui
├── SegmentMesher.py
└── Testing
│ ├── CMakeLists.txt
│ └── Python
│ └── CMakeLists.txt
├── SlicerSegmentMesher.png
├── SlicerSegmentMesher.xcf
├── SuperBuild.cmake
└── SuperBuild
├── External_cleaver.cmake
└── External_tetgen.cmake
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore all dotfiles...
2 | .*
3 |
4 | # Ignore all back-up files...
5 | *~
6 | *.bak
7 |
8 | # except for .gitignore
9 | !.gitignore
10 |
11 | # Exclude Kdevelop4 files ...
12 | .kdev*
13 |
14 | # Exclude QtCreator files ...
15 | CMakeLists.txt.user*
16 |
17 | # Ignore ctags file
18 | tags
19 |
20 | # Ignore pyc files ...
21 | *.pyc
22 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.7)
2 | # 3.7 is required for SOURCE_SUBDIR External_cleaver.cmake
3 |
4 | project(SegmentMesher)
5 |
6 | #-----------------------------------------------------------------------------
7 | # Extension meta-information
8 | set(EXTENSION_HOMEPAGE "https://github.com/lassoan/SlicerSegmentMesher")
9 | set(EXTENSION_CATEGORY "Segmentation")
10 | set(EXTENSION_CONTRIBUTORS "Andras Lasso (PerkLab, Queen's University)")
11 | set(EXTENSION_DESCRIPTION "Create volumetric mesh from segmentation using Cleaver2 or TetGen.")
12 | set(EXTENSION_ICONURL "https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/master/SlicerSegmentMesher.png")
13 | set(EXTENSION_SCREENSHOTURLS "https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/master/Screenshot01.jpg")
14 | set(EXTENSION_DEPENDS "NA") # Specified as a space separated string, a list or 'NA' if any
15 | set(EXTENSION_BUILD_SUBDIRECTORY inner-build)
16 |
17 | set(SUPERBUILD_TOPLEVEL_PROJECT inner)
18 |
19 | #-----------------------------------------------------------------------------
20 | # Extension dependencies
21 | find_package(Slicer REQUIRED)
22 | include(${Slicer_USE_FILE})
23 | mark_as_superbuild(Slicer_DIR)
24 |
25 | find_package(Git REQUIRED)
26 | mark_as_superbuild(GIT_EXECUTABLE)
27 |
28 | #-----------------------------------------------------------------------------
29 | # SuperBuild setup
30 | option(${EXTENSION_NAME}_SUPERBUILD "Build ${EXTENSION_NAME} and the projects it depends on." ON)
31 | mark_as_advanced(${EXTENSION_NAME}_SUPERBUILD)
32 | if(${EXTENSION_NAME}_SUPERBUILD)
33 | include("${CMAKE_CURRENT_SOURCE_DIR}/SuperBuild.cmake")
34 | return()
35 | endif()
36 |
37 | #-----------------------------------------------------------------------------
38 | # Extension modules
39 | add_subdirectory(SegmentMesher)
40 | ## NEXT_MODULE
41 |
42 | #-----------------------------------------------------------------------------
43 | # install directory, install project name, install component, and install subdirectory.
44 | set(CPACK_INSTALL_CMAKE_PROJECTS "${CPACK_INSTALL_CMAKE_PROJECTS};${CMAKE_BINARY_DIR};${EXTENSION_NAME};ALL;/")
45 | set(CPACK_INSTALL_CMAKE_PROJECTS "${CPACK_INSTALL_CMAKE_PROJECTS};${tetgen_DIR};tetgen;RuntimeLibraries;/")
46 | set(CPACK_INSTALL_CMAKE_PROJECTS "${CPACK_INSTALL_CMAKE_PROJECTS};${cleaver_DIR};CLEAVER2;RuntimeCLI;/")
47 | MESSAGE(STATUS "CPACK_INSTALL_CMAKE_PROJECTS = ${CPACK_INSTALL_CMAKE_PROJECTS}")
48 |
49 | include(${Slicer_EXTENSION_CPACK})
50 |
--------------------------------------------------------------------------------
/CTestConfig.cmake:
--------------------------------------------------------------------------------
1 | set(CTEST_PROJECT_NAME "SlicerSegmentMesher")
2 | set(CTEST_NIGHTLY_START_TIME "3:00:00 UTC")
3 |
4 | set(CTEST_DROP_METHOD "http")
5 | set(CTEST_DROP_SITE "slicer.cdash.org")
6 | set(CTEST_DROP_LOCATION "/submit.php?project=Slicer4")
7 | set(CTEST_DROP_SITE_CDASH TRUE)
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Segment Mesher extension
2 |
3 | This is a 3D Slicer extension for creating volumetric meshes from segmentation using Cleaver2 or TetGen.
4 |
5 | Cleaver2 mesher is freely usable, without any restrictions.
6 | TetGen mesher is only free for private, research, and educational use (see license for details).
7 |
8 | 
9 |
10 | ## Installation
11 |
12 | * Download and install a latest stable version of 3D Slicer (https://download.slicer.org).
13 | * Start 3D Slicer application, open the Extension Manager (menu: View / Extension manager)
14 | * Install SegmentMesher extension.
15 |
16 | ## Tutorial
17 |
18 | * Start 3D Slicer
19 | * Load a volume: switch to "Sample Data" module and load MRHead image
20 | * Switch to "Segment Editor" module
21 | * Add a new segment (it will contain the entire head)
22 | * Fill segment by thresholding: click "Threshold" effect set 30 as lower threshold, click "Apply"
23 | * Smooth segment: click "Smoothing" effect, set kernel size to 6mm, click "Apply"
24 | * Add a new segment (it will contain a spherical lesion)
25 | * Paint a sphere in the brain (simulating a lesion): click "Paint" effect, enable "Sphere brush", set "Diameter" to 8%, and click in the yellow slice view
26 | * Switch to "Segment Mesher" module (in Segmentation category)
27 | * Select "Create new Model" for Output model (this will contain the generated volumetric mesh)
28 | * Click Apply button and wait a about a minute
29 | * Inspect results: open "Display" section, enable "Yellow slice clipping", move slider at the top of yellor slice view to move the clipping plane; enable "Keep only whole cells when clipping" to see shape of mesh elements
30 | * Create more accurate mesh: open "Advanced" section, set scale parameter to 0.5, click "Apply", and wait a couple of minutes
31 |
32 | ## Visualize and save results
33 | * Open "Display" section to enable clipping with slices.
34 | * Go to "Segmentations" module to hide current segmentation.
35 | * Switch to "Models" module to adjust visualization parameters.
36 | * To save Output model select in menu: File / Save.
37 |
38 | ")
39 |
40 | ## Mesh generation parameters
41 |
42 | Cleaver parameters are described at https://sciinstitute.github.io/cleaver.pages/manual.html. To make the output mesh elements smaller: decrease value of `--feature_scaling`. To make the output mesh preserve small details (at the cost of more computation time and memory usage): increase `--sampling-rate` (up to 1.0).
43 |
44 | ```
45 | Input data:
46 | -i [ --input_files ] arg material field paths or segmentation path
47 | This argument is set automatically by SlicerSegmentMesher module.
48 | -B [ --blend_sigma ] arg blending function sigma for input(s) to
49 | remove alias artifacts.
50 | Too low value will not remove staircase artifacts.
51 | Too high value may shrink structures and remove relevant details.
52 | Default: 1.0.
53 |
54 | Output data:
55 | -f [ --output_format ] arg output mesh format (tetgen [default],
56 | scirun, matlab, vtkUSG, vtkPoly, ply
57 | [surface mesh only])
58 | This argument is set automatically by SlicerSegmentMesher module to vtkUSG.
59 | -n [ --output_name ] arg output mesh name (default 'output')
60 | This argument is set automatically by SlicerSegmentMesher module.
61 | -o [ --output_path ] arg output path prefix
62 | This argument is set automatically by SlicerSegmentMesher module.
63 |
64 | Meshing mode (element size control):
65 | -m [ --element_sizing_method ] arg background mesh mode (adaptive [default],
66 | constant)
67 |
68 | For constant mode:
69 | -a [ --alpha ] arg initial alpha value, default: 0.4
70 | -s [ --alpha_short ] arg alpha short value for constant element
71 | sizing method, default: 0.203
72 | -l [ --alpha_long ] arg alpha long value for constant element
73 | sizing method, default: 0.357
74 |
75 | For adaptive mode:
76 | -F [ --feature_scaling ] arg feature size scaling (higher values make a
77 | coarser mesh), default: 1.0.
78 | Meaningful range is about 0.2 to 5.0.
79 | Lower value makes the output mesh finer,
80 | higher value makes the output mesh coarser and meshing faster.
81 | -L [ --lipschitz ] arg maximum rate of change of element size (1
82 | is uniform), default: 0.2
83 | It specifies how quickly the sizing field may grow away from size-limiting
84 | features (like corners or curved interfaces).
85 | -R [ --sampling_rate ] arg volume sampling rate (lower values make a
86 | coarser mesh), default: 1.0 (full sampling)
87 | Meaningful range is 0.1 to 1.0.
88 | Lower value makes meshing faster, higher value
89 | preserves fine details.
90 |
91 | Advanced:
92 | -b [ --background_mesh ] arg input background mesh
93 | -I [ --indicator_functions ] the input files are indicator functions (boundary is defined as isosurface
94 | where image value = 0)
95 | -z [ --sizing_field ] arg sizing field path (use precomputed sizing field for adaptive mode)
96 | -w [ --write_background_mesh ] write background mesh
97 | --simple use simple interface approximation
98 | -j [ --fix_tet_windup ] ensure positive Jacobians with proper vertex wind-up
99 | (prevents inside-out tetrahedra in the output mesh)
100 | This flag is specified by SlicerSegmentMesher module, no need to specify it as additional option.
101 | -e [ --strip_exterior ] strip exterior tetrahedra (remove temporary elements that are added to make the volume cubic)
102 | This flag is specified by SlicerSegmentMesher module, no need to specify it as additional option.
103 |
104 | Other:
105 | -h [ --help ] display help message
106 | -r [ --record ] arg record operations on tets from input file
107 | -t [ --strict ] warnings become errors
108 | -v [ --verbose ] enable verbose output
109 | This flag is specified by SlicerSegmentMesher module (based on Verbose option).
110 | -V [ --version ] display version information
111 | ```
112 |
113 | TetGen parameters are described at http://wias-berlin.de/software/tetgen/1.5/doc/manual/manual005.html#sec%3Acmdline
114 |
115 | ## Developers
116 |
117 | ### Split mesh to submeshes
118 |
119 | ```python
120 | meshNode = getNode('Model')
121 | mesh = meshNode.GetMesh()
122 | cellData = mesh.GetCellData()
123 | labelsRange = cellData.GetArray("labels").GetRange()
124 | for labelValue in range(int(labelsRange[0]), int(labelsRange[1]+1)):
125 | threshold = vtk.vtkThreshold()
126 | threshold.SetInputData(mesh)
127 | threshold.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS, "labels")
128 | threshold.ThresholdBetween(labelValue, labelValue)
129 | threshold.Update()
130 | if threshold.GetOutput().GetNumberOfPoints() > 0:
131 | modelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "{0}_{1}".format(meshNode.GetName(), labelValue))
132 | modelNode.SetAndObserveMesh(threshold.GetOutput())
133 | modelNode.CreateDefaultDisplayNodes()
134 | ```
135 |
136 | ## Acknowledgments
137 |
138 | Cleaver is an Open Source software project that is principally funded through the SCI Institute's NIH/NIGMS CIBC Center. Please use the following acknowledgment and send references to any publications, presentations, or successful funding applications that make use of NIH/NIGMS CIBC software or data sets to SCI: "This project was supported by the National Institute of General Medical Sciences of the National Institutes of Health under grant number P41 GM103545-18."
139 |
140 |
141 | TetGen citation: Si, Hang (2015). "TetGen, a Delaunay-based Tetrahedral Mesh Generator". ACM Transactions on Mathematical Software. 41 (2): 11:1-11:36. doi:10.1145/2629697
142 |
--------------------------------------------------------------------------------
/Screenshot01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/37a56a4216048df01af0c6392d90751f47707cea/Screenshot01.jpg
--------------------------------------------------------------------------------
/Screenshot02.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/37a56a4216048df01af0c6392d90751f47707cea/Screenshot02.gif
--------------------------------------------------------------------------------
/SegmentMesher/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #-----------------------------------------------------------------------------
2 | set(MODULE_NAME SegmentMesher)
3 |
4 | #-----------------------------------------------------------------------------
5 | set(MODULE_PYTHON_SCRIPTS
6 | ${MODULE_NAME}.py
7 | )
8 |
9 | set(MODULE_PYTHON_RESOURCES
10 | Resources/Icons/${MODULE_NAME}.png
11 | Resources/UI/${MODULE_NAME}.ui
12 | )
13 |
14 | #-----------------------------------------------------------------------------
15 | slicerMacroBuildScriptedModule(
16 | NAME ${MODULE_NAME}
17 | SCRIPTS ${MODULE_PYTHON_SCRIPTS}
18 | RESOURCES ${MODULE_PYTHON_RESOURCES}
19 | WITH_GENERIC_TESTS
20 | )
21 |
22 | #-----------------------------------------------------------------------------
23 | if(BUILD_TESTING)
24 | # Register the unittest subclass in the main script as a ctest.
25 | # Note that the test will also be available at runtime.
26 | slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py)
27 |
28 | # Additional build-time testing
29 | add_subdirectory(Testing)
30 | endif()
31 |
--------------------------------------------------------------------------------
/SegmentMesher/Resources/Icons/SegmentMesher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/37a56a4216048df01af0c6392d90751f47707cea/SegmentMesher/Resources/Icons/SegmentMesher.png
--------------------------------------------------------------------------------
/SegmentMesher/Resources/UI/SegmentMesher.ui:
--------------------------------------------------------------------------------
1 |
2 |
See module documentation for description of meshing parameters. 26 |
Cleaver2 is freely usable, without any restrictions. 27 |
TetGen is only free for private, research, and educational use (see license for details). 28 | """ 29 | #self.parent.helpText += self.getDefaultModuleDocumentationLink() 30 | self.parent.acknowledgementText = """ 31 | This module was originally developed by Andras Lasso (Queen's University, PerkLab) to serve as a convenient frontend for existing commonly used open-source generator software. 32 | 33 |
Cleaver is an Open Source software project that is principally funded through the SCI Institute's NIH/NIGMS CIBC Center. Please use the following acknowledgment and send references to any publications, presentations, or successful funding applications that make use of NIH/NIGMS CIBC software or data sets to SCI: "This project was supported by the National Institute of General Medical Sciences of the National Institutes of Health under grant number P41 GM103545-18." 34 | 35 |
TetGen citation: Si, Hang (2015). "TetGen, a Delaunay-based Tetrahedral Mesh Generator". ACM Transactions on Mathematical Software. 41 (2): 11:1-11:36. doi:10.1145/2629697 36 | """ 37 | 38 | # 39 | # SegmentMesherWidget 40 | # 41 | 42 | class SegmentMesherWidget(ScriptedLoadableModuleWidget, VTKObservationMixin): 43 | """Uses ScriptedLoadableModuleWidget base class, available at: 44 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 45 | """ 46 | 47 | def __init__(self, parent=None): 48 | """ 49 | Called when the user opens the module the first time and the widget is initialized. 50 | """ 51 | ScriptedLoadableModuleWidget.__init__(self, parent) 52 | VTKObservationMixin.__init__(self) # needed for parameter node observation 53 | self.logic = None 54 | self._parameterNode = None 55 | self._updatingGUIFromParameterNode = False 56 | 57 | def setup(self): 58 | ScriptedLoadableModuleWidget.setup(self) 59 | 60 | self.logic = SegmentMesherLogic() 61 | self.logic.logCallback = self.addLog 62 | self.modelGenerationInProgress = False 63 | 64 | uiWidget = slicer.util.loadUI(self.resourcePath('UI/SegmentMesher.ui')) 65 | self.layout.addWidget(uiWidget) 66 | self.ui = slicer.util.childWidgetVariables(uiWidget) 67 | uiWidget.setPalette(slicer.util.mainWindow().style().standardPalette()) 68 | 69 | # Finish UI setup ... 70 | self.ui.parameterNodeSelector.addAttribute( "vtkMRMLScriptedModuleNode", "ModuleName", "SegmentMesher" ) 71 | self.ui.parameterNodeSelector.setMRMLScene( slicer.mrmlScene ) 72 | self.ui.inputSegmentationSelector.setMRMLScene( slicer.mrmlScene ) 73 | self.ui.inputModelSelector.setMRMLScene( slicer.mrmlScene ) 74 | self.ui.outputModelSelector.setMRMLScene( slicer.mrmlScene ) 75 | 76 | self.ui.methodSelectorComboBox.addItem("Cleaver", METHOD_CLEAVER) 77 | self.ui.methodSelectorComboBox.addItem("TetGen", METHOD_TETGEN) 78 | 79 | customCleaverPath = self.logic.getCustomCleaverPath() 80 | self.ui.customCleaverPathSelector.setCurrentPath(customCleaverPath) 81 | self.ui.customCleaverPathSelector.nameFilters = [self.logic.cleaverFilename] 82 | 83 | customTetGenPath = self.logic.getCustomTetGenPath() 84 | self.ui.customTetGenPathSelector.setCurrentPath(customTetGenPath) 85 | self.ui.customTetGenPathSelector.nameFilters = [self.logic.tetGenFilename] 86 | 87 | clipNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLClipModelsNode") 88 | self.ui.clipNodeWidget.setMRMLClipNode(clipNode) 89 | 90 | # These connections ensure that we update parameter node when scene is closed 91 | self.addObserver(slicer.mrmlScene, slicer.mrmlScene.StartCloseEvent, self.onSceneStartClose) 92 | self.addObserver(slicer.mrmlScene, slicer.mrmlScene.EndCloseEvent, self.onSceneEndClose) 93 | 94 | # connections 95 | self.ui.selectAllSegmentsButton.connect('clicked(bool)', self.onSelectAllSegmentsButton) 96 | self.ui.applyButton.connect('clicked(bool)', self.onApplyButton) 97 | self.ui.showTemporaryFilesFolderButton.connect('clicked(bool)', self.onShowTemporaryFilesFolder) 98 | self.ui.inputSegmentationSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateMRMLFromGUI) 99 | self.ui.inputModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateMRMLFromGUI) 100 | self.ui.outputModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateMRMLFromGUI) 101 | self.ui.methodSelectorComboBox.connect("currentIndexChanged(int)", self.updateMRMLFromGUI) 102 | # Immediately update deleteTemporaryFiles in the logic to make it possible to decide to 103 | # keep the temporary file while the model generation is running 104 | self.ui.keepTemporaryFilesCheckBox.connect("toggled(bool)", self.onKeepTemporaryFilesToggled) 105 | self.ui.tetgenUseSurface.connect("toggled(bool)", self.updateMRMLFromGUI) 106 | 107 | #Parameter node connections 108 | self.ui.inputSegmentationSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI) 109 | self.ui.inputModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI) 110 | self.ui.outputModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.updateParameterNodeFromGUI) 111 | self.ui.methodSelectorComboBox.connect("currentIndexChanged(int)", self.updateParameterNodeFromGUI) 112 | 113 | 114 | self.ui.showDetailedLogDuringExecutionCheckBox.connect("toggled(bool)", self.updateParameterNodeFromGUI) 115 | self.ui.keepTemporaryFilesCheckBox.connect("toggled(bool)", self.updateParameterNodeFromGUI) 116 | 117 | self.ui.cleaverFeatureScalingParameterWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI) 118 | self.ui.cleaverSamplingParameterWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI) 119 | self.ui.cleaverRateParameterWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI) 120 | self.ui.cleaverAdditionalParametersWidget.connect("textChanged(const QString&)", self.updateParameterNodeFromGUI) 121 | self.ui.cleaverRemoveBackgroundMeshCheckBox.connect("toggled(bool)", self.updateParameterNodeFromGUI) 122 | self.ui.cleaverPaddingPercentSpinBox.connect("valueChanged(int)", self.updateParameterNodeFromGUI) 123 | self.ui.customCleaverPathSelector.connect("currentPathChanged(const QString&)", self.updateParameterNodeFromGUI) 124 | 125 | self.ui.tetgenUseSurface.connect("toggled(bool)", self.updateParameterNodeFromGUI) 126 | self.ui.tetgenRatioParameterWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI) 127 | self.ui.tetgenAngleParameterWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI) 128 | self.ui.tetgenVolumeParameterWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI) 129 | self.ui.tetGenAdditionalParametersWidget.connect("textChanged(const QString&)", self.updateParameterNodeFromGUI) 130 | self.ui.customTetGenPathSelector.connect("currentPathChanged(const QString&)", self.updateParameterNodeFromGUI) 131 | 132 | # Add vertical spacer 133 | self.layout.addStretch(1) 134 | 135 | # Make sure parameter node is initialized (needed for module reload) 136 | self.initializeParameterNode() 137 | self.ui.parameterNodeSelector.setCurrentNode(self._parameterNode) 138 | self.ui.parameterNodeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.setParameterNode) 139 | 140 | # Refresh Apply button state 141 | self.updateMRMLFromGUI() 142 | 143 | def enter(self): 144 | """ 145 | Called each time the user opens this module. 146 | """ 147 | # Make sure parameter node exists and observed 148 | self.initializeParameterNode() 149 | self.updateMRMLFromGUI() 150 | 151 | def cleanup(self): 152 | """ 153 | Called when the application closes and the module widget is destroyed. 154 | """ 155 | self.removeObservers() 156 | 157 | def exit(self): 158 | """ 159 | Called each time the user opens a different module. 160 | """ 161 | # Do not react to parameter node changes (GUI wlil be updated when the user enters into the module) 162 | self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) 163 | 164 | def onSceneStartClose(self, caller, event): 165 | """ 166 | Called just before the scene is closed. 167 | """ 168 | # Parameter node will be reset, do not use it anymore 169 | self.setParameterNode(None) 170 | 171 | def onSceneEndClose(self, caller, event): 172 | """ 173 | Called just after the scene is closed. 174 | """ 175 | # If this module is shown while the scene is closed then recreate a new parameter node immediately 176 | if self.parent.isEntered: 177 | self.initializeParameterNode() 178 | 179 | 180 | def initializeParameterNode(self): 181 | """ 182 | Ensure parameter node exists and observed. 183 | """ 184 | # Parameter node stores all user choices in parameter values, node selections, etc. 185 | # so that when the scene is saved and reloaded, these settings are restored. 186 | 187 | self.setParameterNode(self.logic.getParameterNode()) 188 | 189 | # Select default input nodes if nothing is selected yet to save a few clicks for the user 190 | if not self._parameterNode.GetNodeReference("InputSegmentation"): 191 | firstVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode") 192 | if firstVolumeNode: 193 | self._parameterNode.SetNodeReferenceID("InputSegmentation", firstVolumeNode.GetID()) 194 | 195 | # Select default input nodes if nothing is selected yet to save a few clicks for the user 196 | if not self._parameterNode.GetNodeReference("InputSurface"): 197 | firstVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLModelNode") 198 | if firstVolumeNode: 199 | self._parameterNode.SetNodeReferenceID("InputSurface", firstVolumeNode.GetID()) 200 | 201 | def setParameterNode(self, inputParameterNode): 202 | """ 203 | Set and observe parameter node. 204 | Observation is needed because when the parameter node is changed then the GUI must be updated immediately. 205 | """ 206 | 207 | if inputParameterNode: 208 | self.logic.setDefaultParameters(inputParameterNode) 209 | 210 | # Unobserve previously selected parameter node and add an observer to the newly selected. 211 | # Changes of parameter node are observed so that whenever parameters are changed by a script or any other module 212 | # those are reflected immediately in the GUI. 213 | if self._parameterNode is not None: 214 | self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) 215 | self._parameterNode = inputParameterNode 216 | if self._parameterNode is not None: 217 | self.addObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) 218 | 219 | # Initial GUI update 220 | self.updateGUIFromParameterNode() 221 | 222 | def updateGUIFromParameterNode(self, caller=None, event=None): 223 | """ 224 | This method is called whenever parameter node is changed. 225 | The module GUI is updated to show the current state of the parameter node. 226 | """ 227 | 228 | if self._parameterNode is None or self._updatingGUIFromParameterNode: 229 | return 230 | 231 | # Make sure GUI changes do not call updateParameterNodeFromGUI (it could cause infinite loop) 232 | self._updatingGUIFromParameterNode = True 233 | 234 | # Update node selectors and sliders 235 | self.ui.inputSegmentationSelector.setCurrentNode(self._parameterNode.GetNodeReference("InputSegmentation")) 236 | self.ui.inputModelSelector.setCurrentNode(self._parameterNode.GetNodeReference("InputSurface")) 237 | self.ui.outputModelSelector.setCurrentNode(self._parameterNode.GetNodeReference("OutputModel")) 238 | self.ui.methodSelectorComboBox.setCurrentText(self._parameterNode.GetParameter("Method")) 239 | 240 | self.ui.showDetailedLogDuringExecutionCheckBox.checked = (self._parameterNode.GetParameter("showDetailedLogDuringExecution") == "true") 241 | self.ui.keepTemporaryFilesCheckBox.checked = (self._parameterNode.GetParameter("keepTemporaryFiles") == "true") 242 | 243 | self.ui.cleaverFeatureScalingParameterWidget.value = float(self._parameterNode.GetParameter("cleaverFeatureScalingParameter")) 244 | self.ui.cleaverSamplingParameterWidget.value = float(self._parameterNode.GetParameter("cleaverSamplingParameter")) 245 | self.ui.cleaverRateParameterWidget.value = float(self._parameterNode.GetParameter("cleaverRateParameter")) 246 | self.ui.cleaverAdditionalParametersWidget.text = self._parameterNode.GetParameter("cleaverAdditionalParameters") 247 | self.ui.cleaverRemoveBackgroundMeshCheckBox.checked = (self._parameterNode.GetParameter("cleaverRemoveBackgroundMesh") == "true") 248 | self.ui.cleaverPaddingPercentSpinBox.value = int(self._parameterNode.GetParameter("cleaverPaddingPercent")) 249 | self.ui.customCleaverPathSelector.setCurrentPath(self._parameterNode.GetParameter("customCleaverPath")) 250 | 251 | self.ui.tetgenUseSurface.checked = (self._parameterNode.GetParameter("tetgenUseSurface") == "true") 252 | self.ui.tetgenRatioParameterWidget.value = float(self._parameterNode.GetParameter("tetgenRatioParameter")) 253 | self.ui.tetgenAngleParameterWidget.value = float(self._parameterNode.GetParameter("tetgenAngleParameter")) 254 | self.ui.tetgenVolumeParameterWidget.value = float(self._parameterNode.GetParameter("tetgenVolumeParameter")) 255 | self.ui.tetGenAdditionalParametersWidget.text = self._parameterNode.GetParameter("tetGenAdditionalParameters") 256 | self.ui.customTetGenPathSelector.setCurrentPath(self._parameterNode.GetParameter("customTetGenPath")) 257 | 258 | 259 | # Update buttons states and tooltips 260 | self.updateMRMLFromGUI() 261 | 262 | # All the GUI updates are done 263 | self._updatingGUIFromParameterNode = False 264 | 265 | def updateParameterNodeFromGUI(self, caller=None, event=None): 266 | """ 267 | This method is called when the user makes any change in the GUI. 268 | The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded). 269 | """ 270 | 271 | if self._parameterNode is None or self._updatingGUIFromParameterNode: 272 | return 273 | 274 | wasModified = self._parameterNode.StartModify() # Modify all properties in a single batch 275 | 276 | #Inputs/Outputs 277 | self._parameterNode.SetNodeReferenceID("InputSegmentation", self.ui.inputSegmentationSelector.currentNodeID) 278 | self._parameterNode.SetNodeReferenceID("InputSurface", self.ui.inputModelSelector.currentNodeID) 279 | self._parameterNode.SetNodeReferenceID("OutputModel", self.ui.outputModelSelector.currentNodeID) 280 | self._parameterNode.SetParameter("Method", self.ui.methodSelectorComboBox.currentText) 281 | 282 | #General parameters 283 | self._parameterNode.SetParameter("showDetailedLogDuringExecution", "true" if self.ui.showDetailedLogDuringExecutionCheckBox.checked else "false") 284 | self._parameterNode.SetParameter("keepTemporaryFiles", "true" if self.ui.keepTemporaryFilesCheckBox.checked else "false") 285 | 286 | #Cleaver parameters 287 | self._parameterNode.SetParameter("cleaverFeatureScalingParameter", str(self.ui.cleaverFeatureScalingParameterWidget.value)) 288 | self._parameterNode.SetParameter("cleaverSamplingParameter", str(self.ui.cleaverSamplingParameterWidget.value)) 289 | self._parameterNode.SetParameter("cleaverRateParameter", str(self.ui.cleaverRateParameterWidget.value)) 290 | self._parameterNode.SetParameter("cleaverAdditionalParameters", self.ui.cleaverAdditionalParametersWidget.text) 291 | self._parameterNode.SetParameter("cleaverRemoveBackgroundMesh", "true" if self.ui.cleaverRemoveBackgroundMeshCheckBox.checked else "false") 292 | self._parameterNode.SetParameter("cleaverPaddingPercent", str(self.ui.cleaverPaddingPercentSpinBox.value)) 293 | self._parameterNode.SetParameter("customCleaverPath", self.ui.customCleaverPathSelector.currentPath) 294 | 295 | #TetGen parameters 296 | self._parameterNode.SetParameter("tetgenUseSurface", "true" if self.ui.tetgenUseSurface.checked else "false") 297 | self._parameterNode.SetParameter("tetgenRatioParameter", str(self.ui.tetgenRatioParameterWidget.value)) 298 | self._parameterNode.SetParameter("tetgenAngleParameter", str(self.ui.tetgenAngleParameterWidget.value)) 299 | self._parameterNode.SetParameter("tetgenVolumeParameter", str(self.ui.tetgenVolumeParameterWidget.value)) 300 | self._parameterNode.SetParameter("tetGenAdditionalParameters", self.ui.tetGenAdditionalParametersWidget.text) 301 | self._parameterNode.SetParameter("customTetGenPath", self.ui.customTetGenPathSelector.currentPath) 302 | 303 | self._parameterNode.EndModify(wasModified) 304 | 305 | def updateMRMLFromGUI(self): 306 | 307 | method = self.ui.methodSelectorComboBox.itemData(self.ui.methodSelectorComboBox.currentIndex) 308 | 309 | #Enable correct input selections 310 | inputIsModel = (self.ui.tetgenUseSurface.isChecked() and method == METHOD_TETGEN) 311 | self.ui.inputSegmentationLabel.visible = not inputIsModel 312 | self.ui.inputSegmentationSelector.visible = not inputIsModel 313 | self.ui.segmentSelectorLabel.visible = not inputIsModel 314 | self.ui.segmentSelectorCombBox.visible = not inputIsModel 315 | self.ui.inputModelLabel.visible = inputIsModel 316 | self.ui.inputModelSelector.visible = inputIsModel 317 | segmentationSelected = self.ui.inputSegmentationSelector.currentNode() is not None 318 | self.ui.segmentSelectorCombBox.enabled = segmentationSelected 319 | self.ui.selectAllSegmentsButton.enabled = segmentationSelected 320 | 321 | #populate segments 322 | inputSeg = self.ui.inputSegmentationSelector.currentNode() 323 | oldIndex = self.ui.segmentSelectorCombBox.checkedIndexes() 324 | oldCount = self.ui.segmentSelectorCombBox.count 325 | self.ui.segmentSelectorCombBox.clear() 326 | if inputSeg is not None: 327 | segmentIDs = vtk.vtkStringArray() 328 | inputSeg.GetSegmentation().GetSegmentIDs(segmentIDs) 329 | for index in range(0, segmentIDs.GetNumberOfValues()): 330 | segmentId = segmentIDs.GetValue(index) 331 | self.ui.segmentSelectorCombBox.addItem(inputSeg.GetSegmentation().GetSegment(segmentId).GetName(), segmentId) 332 | 333 | #Restore index - often we will be reloading the data from the same segmentation, so re-select items number of items is the same 334 | if oldCount == self.ui.segmentSelectorCombBox.count: 335 | for index in oldIndex: 336 | self.ui.segmentSelectorCombBox.setCheckState(index, qt.Qt.Checked) 337 | 338 | self.ui.CleaverParametersGroupBox.visible = (method == METHOD_CLEAVER) 339 | self.ui.TetGenParametersGroupBox.visible = (method == METHOD_TETGEN) 340 | 341 | if method == METHOD_TETGEN and self.ui.tetgenUseSurface.isChecked(): 342 | if not self.ui.inputModelSelector.currentNode(): 343 | self.ui.applyButton.text = "Select input surface" 344 | self.ui.applyButton.enabled = False 345 | elif not self.ui.outputModelSelector.currentNode(): 346 | self.ui.applyButton.text = "Select an output model node" 347 | self.ui.applyButton.enabled = False 348 | elif self.ui.inputModelSelector.currentNode() == self.ui.outputModelSelector.currentNode(): 349 | self.ui.applyButton.text = "Choose different Output model" 350 | self.ui.applyButton.enabled = False 351 | else: 352 | self.ui.applyButton.text = "Apply" 353 | self.ui.applyButton.enabled = True 354 | else: 355 | if not self.ui.inputSegmentationSelector.currentNode(): 356 | self.ui.applyButton.text = "Select input segmentation" 357 | self.ui.applyButton.enabled = False 358 | elif not self.ui.outputModelSelector.currentNode(): 359 | self.ui.applyButton.text = "Select an output model node" 360 | self.ui.applyButton.enabled = False 361 | elif self.ui.inputSegmentationSelector.currentNode() == self.ui.outputModelSelector.currentNode(): 362 | self.ui.applyButton.text = "Choose different Output model" 363 | self.ui.applyButton.enabled = False 364 | else: 365 | self.ui.applyButton.text = "Apply" 366 | self.ui.applyButton.enabled = True 367 | 368 | self.updateParameterNodeFromGUI() 369 | 370 | 371 | # def updateGUIFromMRML(self): 372 | # parameterNode = self.parameterNodeSelector.currentNode() 373 | # method = parameterNode.parameter("Method") 374 | # methodIndex = self.methodSelectorComboBox.findData(method) 375 | # wasBlocked = self.methodSelectorComboBox.blockSignals(True) 376 | # self.methodSelectorComboBox.setCurrentIndex(methodIndex) 377 | # self.methodSelectorComboBox.blockSignals(wasBlocked) 378 | 379 | def onShowTemporaryFilesFolder(self): 380 | qt.QDesktopServices().openUrl(qt.QUrl("file:///" + self.logic.getTempDirectoryBase(), qt.QUrl.TolerantMode)); 381 | 382 | def onKeepTemporaryFilesToggled(self, toggle): 383 | self.logic.deleteTemporaryFiles = toggle 384 | 385 | def onApplyButton(self): 386 | if self.modelGenerationInProgress: 387 | self.modelGenerationInProgress = False 388 | self.logic.abortRequested = True 389 | self.ui.applyButton.text = "Cancelling..." 390 | self.ui.applyButton.enabled = False 391 | return 392 | 393 | self.modelGenerationInProgress = True 394 | self.ui.applyButton.text = "Cancel" 395 | self.ui.statusLabel.plainText = '' 396 | slicer.app.setOverrideCursor(qt.Qt.WaitCursor) 397 | try: 398 | self.logic.setCustomCleaverPath(self.ui.customCleaverPathSelector.currentPath) 399 | self.logic.setCustomTetGenPath(self.ui.customTetGenPathSelector.currentPath) 400 | 401 | self.logic.deleteTemporaryFiles = not self.ui.keepTemporaryFilesCheckBox.checked 402 | self.logic.logStandardOutput = self.ui.showDetailedLogDuringExecutionCheckBox.checked 403 | 404 | method = self.ui.methodSelectorComboBox.itemData(self.ui.methodSelectorComboBox.currentIndex) 405 | 406 | #Get list of segments to mesh 407 | segmentIndexes = self.ui.segmentSelectorCombBox.checkedIndexes() 408 | segments = [] 409 | 410 | for index in segmentIndexes: 411 | segments.append(self.ui.segmentSelectorCombBox.itemData(index.row())) 412 | 413 | print(method) 414 | if method == METHOD_CLEAVER: 415 | self.logic.createMeshFromSegmentationCleaver(self.ui.inputSegmentationSelector.currentNode(), 416 | self.ui.outputModelSelector.currentNode(), segments, self.ui.cleaverAdditionalParametersWidget.text, 417 | self.ui.cleaverRemoveBackgroundMeshCheckBox.isChecked(), 418 | self.ui.cleaverPaddingPercentSpinBox.value * 0.01, self.ui.cleaverFeatureScalingParameterWidget.value, self.ui.cleaverSamplingParameterWidget.value, self.ui.cleaverRateParameterWidget.value) 419 | else: 420 | if self.ui.tetgenUseSurface.isChecked(): 421 | if self.ui.inputModelSelector.currentNode().GetUnstructuredGrid() is not None: 422 | self.addLog("Error: Mesh must be a surface, not volumetric") 423 | return 424 | self.logic.createMeshFromPolyDataTetGen(self.ui.inputModelSelector.currentNode().GetPolyData(), 425 | self.ui.outputModelSelector.currentNode(), self.ui.tetGenAdditionalParametersWidget.text, 426 | self.ui.tetgenRatioParameterWidget.value, self.ui.tetgenAngleParameterWidget.value, self.ui.tetgenVolumeParameterWidget.value) 427 | else: 428 | self.logic.createMeshFromSegmentationTetGen(self.ui.inputSegmentationSelector.currentNode(), 429 | self.ui.outputModelSelector.currentNode(), segments, self.ui.tetGenAdditionalParametersWidget.text, 430 | self.ui.tetgenRatioParameterWidget.value, self.ui.tetgenAngleParameterWidget.value, self.ui.tetgenVolumeParameterWidget.value) 431 | 432 | except Exception as e: 433 | print(e) 434 | self.addLog("Error: {0}".format(str(e))) 435 | import traceback 436 | traceback.print_exc() 437 | finally: 438 | slicer.app.restoreOverrideCursor() 439 | self.modelGenerationInProgress = False 440 | self.updateMRMLFromGUI() # restores default Apply button state 441 | 442 | def onSelectAllSegmentsButton(self): 443 | newState = qt.Qt.Unchecked if self.ui.segmentSelectorCombBox.allChecked() else qt.Qt.Checked 444 | model = self.ui.segmentSelectorCombBox.model() 445 | for i in range(self.ui.segmentSelectorCombBox.count): 446 | self.ui.segmentSelectorCombBox.setCheckState(model.index(i, 0), newState) 447 | 448 | def addLog(self, text): 449 | """Append text to log window 450 | """ 451 | self.ui.statusLabel.appendPlainText(text) 452 | slicer.app.processEvents() # force update 453 | 454 | # 455 | # SegmentMesherLogic 456 | # 457 | 458 | class SegmentMesherLogic(ScriptedLoadableModuleLogic): 459 | """This class should implement all the actual 460 | computation done by your module. The interface 461 | should be such that other python code can import 462 | this class and make use of the functionality without 463 | requiring an instance of the Widget. 464 | Uses ScriptedLoadableModuleLogic base class, available at: 465 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 466 | """ 467 | 468 | def __init__(self): 469 | ScriptedLoadableModuleLogic.__init__(self) 470 | self.logCallback = None 471 | self.abortRequested = False 472 | self.deleteTemporaryFiles = True 473 | self.logStandardOutput = False 474 | self.customCleaverPathSettingsKey = 'SegmentMesher/CustomCleaverPath' 475 | self.customTetGenPathSettingsKey = 'SegmentMesher/CustomTetGenPath' 476 | import os 477 | self.scriptPath = os.path.dirname(os.path.abspath(__file__)) 478 | self.cleaverPath = None # this will be determined dynamically 479 | self.tetGenPath = None # this will be determined dynamically 480 | 481 | import platform 482 | executableExt = '.exe' if platform.system() == 'Windows' else '' 483 | self.cleaverFilename = 'cleaver-cli' + executableExt 484 | self.tetGenFilename = 'tetgen' + executableExt 485 | 486 | self.binDirCandidates = [ 487 | # install tree 488 | os.path.join(self.scriptPath, '..'), 489 | os.path.join(self.scriptPath, '../../../bin'), 490 | # build tree 491 | os.path.join(self.scriptPath, '../../../../bin'), 492 | os.path.join(self.scriptPath, '../../../../bin/Release'), 493 | os.path.join(self.scriptPath, '../../../../bin/Debug'), 494 | os.path.join(self.scriptPath, '../../../../bin/RelWithDebInfo'), 495 | os.path.join(self.scriptPath, '../../../../bin/MinSizeRel') ] 496 | 497 | def setDefaultParameters(self, parameterNode): 498 | """ 499 | Initialize parameter node with default settings. 500 | """ 501 | self.setParameterIfNotDefined(parameterNode, "showDetailedLogDuringExecution", "false") 502 | self.setParameterIfNotDefined(parameterNode, "keepTemporaryFiles", "false") 503 | 504 | self.setParameterIfNotDefined(parameterNode, "cleaverFeatureScalingParameter", "2.0") 505 | self.setParameterIfNotDefined(parameterNode, "cleaverSamplingParameter", "0.2") 506 | self.setParameterIfNotDefined(parameterNode, "cleaverRateParameter", "0.2") 507 | self.setParameterIfNotDefined(parameterNode, "cleaverAdditionalParameters", "") 508 | self.setParameterIfNotDefined(parameterNode, "cleaverRemoveBackgroundMesh", "true") 509 | self.setParameterIfNotDefined(parameterNode, "cleaverPaddingPercent", "10") 510 | self.setParameterIfNotDefined(parameterNode, "customCleaverPath", "") 511 | 512 | self.setParameterIfNotDefined(parameterNode, "tetgenUseSurface", "false") 513 | self.setParameterIfNotDefined(parameterNode, "tetgenRatioParameter", "5") 514 | self.setParameterIfNotDefined(parameterNode, "tetgenAngleParameter", "5") 515 | self.setParameterIfNotDefined(parameterNode, "tetgenVolumeParameter", "5") 516 | self.setParameterIfNotDefined(parameterNode, "tetGenAdditionalParameters", "") 517 | self.setParameterIfNotDefined(parameterNode, "customTetGenPath", "") 518 | 519 | 520 | def setParameterIfNotDefined(self, parameterNode, key, value): 521 | if not parameterNode.GetParameter(key): 522 | parameterNode.SetParameter(key, value) 523 | 524 | def addLog(self, text): 525 | logging.info(text) 526 | if self.logCallback: 527 | self.logCallback(text) 528 | 529 | def getCleaverPath(self): 530 | if self.cleaverPath: 531 | return self.cleaverPath 532 | 533 | self.cleaverPath = self.getCustomCleaverPath() 534 | if self.cleaverPath: 535 | return self.cleaverPath 536 | 537 | for binDirCandidate in self.binDirCandidates: 538 | cleaverPath = os.path.abspath(os.path.join(binDirCandidate, self.cleaverFilename)) 539 | logging.debug("Attempt to find executable at: "+cleaverPath) 540 | if os.path.isfile(cleaverPath): 541 | # found 542 | self.cleaverPath = cleaverPath 543 | return self.cleaverPath 544 | 545 | raise ValueError('Cleaver not found') 546 | 547 | def getTetGenPath(self): 548 | if self.tetGenPath: 549 | return self.tetGenPath 550 | 551 | self.tetGenPath = self.getCustomTetGenPath() 552 | if self.tetGenPath: 553 | return self.tetGenPath 554 | 555 | for tetGenBinDirCandidate in self.binDirCandidates: 556 | tetGenPath = os.path.abspath(os.path.join(tetGenBinDirCandidate, self.tetGenFilename)) 557 | logging.debug("Attempt to find executable at: "+tetGenPath) 558 | if os.path.isfile(tetGenPath): 559 | # TetGen found 560 | self.tetGenPath = tetGenPath 561 | return self.tetGenPath 562 | 563 | raise ValueError('TetGen not found') 564 | 565 | def getCustomCleaverPath(self): 566 | settings = qt.QSettings() 567 | if settings.contains(self.customCleaverPathSettingsKey): 568 | return settings.value(self.customCleaverPathSettingsKey) 569 | return '' 570 | 571 | def getCustomTetGenPath(self): 572 | settings = qt.QSettings() 573 | if settings.contains(self.customTetGenPathSettingsKey): 574 | return settings.value(self.customTetGenPathSettingsKey) 575 | return '' 576 | 577 | def setCustomCleaverPath(self, customPath): 578 | # don't save it if already saved 579 | settings = qt.QSettings() 580 | if settings.contains(self.customCleaverPathSettingsKey): 581 | if customPath == settings.value(self.customCleaverPathSettingsKey): 582 | return 583 | settings.setValue(self.customCleaverPathSettingsKey, customPath) 584 | # Update Cleaver bin dir 585 | self.cleaverPath = None 586 | self.getCleaverPath() 587 | 588 | def setCustomTetGenPath(self, customPath): 589 | # don't save it if already saved 590 | settings = qt.QSettings() 591 | if settings.contains(self.customTetGenPathSettingsKey): 592 | if customPath == settings.value(self.customTetGenPathSettingsKey): 593 | return 594 | settings.setValue(self.customTetGenPathSettingsKey, customPath) 595 | # Update TetGen bin dir 596 | self.tetGenPath = None 597 | self.getTetGenPath() 598 | 599 | def startMesher(self, cmdLineArguments, executableFilePath): 600 | self.addLog("Generating volumetric mesh...") 601 | import subprocess 602 | 603 | # Hide console window on Windows 604 | from sys import platform 605 | if platform == "win32": 606 | info = subprocess.STARTUPINFO() 607 | info.dwFlags = 1 608 | info.wShowWindow = 0 609 | else: 610 | info = None 611 | 612 | logging.info("Generate mesh using: "+executableFilePath+": "+repr(cmdLineArguments)) 613 | return subprocess.Popen([executableFilePath] + cmdLineArguments, 614 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, startupinfo=info) 615 | 616 | def logProcessOutput(self, process, processName): 617 | # save process output (if not logged) so that it can be displayed in case of an error 618 | processOutput = '' 619 | import subprocess 620 | for stdout_line in iter(process.stdout.readline, ""): 621 | if self.logStandardOutput: 622 | self.addLog(stdout_line.rstrip()) 623 | else: 624 | processOutput += stdout_line.rstrip() + '\n' 625 | slicer.app.processEvents() # give a chance to click Cancel button 626 | if self.abortRequested: 627 | process.kill() 628 | process.stdout.close() 629 | return_code = process.wait() 630 | if return_code: 631 | if self.abortRequested: 632 | raise ValueError("User requested cancel.") 633 | else: 634 | if processOutput: 635 | self.addLog(processOutput) 636 | raise subprocess.CalledProcessError(return_code, processName) 637 | 638 | def getTempDirectoryBase(self): 639 | tempDir = qt.QDir(slicer.app.temporaryPath) 640 | fileInfo = qt.QFileInfo(qt.QDir(tempDir), "SegmentMesher") 641 | dirPath = fileInfo.absoluteFilePath() 642 | qt.QDir().mkpath(dirPath) 643 | return dirPath 644 | 645 | def createTempDirectory(self): 646 | import qt, slicer 647 | tempDir = qt.QDir(self.getTempDirectoryBase()) 648 | tempDirName = qt.QDateTime().currentDateTime().toString("yyyyMMdd_hhmmss_zzz") 649 | fileInfo = qt.QFileInfo(qt.QDir(tempDir), tempDirName) 650 | dirPath = fileInfo.absoluteFilePath() 651 | qt.QDir().mkpath(dirPath) 652 | return dirPath 653 | 654 | def createMeshFromSegmentationCleaver(self, inputSegmentation, outputMeshNode, segments = [], additionalParameters = None, removeBackgroundMesh = False, 655 | paddingRatio = 0.10, featureScale = 2, samplingRate=0.2, rateOfChange=0.2): 656 | 657 | if additionalParameters is None: 658 | additionalParameters="" 659 | 660 | 661 | self.abortRequested = False 662 | tempDir = self.createTempDirectory() 663 | self.addLog('Mesh generation using Cleaver is started in working directory: '+tempDir) 664 | 665 | inputParamsCleaver = [] 666 | 667 | # Write inputs 668 | qt.QDir().mkpath(tempDir) 669 | 670 | # Create temporary labelmap node. It will be used both for storing reference geometry 671 | # and resulting merged labelmap. 672 | labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') 673 | parentTransformNode = inputSegmentation.GetParentTransformNode() 674 | labelmapVolumeNode.SetAndObserveTransformNodeID(parentTransformNode.GetID() if parentTransformNode else None) 675 | 676 | # Create binary labelmap representation using default parameters 677 | if not inputSegmentation.GetSegmentation().CreateRepresentation(slicer.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName()): 678 | self.addLog('Failed to create binary labelmap representation') 679 | return 680 | 681 | # Set reference geometry in labelmapVolumeNode 682 | referenceGeometry_Segmentation = slicer.vtkOrientedImageData() 683 | inputSegmentation.GetSegmentation().SetImageGeometryFromCommonLabelmapGeometry(referenceGeometry_Segmentation, None, 684 | slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY) 685 | slicer.modules.segmentations.logic().CopyOrientedImageDataToVolumeNode(referenceGeometry_Segmentation, labelmapVolumeNode) 686 | 687 | # Add margin 688 | extent = labelmapVolumeNode.GetImageData().GetExtent() 689 | paddedExtent = [0, -1, 0, -1, 0, -1] 690 | for axisIndex in range(3): 691 | paddingSizeVoxels = int((extent[axisIndex * 2 + 1] - extent[axisIndex * 2]) * paddingRatio) 692 | paddedExtent[axisIndex * 2] = extent[axisIndex * 2] - paddingSizeVoxels 693 | paddedExtent[axisIndex * 2 + 1] = extent[axisIndex * 2 + 1] + paddingSizeVoxels 694 | labelmapVolumeNode.GetImageData().SetExtent(paddedExtent) 695 | labelmapVolumeNode.ShiftImageDataExtentToZeroStart() 696 | 697 | # Get merged labelmap 698 | segmentIdList = vtk.vtkStringArray() 699 | 700 | for segment in segments: 701 | segmentIdList.InsertNextValue(segment) 702 | 703 | if segmentIdList.GetNumberOfValues() == 0: 704 | self.addLog("No input segments are selected, therefore no output is generated.") 705 | return 706 | 707 | slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(inputSegmentation, segmentIdList, labelmapVolumeNode, labelmapVolumeNode) 708 | 709 | 710 | inputLabelmapVolumeFilePath = os.path.join(tempDir, "inputLabelmap.nrrd") 711 | slicer.util.saveNode(labelmapVolumeNode, inputLabelmapVolumeFilePath, {"useCompression": False}) 712 | inputParamsCleaver.extend(["--input_files", inputLabelmapVolumeFilePath]) 713 | 714 | # Keep IJK to RAS matrix, we'll need it later 715 | unscaledIjkToRasMatrix = vtk.vtkMatrix4x4() 716 | labelmapVolumeNode.GetIJKToRASDirectionMatrix(unscaledIjkToRasMatrix) # axis directions, without scaling by spacing 717 | ijkToRasMatrix = vtk.vtkMatrix4x4() 718 | labelmapVolumeNode.GetIJKToRASMatrix(ijkToRasMatrix) 719 | origin = ijkToRasMatrix.MultiplyPoint([-0.5, -0.5, -0.5, 1.0]) # Cleaver uses the voxel corner as its origin, therefore we need a half-voxel offset 720 | for i in range(3): 721 | unscaledIjkToRasMatrix.SetElement(i,3, origin[i]) 722 | 723 | # Keep color node, we'll need it later 724 | colorTableNode = labelmapVolumeNode.GetDisplayNode().GetColorNode() 725 | # Background color is transparent by default which is not ideal for 3D display 726 | colorTableNode.SetColor(0,0.6,0.6,0.6,1.0) 727 | 728 | slicer.mrmlScene.RemoveNode(labelmapVolumeNode) 729 | slicer.mrmlScene.RemoveNode(colorTableNode) 730 | 731 | #User set parameters 732 | inputParamsCleaver.extend(["--feature_scaling", "{:.2f}".format(featureScale)]) 733 | inputParamsCleaver.extend(["--sampling_rate", "{:.2f}".format(samplingRate)]) 734 | inputParamsCleaver.extend(["--lipschitz", "{:.2f}".format(rateOfChange)]) 735 | 736 | # Set up output format 737 | 738 | inputParamsCleaver.extend(["--output_path", tempDir+"/"]) 739 | inputParamsCleaver.extend(["--output_format", "vtkUSG"]) # VTK unstructed grid 740 | inputParamsCleaver.append("--fix_tet_windup") # prevent inside-out tets 741 | inputParamsCleaver.append("--strip_exterior") # remove temporary elements that are added to make the volume cubic 742 | 743 | inputParamsCleaver.append("--verbose") 744 | 745 | # Quality 746 | if additionalParameters: 747 | inputParamsCleaver.extend(additionalParameters.split(' ')) 748 | 749 | # Run Cleaver 750 | ep = self.startMesher(inputParamsCleaver, self.getCleaverPath()) 751 | self.logProcessOutput(ep, self.cleaverFilename) 752 | 753 | # Read results 754 | if not self.abortRequested: 755 | outputVolumetricMeshPath = os.path.join(tempDir, "output.vtk") 756 | outputReader = vtk.vtkUnstructuredGridReader() 757 | outputReader.SetFileName(outputVolumetricMeshPath) 758 | outputReader.ReadAllScalarsOn() 759 | outputReader.ReadAllVectorsOn() 760 | outputReader.ReadAllNormalsOn() 761 | outputReader.ReadAllTensorsOn() 762 | outputReader.ReadAllColorScalarsOn() 763 | outputReader.ReadAllTCoordsOn() 764 | outputReader.ReadAllFieldsOn() 765 | outputReader.Update() 766 | 767 | # Cleaver returns the mesh in voxel coordinates, need to transform to RAS space 768 | transformer = vtk.vtkTransformFilter() 769 | transformer.SetInputData(outputReader.GetOutput()) 770 | ijkToRasTransform = vtk.vtkTransform() 771 | ijkToRasTransform.SetMatrix(unscaledIjkToRasMatrix) 772 | transformer.SetTransform(ijkToRasTransform) 773 | 774 | if removeBackgroundMesh: 775 | transformer.Update() 776 | mesh = transformer.GetOutput() 777 | cellData = mesh.GetCellData() 778 | cellData.SetActiveScalars("labels") 779 | backgroundMeshRemover = vtk.vtkThreshold() 780 | backgroundMeshRemover.SetInputData(mesh) 781 | backgroundMeshRemover.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS, vtk.vtkDataSetAttributes.SCALARS) 782 | backgroundMeshRemover.SetLowerThreshold(1) 783 | outputMeshNode.SetUnstructuredGridConnection(backgroundMeshRemover.GetOutputPort()) 784 | else: 785 | outputMeshNode.SetUnstructuredGridConnection(transformer.GetOutputPort()) 786 | 787 | outputMeshDisplayNode = outputMeshNode.GetDisplayNode() 788 | if not outputMeshDisplayNode: 789 | # Initial setup of display node 790 | outputMeshNode.CreateDefaultDisplayNodes() 791 | 792 | outputMeshDisplayNode = outputMeshNode.GetDisplayNode() 793 | outputMeshDisplayNode.SetEdgeVisibility(True) 794 | outputMeshDisplayNode.SetClipping(True) 795 | 796 | colorTableNode = slicer.mrmlScene.AddNode(colorTableNode) 797 | outputMeshDisplayNode.SetAndObserveColorNodeID(colorTableNode.GetID()) 798 | 799 | outputMeshDisplayNode.ScalarVisibilityOn() 800 | outputMeshDisplayNode.SetActiveScalarName('labels') 801 | outputMeshDisplayNode.SetActiveAttributeLocation(vtk.vtkAssignAttribute.CELL_DATA) 802 | outputMeshDisplayNode.SetSliceIntersectionVisibility(True) 803 | outputMeshDisplayNode.SetSliceIntersectionOpacity(0.5) 804 | outputMeshDisplayNode.SetScalarRangeFlag(slicer.vtkMRMLDisplayNode.UseColorNodeScalarRange) 805 | else: 806 | currentColorNode = outputMeshDisplayNode.GetColorNode() 807 | if currentColorNode is not None and currentColorNode.GetType() == currentColorNode.User and currentColorNode.IsA("vtkMRMLColorTableNode"): 808 | # current color table node can be overwritten 809 | currentColorNode.Copy(colorTableNode) 810 | else: 811 | colorTableNode = slicer.mrmlScene.AddNode(colorTableNode) 812 | outputMeshDisplayNode.SetAndObserveColorNodeID(colorTableNode.GetID()) 813 | 814 | # Flip clipping setting twice, this workaround forces update of the display pipeline 815 | # when switching between surface and volumetric mesh 816 | outputMeshDisplayNode.SetClipping(not outputMeshDisplayNode.GetClipping()) 817 | outputMeshDisplayNode.SetClipping(not outputMeshDisplayNode.GetClipping()) 818 | 819 | # Clean up 820 | if self.deleteTemporaryFiles: 821 | import shutil 822 | shutil.rmtree(tempDir) 823 | 824 | self.addLog("Model generation is completed") 825 | 826 | def createMeshFromSegmentationTetGen(self, inputSegmentation, outputMeshNode, segments = [], additionalParameters="", ratio=5, angle=0, volume=10): 827 | 828 | segmentIdList = vtk.vtkStringArray() 829 | for segment in segments: 830 | segmentIdList.InsertNextValue(segment) 831 | 832 | if segmentIdList.GetNumberOfValues() == 0: 833 | logging.info("createMeshFromSegmentationTetGen skipped: there are no selected segments") 834 | return 835 | inputSegmentation.CreateClosedSurfaceRepresentation() 836 | appender = vtk.vtkAppendPolyData() 837 | for i in range(segmentIdList.GetNumberOfValues()): 838 | segmentId = segmentIdList.GetValue(i) 839 | 840 | #Use old function arguments for 4.10 841 | if slicer.app.majorVersion == 4 and slicer.app.minorVersion < 11: 842 | polydata = inputSegmentation.GetClosedSurfaceRepresentation(segmentId) 843 | else: 844 | polydata = vtk.vtkPolyData() 845 | inputSegmentation.GetClosedSurfaceRepresentation(segmentId, polydata) 846 | appender.AddInputData(polydata) 847 | 848 | appender.Update() 849 | self.createMeshFromPolyDataTetGen(appender.GetOutput(), outputMeshNode, additionalParameters, ratio, angle, volume) 850 | 851 | #Clean up representation 852 | inputSegmentation.GetSegmentation().RemoveRepresentation(slicer.vtkSegmentationConverter().GetClosedSurfaceRepresentationName()) 853 | 854 | def createMeshFromPolyDataTetGen(self, inputPolyData, outputMeshNode, additionalParameters="", ratio=5, angle=0, volume=10): 855 | 856 | self.abortRequested = False 857 | tempDir = self.createTempDirectory() 858 | self.addLog('Mesh generation is started in working directory: '+tempDir) 859 | 860 | # Write inputs 861 | qt.QDir().mkpath(tempDir) 862 | 863 | inputSurfaceMeshFilePath = os.path.join(tempDir, "mesh.ply") 864 | inputWriter = vtk.vtkPLYWriter() 865 | inputWriter.SetInputData(inputPolyData) 866 | inputWriter.SetFileName(inputSurfaceMeshFilePath) 867 | inputWriter.SetFileTypeToASCII() 868 | inputWriter.Write() 869 | 870 | #Command line for quality parameters 871 | parameters = 'q'+"{:.2f}".format(ratio)+'/'+"{:.2f}".format(angle)+'a'+"{:.2f}".format(volume) 872 | 873 | inputParamsTetGen = [] 874 | inputParamsTetGen.append("-k"+parameters+additionalParameters) 875 | inputParamsTetGen.append(inputSurfaceMeshFilePath) 876 | 877 | # Run tetgen 878 | ep = self.startMesher(inputParamsTetGen, self.getTetGenPath()) 879 | self.logProcessOutput(ep, self.tetGenFilename) 880 | 881 | # Read results 882 | if not self.abortRequested: 883 | outputVolumetricMeshPath = os.path.join(tempDir, "mesh.1.vtk") 884 | outputReader = vtk.vtkUnstructuredGridReader() 885 | outputReader.SetFileName(outputVolumetricMeshPath) 886 | outputReader.ReadAllScalarsOn() 887 | outputReader.ReadAllVectorsOn() 888 | outputReader.ReadAllNormalsOn() 889 | outputReader.ReadAllTensorsOn() 890 | outputReader.ReadAllColorScalarsOn() 891 | outputReader.ReadAllTCoordsOn() 892 | outputReader.ReadAllFieldsOn() 893 | outputReader.Update() 894 | outputMeshNode.SetUnstructuredGridConnection(outputReader.GetOutputPort()) 895 | 896 | outputMeshDisplayNode = outputMeshNode.GetDisplayNode() 897 | if not outputMeshDisplayNode: 898 | # Initial setup of display node 899 | outputMeshNode.CreateDefaultDisplayNodes() 900 | outputMeshDisplayNode = outputMeshNode.GetDisplayNode() 901 | outputMeshDisplayNode.SetEdgeVisibility(True) 902 | outputMeshDisplayNode.SetClipping(True) 903 | 904 | # Clean up 905 | if self.deleteTemporaryFiles: 906 | import shutil 907 | shutil.rmtree(tempDir) 908 | 909 | self.addLog("Model generation is completed") 910 | 911 | class SegmentMesherTest(ScriptedLoadableModuleTest): 912 | """ 913 | This is the test case for your scripted module. 914 | Uses ScriptedLoadableModuleTest base class, available at: 915 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 916 | """ 917 | 918 | def setUp(self): 919 | """ Do whatever is needed to reset the state - typically a scene clear will be enough. 920 | """ 921 | slicer.mrmlScene.Clear(0) 922 | 923 | def runTest(self): 924 | """Run as few or as many tests as needed here. 925 | """ 926 | self.setUp() 927 | self.test_TetGen1() 928 | 929 | def test_TetGen1(self): 930 | """ Ideally you should have several levels of tests. At the lowest level 931 | tests should exercise the functionality of the logic with different inputs 932 | (both valid and invalid). At higher levels your tests should emulate the 933 | way the user would interact with your code and confirm that it still works 934 | the way you intended. 935 | One of the most important features of the tests is that it should alert other 936 | developers when their changes will have an impact on the behavior of your 937 | module. For example, if a developer removes a feature that you depend on, 938 | your test should break so they know that the feature is needed. 939 | """ 940 | 941 | self.delayDisplay("Starting the test") 942 | 943 | cylinder = vtk.vtkCylinderSource() 944 | cylinder.SetRadius(10) 945 | cylinder.SetHeight(40) 946 | cylinder.Update() 947 | inputModelNode = slicer.modules.models.logic().AddModel(cylinder.GetOutput()) 948 | 949 | outputModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode") 950 | outputModelNode.CreateDefaultDisplayNodes() 951 | 952 | logic = SegmentMesherLogic() 953 | logic.createMeshFromPolyDataTetGen(inputModelNode.GetPolyData(), outputModelNode, '', 100, 0, 100) 954 | 955 | self.assertTrue(outputModelNode.GetMesh().GetNumberOfPoints()>0) 956 | self.assertTrue(outputModelNode.GetMesh().GetNumberOfCells()>0) 957 | 958 | inputModelNode.GetDisplayNode().SetOpacity(0.2) 959 | 960 | outputDisplayNode = outputModelNode.GetDisplayNode() 961 | outputDisplayNode.SetColor(1,0,0) 962 | outputDisplayNode.SetEdgeVisibility(True) 963 | outputDisplayNode.SetClipping(True) 964 | 965 | clipNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLClipModelsNode") 966 | clipNode.SetRedSliceClipState(clipNode.ClipNegativeSpace) 967 | 968 | self.delayDisplay('Test passed!') 969 | 970 | METHOD_CLEAVER = 'CLEAVER' 971 | METHOD_TETGEN = 'TETGEN' 972 | -------------------------------------------------------------------------------- /SegmentMesher/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Python) 2 | -------------------------------------------------------------------------------- /SegmentMesher/Testing/Python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py) 3 | -------------------------------------------------------------------------------- /SlicerSegmentMesher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/37a56a4216048df01af0c6392d90751f47707cea/SlicerSegmentMesher.png -------------------------------------------------------------------------------- /SlicerSegmentMesher.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lassoan/SlicerSegmentMesher/37a56a4216048df01af0c6392d90751f47707cea/SlicerSegmentMesher.xcf -------------------------------------------------------------------------------- /SuperBuild.cmake: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Enable and setup External project global properties 3 | #----------------------------------------------------------------------------- 4 | 5 | set(ep_common_c_flags "${CMAKE_C_FLAGS_INIT} ${ADDITIONAL_C_FLAGS}") 6 | set(ep_common_cxx_flags "${CMAKE_CXX_FLAGS_INIT} ${ADDITIONAL_CXX_FLAGS}") 7 | 8 | #----------------------------------------------------------------------------- 9 | # Project dependencies 10 | #----------------------------------------------------------------------------- 11 | 12 | include(ExternalProject) 13 | 14 | foreach(dep ${EXTENSION_DEPENDS}) 15 | mark_as_superbuild(${dep}_DIR) 16 | endforeach() 17 | 18 | set(proj ${SUPERBUILD_TOPLEVEL_PROJECT}) 19 | set(${proj}_DEPENDS tetgen cleaver) 20 | 21 | ExternalProject_Include_Dependencies(${proj} 22 | PROJECT_VAR proj 23 | SUPERBUILD_VAR ${EXTENSION_NAME}_SUPERBUILD 24 | ) 25 | 26 | ExternalProject_Add(${proj} 27 | ${${proj}_EP_ARGS} 28 | DOWNLOAD_COMMAND "" 29 | INSTALL_COMMAND "" 30 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} 31 | BINARY_DIR ${EXTENSION_BUILD_SUBDIRECTORY} 32 | BUILD_ALWAYS 1 33 | CMAKE_CACHE_ARGS 34 | -DSubversion_SVN_EXECUTABLE:STRING=${Subversion_SVN_EXECUTABLE} 35 | -DGIT_EXECUTABLE:STRING=${GIT_EXECUTABLE} 36 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 37 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 38 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 39 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 40 | -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} 41 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_RUNTIME_OUTPUT_DIRECTORY} 42 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY} 43 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 44 | -DMIDAS_PACKAGE_EMAIL:STRING=${MIDAS_PACKAGE_EMAIL} 45 | -DMIDAS_PACKAGE_API_KEY:STRING=${MIDAS_PACKAGE_API_KEY} 46 | -D${EXTENSION_NAME}_SUPERBUILD:BOOL=OFF 47 | -DEXTENSION_SUPERBUILD_BINARY_DIR:PATH=${${EXTENSION_NAME}_BINARY_DIR} 48 | DEPENDS 49 | ${${proj}_DEPENDS} 50 | ) 51 | -------------------------------------------------------------------------------- /SuperBuild/External_cleaver.cmake: -------------------------------------------------------------------------------- 1 | set(proj cleaver) 2 | 3 | # Set dependency list 4 | 5 | if (NOT ITK_FOUND) 6 | # Cleaver is bundled with Slicer in a custom application. 7 | # In this case ITK dependency must be added. 8 | set(${proj}_DEPENDS ITK) 9 | endif() 10 | 11 | # Include dependent projects if any 12 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 13 | 14 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 15 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 16 | endif() 17 | 18 | # Sanity checks 19 | if(DEFINED cleaver_DIR AND NOT EXISTS ${cleaver_DIR}) 20 | message(FATAL_ERROR "cleaver_DIR variable is defined but corresponds to nonexistent directory") 21 | endif() 22 | 23 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 24 | 25 | set(${proj}_INSTALL_DIR ${CMAKE_BINARY_DIR}/${proj}-install) 26 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 27 | 28 | ExternalProject_Add(${proj} 29 | # Slicer 30 | ${${proj}_EP_ARGS} 31 | SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj} 32 | SOURCE_SUBDIR src # requires CMake 3.7 or later 33 | BINARY_DIR ${proj}-build 34 | INSTALL_DIR ${${proj}_INSTALL_DIR} 35 | GIT_REPOSITORY "${EP_GIT_PROTOCOL}://github.com/SCIInstitute/Cleaver2.git" 36 | GIT_TAG "e4fa5b091b450d736c479cad4f0349fc08b8fb16" 37 | #--Patch step------------- 38 | #PATCH_COMMAND ${CMAKE_COMMAND} -Delastix_SRC_DIR=${CMAKE_BINARY_DIR}/${proj} 39 | # -P ${CMAKE_CURRENT_LIST_DIR}/${proj}_patch.cmake 40 | #--Configure step------------- 41 | CMAKE_CACHE_ARGS 42 | -DGIT_EXECUTABLE:STRING=${GIT_EXECUTABLE} 43 | -DITK_DIR:STRING=${ITK_DIR} 44 | -DBUILD_CLI:BOOL=ON 45 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 46 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 47 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 48 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 49 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 50 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 51 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 52 | -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} 53 | -DBUILD_TESTING:BOOL=OFF 54 | -DCMAKE_MACOSX_RPATH:BOOL=0 55 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 56 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 57 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 58 | -DCLEAVER2_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 59 | -DCLEAVER2_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 60 | #--Build step----------------- 61 | #--Install step----------------- 62 | # Don't perform installation at the end of the build 63 | INSTALL_COMMAND "" 64 | DEPENDS 65 | ${${proj}_DEPENDS} 66 | ) 67 | 68 | else() 69 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 70 | endif() 71 | 72 | mark_as_superbuild(${proj}_DIR:PATH) 73 | -------------------------------------------------------------------------------- /SuperBuild/External_tetgen.cmake: -------------------------------------------------------------------------------- 1 | set(proj tetgen) 2 | 3 | # Set dependency list 4 | set(${proj}_DEPENDS "") 5 | 6 | # Include dependent projects if any 7 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 8 | 9 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 10 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 11 | endif() 12 | 13 | # Sanity checks 14 | if(DEFINED tetgen_DIR AND NOT EXISTS ${tetgen_DIR}) 15 | message(FATAL_ERROR "tetgen_DIR variable is defined but corresponds to nonexistent directory") 16 | endif() 17 | 18 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 19 | 20 | set(${proj}_INSTALL_DIR ${CMAKE_BINARY_DIR}/${proj}-install) 21 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 22 | 23 | ExternalProject_Add(${proj} 24 | # Slicer 25 | ${${proj}_EP_ARGS} 26 | SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj} 27 | #SOURCE_SUBDIR src # requires CMake 3.7 or later 28 | BINARY_DIR ${proj}-build 29 | INSTALL_DIR ${${proj}_INSTALL_DIR} 30 | GIT_REPOSITORY "${EP_GIT_PROTOCOL}://github.com/lassoan/tetgen.git" 31 | #GIT_TAG "ef057ff89233822b26b04b31c3c043af57d5deff" 32 | #--Patch step------------- 33 | #PATCH_COMMAND ${CMAKE_COMMAND} -Delastix_SRC_DIR=${CMAKE_BINARY_DIR}/${proj} 34 | # -P ${CMAKE_CURRENT_LIST_DIR}/${proj}_patch.cmake 35 | #--Configure step------------- 36 | CMAKE_CACHE_ARGS 37 | -DGIT_EXECUTABLE:STRING=${GIT_EXECUTABLE} 38 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 39 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 40 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 41 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 42 | -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} 43 | -DBUILD_TESTING:BOOL=OFF 44 | -DCMAKE_MACOSX_RPATH:BOOL=0 45 | # location of build outputs in the build tree 46 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 47 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 48 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 49 | # location of build outputs in the installation folder 50 | -DTETGEN_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 51 | #--Build step----------------- 52 | #--Install step----------------- 53 | # Don't perform installation at the end of the build 54 | INSTALL_COMMAND "" 55 | DEPENDS 56 | ${${proj}_DEPENDS} 57 | ) 58 | 59 | else() 60 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 61 | endif() 62 | 63 | mark_as_superbuild(${proj}_DIR:PATH) 64 | --------------------------------------------------------------------------------