From 1c86d7d7151a1bed45b27b8b86a6c2697db32b2d Mon Sep 17 00:00:00 2001 From: Saleem Edah-Tally Date: Fri, 6 Dec 2024 18:24:20 +0100 Subject: [PATCH] Add PreFitTube module. This module pre-fits a Shape::Tube markups node along an input markups curve that represents the axis of a non-aneurysmal artery. --- CMakeLists.txt | 1 + Docs/PreFitTube.md | 18 + Docs/PreFitTube_0.png | Bin 0 -> 37381 bytes PreFitTube/CMakeLists.txt | 31 ++ PreFitTube/PreFitTube.py | 430 ++++++++++++++++++++++ PreFitTube/Resources/Icons/PreFitTube.png | Bin 0 -> 8423 bytes PreFitTube/Resources/UI/PreFitTube.ui | 347 +++++++++++++++++ PreFitTube/Testing/CMakeLists.txt | 1 + PreFitTube/Testing/Python/CMakeLists.txt | 2 + README.md | 1 + 10 files changed, 831 insertions(+) create mode 100644 Docs/PreFitTube.md create mode 100644 Docs/PreFitTube_0.png create mode 100644 PreFitTube/CMakeLists.txt create mode 100644 PreFitTube/PreFitTube.py create mode 100644 PreFitTube/Resources/Icons/PreFitTube.png create mode 100644 PreFitTube/Resources/UI/PreFitTube.ui create mode 100644 PreFitTube/Testing/CMakeLists.txt create mode 100644 PreFitTube/Testing/Python/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b184cca..77e6d17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ add_subdirectory(ClipVessel) if(SlicerVMTK_USE_ExtraMarkups) add_subdirectory(CrossSectionAnalysis) add_subdirectory(StenosisMeasurement3D) + add_subdirectory(PreFitTube) endif() #----------------------------------------------------------------------------- diff --git a/Docs/PreFitTube.md b/Docs/PreFitTube.md new file mode 100644 index 0000000..3b875dc --- /dev/null +++ b/Docs/PreFitTube.md @@ -0,0 +1,18 @@ +# Pre-fit tube + +This module pre-fits a Shape::Tube markups node along an input markups curve that represents the axis of a non-aneurysmal artery. The control points of the tube must be further repositioned to represent the best *estimate* of the arterial wall. + +![Example](PreFitTube_0.png) + +### Usage + +Select a markups curve node, a scalar volume of a CT angiogram and a target dimension profile. Select an output Shape::Tube node, and optionally a segmentation node to keep the segment mask. After applying, the resulting Shape::Tube node may be post-processed to position the control points on the arterial wall. + +### Notes + - This extension is a requirement: [ExtraMarkups](https://github.com/chir-set/SlicerExtraMarkups). + - Use cropped volumes to save time. + - For tiny targets, resampling the volume to increase the resolution may help. + - The input curve should represent the axis of the artery. + - For diseased arteries, the more the curve passes through calcifications and soft lesions, the better for the segment mask. + - An output segmentation node can be specified to view the segment mask. + diff --git a/Docs/PreFitTube_0.png b/Docs/PreFitTube_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9df066a51818530d96f82f780d63fe93820c24c9 GIT binary patch literal 37381 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 documentation. +""") + self.parent.acknowledgementText = _(""" +This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc., Andras Lasso, PerkLab, +and Steve Pieper, Isomics, Inc. and was partially funded by NIH grant 3P41RR013218-12S1. +""") + +# +# PreFitTubeParameterNode +# + +@parameterNodeWrapper +class PreFitTubeParameterNode: + inputVolumeNode: slicer.vtkMRMLScalarVolumeNode + inputCurveNode: slicer.vtkMRMLMarkupsCurveNode + dimensionPreset: int = 3 + outputShapeNode: slicer.vtkMRMLMarkupsShapeNode + outputSegmentationNode: slicer.vtkMRMLSegmentationNode + +class dimensionPresets: + tiny = { + "extrusionKernelSize": 0.5, + "gaussianStandardDeviation": 0.5, + "seedRadius": 0.5, + "shellMargin": 5.0, + "shellThickness": 0.8 + } + small = { + "extrusionKernelSize": 1.5, + "gaussianStandardDeviation": 0.8, + "seedRadius": 0.8, + "shellMargin": 9.0, + "shellThickness": 1.1 + } + medium = { + "extrusionKernelSize": 3.2, + "gaussianStandardDeviation": 1.2, + "seedRadius": 1.0, + "shellMargin": 13.0, + "shellThickness": 1.5 + } + big = { + "extrusionKernelSize": 4.1, + "gaussianStandardDeviation": 1.6, + "seedRadius": 1.0, + "shellMargin": 22.0, + "shellThickness": 2.0 + } + huge = { + "extrusionKernelSize": 5.1, + "gaussianStandardDeviation": 2.1, + "seedRadius": 1.0, + "shellMargin": 32.0, + "shellThickness": 2.2 + } + + presets = (tiny, small, medium, big, huge) + def getPreset(self, index): + if index < 1 or index > len(self.presets): + raise ValueError("Preset index out of range.") + return self.presets[index - 1] +# +# PreFitTubeWidget +# + +class PreFitTubeWidget(ScriptedLoadableModuleWidget, VTKObservationMixin): + """Uses ScriptedLoadableModuleWidget base class, available at: + https://github.com/Slicer/Slicer/blob/main/Base/Python/slicer/ScriptedLoadableModule.py + """ + + def __init__(self, parent=None) -> None: + """Called when the user opens the module the first time and the widget is initialized.""" + ScriptedLoadableModuleWidget.__init__(self, parent) + VTKObservationMixin.__init__(self) # needed for parameter node observation + self.logic = None + self._parameterNode = None + self._parameterNodeGuiTag = None + self._updatingGuiFromParameterNode = False + + def setup(self) -> None: + """Called when the user opens the module the first time and the widget is initialized.""" + ScriptedLoadableModuleWidget.setup(self) + + # Load widget from .ui file (created by Qt Designer). + # Additional widgets can be instantiated manually and added to self.layout. + uiWidget = slicer.util.loadUI(self.resourcePath("UI/PreFitTube.ui")) + self.layout.addWidget(uiWidget) + self.ui = slicer.util.childWidgetVariables(uiWidget) + + # Set scene in MRML widgets. Make sure that in Qt designer the top-level qMRMLWidget's + # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's. + # "setMRMLScene(vtkMRMLScene*)" slot. + uiWidget.setMRMLScene(slicer.mrmlScene) + + # Create logic class. Logic implements all computations that should be possible to run + # in batch mode, without a graphical user interface. + self.logic = PreFitTubeLogic() + + self.ui.dimensionComboBox.addItem(_("Tiny"), DIMENSION_TINY) + self.ui.dimensionComboBox.addItem(_("Small"), DIMENSION_SMALL) + self.ui.dimensionComboBox.addItem(_("Medium"), DIMENSION_MEDIUM) + self.ui.dimensionComboBox.addItem(_("Big"), DIMENSION_BIG) + self.ui.dimensionComboBox.addItem(_("Huge"), DIMENSION_HUGE) + + # Connections + self.ui.dimensionComboBox.connect('currentIndexChanged(int)', self.onDimensionPresetChanged) + + # These connections ensure that we update parameter node when scene is closed + self.addObserver(slicer.mrmlScene, slicer.mrmlScene.StartCloseEvent, self.onSceneStartClose) + self.addObserver(slicer.mrmlScene, slicer.mrmlScene.EndCloseEvent, self.onSceneEndClose) + + # Buttons + self.ui.applyButton.connect("clicked(bool)", self.onApplyButton) + + # Make sure parameter node is initialized (needed for module reload) + self.initializeParameterNode() + + def cleanup(self) -> None: + """Called when the application closes and the module widget is destroyed.""" + self.removeObservers() + + def enter(self) -> None: + """Called each time the user opens this module.""" + # Make sure parameter node exists and observed + self.initializeParameterNode() + + def exit(self) -> None: + """Called each time the user opens a different module.""" + # Do not react to parameter node changes (GUI will be updated when the user enters into the module) + if self._parameterNode: + self._parameterNode.disconnectGui(self._parameterNodeGuiTag) + self._parameterNodeGuiTag = None + + def onSceneStartClose(self, caller, event) -> None: + """Called just before the scene is closed.""" + # Parameter node will be reset, do not use it anymore + self.setParameterNode(None) + + def onSceneEndClose(self, caller, event) -> None: + """Called just after the scene is closed.""" + # If this module is shown while the scene is closed then recreate a new parameter node immediately + if self.parent.isEntered: + self.initializeParameterNode() + + def initializeParameterNode(self) -> None: + """Ensure parameter node exists and observed.""" + # Parameter node stores all user choices in parameter values, node selections, etc. + # so that when the scene is saved and reloaded, these settings are restored. + + self.setParameterNode(self.logic.getParameterNode()) + + def setParameterNode(self, inputParameterNode: Optional[PreFitTubeParameterNode]) -> None: + """ + Set and observe parameter node. + Observation is needed because when the parameter node is changed then the GUI must be updated immediately. + """ + + if self._parameterNode: + self._parameterNode.disconnectGui(self._parameterNodeGuiTag) + self._parameterNode = inputParameterNode + if self._parameterNode: + # Note: in the .ui file, a Qt dynamic property called "SlicerParameterName" is set on each + # ui element that needs connection. + self._parameterNodeGuiTag = self._parameterNode.connectGui(self.ui) + self.updateGuiFromParameterNode() + + def onApplyButton(self) -> None: + """Run processing when user clicks "Apply" button.""" + with slicer.util.tryWithErrorDisplay(_("Failed to compute results."), waitCursor=True): + self.logic.process() + + def onDimensionPresetChanged(self, index): + if self._parameterNode: + self._parameterNode.dimensionPreset = self.ui.dimensionComboBox.currentData + + def updateGuiFromParameterNode(self): + if not self._parameterNode or self._updatingGuiFromParameterNode: + return + self._updatingGuiFromParameterNode = True + + index = self.ui.dimensionComboBox.findData(self._parameterNode.dimensionPreset) + self.ui.dimensionComboBox.setCurrentIndex(index) + self.ui.ouitputOptionsCollapsibleButton.collapsed = (self._parameterNode.outputSegmentationNode is None) + + self._updatingGuiFromParameterNode = False +# +# PreFitTubeLogic +# + +class PreFitTubeLogic(ScriptedLoadableModuleLogic): + """This class should implement all the actual + computation done by your module. The interface + should be such that other python code can import + this class and make use of the functionality without + requiring an instance of the Widget. + Uses ScriptedLoadableModuleLogic base class, available at: + https://github.com/Slicer/Slicer/blob/main/Base/Python/slicer/ScriptedLoadableModule.py + """ + + def __init__(self) -> None: + """Called when the logic class is instantiated. Can be used for initializing member variables.""" + ScriptedLoadableModuleLogic.__init__(self) + self._parameterNode = None + + def getParameterNode(self): + self._parameterNode = PreFitTubeParameterNode(super().getParameterNode()) + return self._parameterNode + + # 56.56s for 10⁴ points. + def getFarthestPoints(self, points : vtk.vtkPoints): + numberOfPoints = points.GetNumberOfPoints() + distances = vtk.vtkFloatArray() + distances.SetNumberOfComponents(7) + for i in range(numberOfPoints): + referencePoint = [0] * 3 + points.GetPoint(i, referencePoint) + for j in range(numberOfPoints): + nextPoint = [0] * 3 + points.GetPoint(j, nextPoint) + distance2 = vtk.vtkMath.Distance2BetweenPoints(referencePoint, nextPoint) + distances.InsertNextTuple((referencePoint[0], referencePoint[1], referencePoint[2], + nextPoint[0], nextPoint[1], nextPoint[2], + distance2)) + + sorter = vtk.vtkSortDataArray() + sorter.SortArrayByComponent(distances, 6, 1) + farthest = distances.GetTuple(0) + + result = vtk.vtkPoints() + result.InsertNextPoint((farthest[0], farthest[1], farthest[2])) + result.InsertNextPoint((farthest[3], farthest[4], farthest[5])) + + return result + + def process(self) -> None: + + import time + + startTime = time.time() + logging.info(_("Processing started.")) + + curveNode = self._parameterNode.inputCurveNode + if not curveNode: + raise ValueError("No input curve node specified.") + + volumeNode = self._parameterNode.inputVolumeNode + if not volumeNode: + raise ValueError("No input volume node specified.") + + shapeNode = self._parameterNode.outputShapeNode + if not shapeNode: + raise ValueError("No output shape node specified.") + + segmentationNode = self._parameterNode.outputSegmentationNode + if not segmentationNode: + segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode") + + profiles = dimensionPresets() + profile = profiles.getPreset(self._parameterNode.dimensionPreset) + + """ + Create a segment than can overlap on contrast calcifications and soft + lesions. The more the curve can pass through each type of structure, + the better. But the curve should represent the axis of the vessel. + """ + import GuidedVeinSegmentation + gvsLogic = GuidedVeinSegmentation.GuidedVeinSegmentationLogic() + segmentID = gvsLogic.process( + inputCurve = curveNode, + inputVolume = volumeNode, + inputSegmentation = segmentationNode, + extrusionKernelSize = profile["extrusionKernelSize"], + gaussianStandardDeviation = profile["gaussianStandardDeviation"], + seedRadius = profile["seedRadius"], + shellMargin = profile["shellMargin"], + shellThickness = profile["shellThickness"], + subtractOtherSegments = False # Do not account for prior work in the same segmentation. + ) + segmentationNode.CreateClosedSurfaceRepresentation() + # At this step, the segment editor is already setup. + + # Get segment as polydata. + segmentPolyData = vtk.vtkPolyData() + if not segmentationNode.GetClosedSurfaceRepresentation(segmentID, segmentPolyData): + if not self._parameterNode.outputSegmentationNode: + slicer.mrmlScene.RemoveNode(segmentationNode) + raise RuntimeError(_("Failed to get segment polydata.")) + + # Reset shape node and ensure it is a Tube. + shapeNode.RemoveAllControlPoints() + shapeNode.SetShapeName(slicer.vtkMRMLMarkupsShapeNode.Tube) + shapeNode.CreateDefaultDisplayNodes() + + numberOfControlPoints = curveNode.GetNumberOfControlPoints() + curvePolyData = curveNode.GetCurveWorld() + for controlPointindex in range(numberOfControlPoints): + curvePointIndex = curveNode.GetCurvePointIndexFromControlPointIndex(controlPointindex) + # GetCurveDirectionAtPointIndexWorld() can be troublesome. + p1 = [0.0] * 3 + p2 = [0.0] * 3 + planeOrigin = [0.0] * 3 + if controlPointindex < (numberOfControlPoints - 1): + curvePolyData.GetPoint(curvePointIndex, p1) + curvePolyData.GetPoint(curvePointIndex + 1, p2) + vtk.vtkMath.Assign(p1, planeOrigin) + else: + curvePolyData.GetPoint(curvePointIndex - 1, p1) + curvePolyData.GetPoint(curvePointIndex, p2) + vtk.vtkMath.Assign(p2, planeOrigin) + direction = [0.0] * 3 + vtk.vtkMath.Subtract(p2, p1, direction) + + # Place a plane perpendicular to the curve. + plane = vtk.vtkPlane() + plane.SetOrigin(planeOrigin) + plane.SetNormal(direction) + + # Cut through the segment closed surface and get the points of the contour. + planeCut = vtk.vtkCutter() + planeCut.SetInputData(segmentPolyData) + planeCut.SetCutFunction(plane) + planeCut.Update() + planePoints = planeCut.GetOutput().GetPoints() + if (not planePoints) or (planePoints.GetNumberOfPoints == 0): + logging.info(_("Skipping empty section.")) + continue + + # Keep the closest connected region around the control point. + connectivityFilter = vtk.vtkConnectivityFilter() + connectivityFilter.SetInputData(planeCut.GetOutput()) + connectivityFilter.SetClosestPoint(planeOrigin) + connectivityFilter.SetExtractionModeToClosestPointRegion() + connectivityFilter.Update() + closestPoints = connectivityFilter.GetOutput().GetPoints() + if (not closestPoints) or (closestPoints.GetNumberOfPoints == 0): + logging.info(_("Skipping empty closest section.")) # ! + continue + + # Get farthest points of the contour. + farthestPoints = self.getFarthestPoints(closestPoints) + + # Add control points to the tube. + shapeNode.AddControlPoint(farthestPoints.GetPoint(0)) + shapeNode.AddControlPoint(farthestPoints.GetPoint(1)) + + # If we created the segmentation, remove it. + if not self._parameterNode.outputSegmentationNode: + slicer.mrmlScene.RemoveNode(segmentationNode) + + stopTime = time.time() + durationValue = '%.2f' % (stopTime-startTime) + logging.info(_("Processing completed in {duration} seconds").format(duration=durationValue)) + +# +# PreFitTubeTest +# + + +class PreFitTubeTest(ScriptedLoadableModuleTest): + """ + This is the test case for your scripted module. + Uses ScriptedLoadableModuleTest base class, available at: + https://github.com/Slicer/Slicer/blob/main/Base/Python/slicer/ScriptedLoadableModule.py + """ + + def setUp(self): + """Do whatever is needed to reset the state - typically a scene clear will be enough.""" + slicer.mrmlScene.Clear() + + def runTest(self): + """Run as few or as many tests as needed here.""" + self.setUp() + self.test_PreFitTube1() + + def test_PreFitTube1(self): + """Ideally you should have several levels of tests. At the lowest level + tests should exercise the functionality of the logic with different inputs + (both valid and invalid). At higher levels your tests should emulate the + way the user would interact with your code and confirm that it still works + the way you intended. + One of the most important features of the tests is that it should alert other + developers when their changes will have an impact on the behavior of your + module. For example, if a developer removes a feature that you depend on, + your test should break so they know that the feature is needed. + """ + + self.delayDisplay("Starting the test") + + self.delayDisplay("Test passed") + +DIMENSION_TINY = 1 +DIMENSION_SMALL = 2 +DIMENSION_MEDIUM = 3 +DIMENSION_BIG = 4 +DIMENSION_HUGE = 5 diff --git a/PreFitTube/Resources/Icons/PreFitTube.png b/PreFitTube/Resources/Icons/PreFitTube.png new file mode 100644 index 0000000000000000000000000000000000000000..0674fa78095823961af50ee4e4daef9963e21288 GIT binary patch literal 8423 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 JPDHLkV1lkYsnh@f literal 0 HcmV?d00001 diff --git a/PreFitTube/Resources/UI/PreFitTube.ui b/PreFitTube/Resources/UI/PreFitTube.ui new file mode 100644 index 0000000..6777d73 --- /dev/null +++ b/PreFitTube/Resources/UI/PreFitTube.ui @@ -0,0 +1,347 @@ + + + PreFitTube + + + + 0 + 0 + 325 + 373 + + + + + + + Inputs + + + + + + Open curve: + + + + + + + Select a markups curve. + + + + vtkMRMLMarkupsCurveNode + + + + false + + + true + + + false + + + true + + + true + + + true + + + inputCurveNode + + + + + + + Volume: + + + + + + + Select a volume. + + + + vtkMRMLScalarVolumeNode + + + + false + + + true + + + false + + + true + + + true + + + true + + + inputVolumeNode + + + + + + + Target dimension: + + + + + + + + 0 + 0 + + + + Select a target artery dimension profile. + +Examples: + - Tiny: leg arteries, coronaries, + - Huge: thoracic aorta. + + + + + + + + + + Outputs + + + + + + + + Select a shape node. + + + + vtkMRMLMarkupsShapeNode + + + + false + + + Tube + + + true + + + true + + + true + + + true + + + true + + + outputShapeNode + + + + + + + Shape tube: + + + + + + + + + Options + + + true + + + + + + Select an optional segmentation node to keep the segment mask. + + + + vtkMRMLSegmentationNode + + + + + + + true + + + true + + + true + + + + + + outputSegmentationNode + + + + + + + Segmentation: + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + Run the algorithm. + + + Apply + + + + + + + + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
+ + qMRMLNodeComboBox + QWidget +
qMRMLNodeComboBox.h
+ 1 +
+ + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+
+ + + + PreFitTube + mrmlSceneChanged(vtkMRMLScene*) + inputCurveSelector + setMRMLScene(vtkMRMLScene*) + + + 162 + 155 + + + 216 + 55 + + + + + PreFitTube + mrmlSceneChanged(vtkMRMLScene*) + inputVolumeSelector + setMRMLScene(vtkMRMLScene*) + + + 162 + 155 + + + 216 + 86 + + + + + PreFitTube + mrmlSceneChanged(vtkMRMLScene*) + outputShapeSelector + setMRMLScene(vtkMRMLScene*) + + + 162 + 155 + + + 199 + 203 + + + + + PreFitTube + mrmlSceneChanged(vtkMRMLScene*) + outputSegmentationSelector + setMRMLScene(vtkMRMLScene*) + + + 162 + 155 + + + 206 + 191 + + + + +
diff --git a/PreFitTube/Testing/CMakeLists.txt b/PreFitTube/Testing/CMakeLists.txt new file mode 100644 index 0000000..655007a --- /dev/null +++ b/PreFitTube/Testing/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Python) diff --git a/PreFitTube/Testing/Python/CMakeLists.txt b/PreFitTube/Testing/Python/CMakeLists.txt new file mode 100644 index 0000000..5658d8b --- /dev/null +++ b/PreFitTube/Testing/Python/CMakeLists.txt @@ -0,0 +1,2 @@ + +#slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py) diff --git a/README.md b/README.md index 64c4e2d..f766f52 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Workflows: - [Arterial calcification pre-processor](Docs/ArterialCalcificationPreProcessor.md): Segment arterial calcifications within a specified distance around a lumen. - [Centerline disassembly](Docs/CenterlineDisassembly.md): Break down a bifurcated centerline model into parts. - [Clip vessel](Docs/ClipVessel.md): Clip a segmentation or model normal to the centerline. + - [Pre-fit tube](Docs/PreFitTube.md): Pre-fit a Shape::Tube markups node around an artery. Legacy modules (replaced by other modules, not developed anymore):