diff --git a/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.cxx b/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.cxx index 395171d..a25b1e8 100644 --- a/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.cxx +++ b/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.cxx @@ -382,7 +382,7 @@ void vtkBezierSurfaceSource::ComputeBinomialCoefficients() unsigned int yGrid = this->NumberOfControlPoints[1]; #pragma omp parallel for - for (int i=0; iBinomialCoefficientsX[i] = Factorial(xGrid-1) / @@ -393,7 +393,7 @@ void vtkBezierSurfaceSource::ComputeBinomialCoefficients() if (xGrid != yGrid) { #pragma omp parallel for - for (int i=0; iBinomialCoefficientsY[i] = Factorial(yGrid-1) / @@ -403,7 +403,7 @@ void vtkBezierSurfaceSource::ComputeBinomialCoefficients() else { #pragma omp parallel for - for (int i=0; iBinomialCoefficientsY[i] = this->BinomialCoefficientsX[i]; } @@ -436,7 +436,7 @@ void vtkBezierSurfaceSource::EvaluateBezierSurface(vtkPoints *points) unsigned int yRes = this->Resolution[1]; #pragma omp parallel for - for (int i=0; i(xRes - 1); diff --git a/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.h b/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.h index ca0f654..573a143 100644 --- a/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.h +++ b/LiverMarkups/VTKWidgets/vtkBezierSurfaceSource.h @@ -75,7 +75,7 @@ class VTK_SLICER_LIVERMARKUPS_MODULE_VTKWIDGETS_EXPORT vtkBezierSurfaceSource : * @param os ouptut stream to print the properties to. * @param indent indentation value. */ - void PrintSelf(ostream &os, vtkIndent indent); + void PrintSelf(ostream &os, vtkIndent indent) override; /** * Set the control points. @@ -164,7 +164,7 @@ class VTK_SLICER_LIVERMARKUPS_MODULE_VTKWIDGETS_EXPORT vtkBezierSurfaceSource : * * @return return code. */ - int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *); + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; private: vtkBezierSurfaceSource(const vtkBezierSurfaceSource&); // Not implemented. diff --git a/LiverResections/MRML/vtkMRMLLiverResectionNode.h b/LiverResections/MRML/vtkMRMLLiverResectionNode.h index 18668f2..7846570 100644 --- a/LiverResections/MRML/vtkMRMLLiverResectionNode.h +++ b/LiverResections/MRML/vtkMRMLLiverResectionNode.h @@ -94,7 +94,7 @@ class VTK_SLICER_LIVERRESECTIONS_MODULE_MRML_EXPORT vtkMRMLLiverResectionNode vtkMRMLCopyContentDefaultMacro(vtkMRMLLiverResectionNode); /// Create default storage node or nullptr if does not have one - vtkMRMLStorageNode* CreateDefaultStorageNode(); + vtkMRMLStorageNode* CreateDefaultStorageNode() override; // TODO: Review the need for this further down the road @@ -371,16 +371,16 @@ class VTK_SLICER_LIVERRESECTIONS_MODULE_MRML_EXPORT vtkMRMLLiverResectionNode bool GridVisibility; float GridDivisions; float GridThickness; - bool Grid3DVisibility; bool ShowResection2D; - bool EnableFlexibleBoundary; - bool Grid2DVisibility; double HepaticContourThickness; //Resection margin in mm double PortalContourThickness; //Uncertainty margin in mm float HepaticContourColor[3]; float PortalContourColor[3]; int TextureNumComps; + bool EnableFlexibleBoundary; bool MirrorDisplay; + bool Grid3DVisibility; + bool Grid2DVisibility; private: vtkMRMLLiverResectionNode(const vtkMRMLLiverResectionNode&); diff --git a/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManager2D.h b/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManager2D.h index 5b3503c..6eb2762 100644 --- a/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManager2D.h +++ b/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManager2D.h @@ -80,7 +80,7 @@ vtkMRMLLiverResectionsDisplayableManager2D // Description: // VTK-specific function for print out information - void PrintSelf(ostream& os, vtkIndent indent); + void PrintSelf(ostream& os, vtkIndent indent) override; protected: @@ -92,12 +92,12 @@ vtkMRMLLiverResectionsDisplayableManager2D // Description: // MRML virtual functions - virtual void SetMRMLSceneInternal(vtkMRMLScene *newScene); - virtual void ProcessMRMLNodesEvents(vtkObject *caller, unsigned long event, void *callData); - virtual void OnMRMLSceneNodeAdded(vtkMRMLNode *node); - virtual void OnMRMLNodeModified(vtkMRMLNode *node); - virtual void OnMRMLSceneNodeRemoved(vtkMRMLNode *node); - virtual void OnMRMLSceneEndClose(); + virtual void SetMRMLSceneInternal(vtkMRMLScene *newScene) override; + virtual void ProcessMRMLNodesEvents(vtkObject *caller, unsigned long event, void *callData) override; + virtual void OnMRMLSceneNodeAdded(vtkMRMLNode *node) override; + virtual void OnMRMLNodeModified(vtkMRMLNode *node) override; + virtual void OnMRMLSceneNodeRemoved(vtkMRMLNode *node) override; + virtual void OnMRMLSceneEndClose() override; protected: @@ -120,4 +120,4 @@ vtkMRMLLiverResectionsDisplayableManager2D }; -#endif \ No newline at end of file +#endif diff --git a/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.cpp b/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.cpp index cc17e5e..0149a12 100644 --- a/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.cpp +++ b/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.cpp @@ -87,9 +87,9 @@ vtkMRMLLiverResectionsDisplayableManagerHelper2D:: //--------------------------------------------------------------------------- void vtkMRMLLiverResectionsDisplayableManagerHelper2D:: PrintSelf(ostream &os, - vtkIndent vtkNotUsed(indent)) + vtkIndent indent) { - + this->vtkObject::PrintSelf(os, indent); } //--------------------------------------------------------------------------- diff --git a/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.h b/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.h index 3bb8cc1..d29c104 100644 --- a/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.h +++ b/LiverResections/MRMLDM/vtkMRMLLiverResectionsDisplayableManagerHelper2D.h @@ -80,7 +80,7 @@ vtkMRMLLiverResectionsDisplayableManagerHelper2D // Description: // VTK-specific function for printing information - void PrintSelf(ostream &os, vtkIndent indent); + void PrintSelf(ostream &os, vtkIndent indent) override; // Description: // Add surface contour to the 2D views diff --git a/LiverSegments/CMakeLists.txt b/LiverSegments/CMakeLists.txt index 095c897..2aac743 100644 --- a/LiverSegments/CMakeLists.txt +++ b/LiverSegments/CMakeLists.txt @@ -24,6 +24,29 @@ slicerMacroBuildScriptedModule( WITH_GENERIC_TESTS ) + +# #----------------------------------------------------------------------------- +# set(MODULE_EXPORT_DIRECTIVE "Q_SLICER_QTMODULES_${MODULE_NAME_UPPER}_EXPORT") + +# set(MODULE_INCLUDE_DIRECTORIES +# ${CMAKE_CURRENT_SOURCE_DIR}/Logic +# ${CMAKE_CURRENT_BINARY_DIR}/Logic +# ) + +# #----------------------------------------------------------------------------- +# slicerMacroBuildLoadableModule( +# NAME ${MODULE_NAME} +# TITLE ${MODULE_TITLE} +# EXPORT_DIRECTIVE ${MODULE_EXPORT_DIRECTIVE} +# INCLUDE_DIRECTORIES ${MODULE_INCLUDE_DIRECTORIES} +# SRCS ${MODULE_SRCS} +# MOC_SRCS ${MODULE_MOC_SRCS} +# UI_SRCS ${MODULE_UI_SRCS} +# TARGET_LIBRARIES ${MODULE_TARGET_LIBRARIES} +# RESOURCES ${MODULE_RESOURCES} +# # WITH_GENERIC_TESTS +# ) + #----------------------------------------------------------------------------- if(BUILD_TESTING) diff --git a/LiverSegments/LiverSegments.py b/LiverSegments/LiverSegments.py index 6a1716f..dab1b86 100644 --- a/LiverSegments/LiverSegments.py +++ b/LiverSegments/LiverSegments.py @@ -90,6 +90,7 @@ def __init__(self, parent=None): self.logic = None self._parameterNode = None self._updatingGUIFromParameterNode = False + self._updatingGUIFromSegmentationNode = False ScriptedLoadableModuleWidget.__init__(self, parent) VTKObservationMixin.__init__(self) # needed for parameter node observation @@ -112,6 +113,7 @@ def setup(self): self.nodeSelectors = [ (self.ui.inputSurfaceSelector, "InputSurface"), (self.ui.endPointsMarkupsSelector, "CenterlineSegment"), + (self.ui.selectedVascularTerritorySegmId, "VascularTerritorySegmentation") ] # Set scene in MRML widgets. Make sure that in Qt designer @@ -138,11 +140,11 @@ def setup(self): #self.ui.endPointsMarkupsSelector.connect('nodeAdded(vtkMRMLNode*)', self.newEndpointsListCreated) self.ui.inputSurfaceSelector.connect('currentNodeChanged(bool)', self.segmentationNodeSelected) self.ui.vascularTerritoryId.connect('currentIndexChanged(int)', self.onVascularTerritoryIdChanged) + self.ui.selectedVascularTerritorySegmId.connect('currentNodeChanged(bool)', self.updateParameterNodeFromGUI) self.ui.selectedVascularTerritorySegmId.connect('currentNodeChanged(bool)', self.vascular_territory_segmentationNodeSelected) self.ui.selectedVascularTerritorySegmId.setNodeTypeLabel('Vascular Territory Segmentation', 'vtkMRMLSegmentationNode') self.ui.selectedVascularTerritorySegmId.addAttribute("vtkMRMLSegmentationNode", "LiverSegments.SegmentationId") - self.ui.endPointsMarkupsSelector.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.SegmentationId") #self.onVascularTerritoryIdChanged() #self.ui.endPointsMarkupsSelector.setEnabled(False)#Disable selector for now, as the lists are automatically managed @@ -161,18 +163,18 @@ def setup(self): # Buttons self.ui.calculateVascularTerritoryMapButton.connect('clicked(bool)', self.onCalculateVascularTerritoryMapButton) - self.ui.addSegmentButton.connect('clicked(bool)', self.onAddCenterlineButton) + self.ui.addCenterlineSegmentButton.connect('clicked(bool)', self.onAddCenterlineButton) self.ui.addSegmentationButton.connect('clicked(bool)', self.onAddSegmentationButton) self.ui.ColorPickerButton.connect('colorChanged(QColor)', self.onColorChanged) self.ui.showHideButton.connect('clicked(bool)', self.onShowHideButton) - self.enableWidgetButtons(False) + #self.enableWidgetButtons(False) # Make sure parameter node is initialized (needed for module reload) self.initializeParameterNode() def enableWidgetButtons(self, state): self.ui.addSegmentationButton.setEnabled(state) - self.ui.addSegmentButton.setEnabled(state) + self.ui.addCenterlineSegmentButton.setEnabled(state) self.ui.calculateVascularTerritoryMapButton.setEnabled(state) self.ui.inputSurfaceSelector.setEnabled(state) self.ui.vascularTerritoryId.setEnabled(state) @@ -180,7 +182,6 @@ def enableWidgetButtons(self, state): self.ui.showHideButton.setEnabled(state) def segmentationNodeSelected(self): - print('segmentationNodeSelected()') self.ui.SegmentationShow3DButton.setEnabled(True) segmentationNode = self.ui.inputSurfaceSelector.currentNode() self.ui.SegmentationShow3DButton.setSegmentationNode(segmentationNode) @@ -191,7 +192,7 @@ def segmentationNodeSelected(self): displayNode.SetOpacity3D(0.3) self.updateShowHideButtonText() - #Auto create if name/id don't exist. Auto switch it it exists + #Auto create if name/id don't exist. Auto switch if it exists def onSegmentChanged(self): endPointsMarkupsNode = self.ui.endPointsMarkupsSelector.currentNode() if endPointsMarkupsNode is not None: @@ -200,20 +201,29 @@ def onSegmentChanged(self): return if not self.ui.inputSegmentSelectorWidget.currentSegmentID(): return - Idno = self.ui.selectedVascularTerritorySegmId.nodeCount() + #Check if this is a Vascular Territory Segmentation node + segmentationNodeAttribute = self.ui.inputSurfaceSelector.currentNode().GetAttribute("LiverSegments.SegmentationId") + if segmentationNodeAttribute is not None: + return + + VascSegmIdno = self.ui.selectedVascularTerritorySegmId.currentNode().GetAttribute("LiverSegments.SegmentationId") + VascTerrIdno = self.ui.vascularTerritoryId.currentIndex vesselPointsSelector = self.ui.endPointsMarkupsSelector -# vesselPointsSelector.SetAttribute("LiverSegments.SegmentationId",str(Idno)) + vesselPointsSelector.blockSignals(True) + vesselPointsSelector.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.VascTerrId", str(VascTerrIdno)) + vesselPointsSelector.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.SegmentationId", str(VascSegmIdno)) + vesselPointsSelector.blockSignals(False) endPointsMarkupsNode = self.getVesselSegmentfromName() if endPointsMarkupsNode is None: endPointsMarkupsNode = self.ui.endPointsMarkupsSelector.addNode() - endPointsMarkupsNode.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.SegmentationId",str(Idno)) self.ui.endPointsMarkupsSelector.setCurrentNode(endPointsMarkupsNode) - else: - endPointsMarkupsNode.SetAttribute("LiverSegments.SegmentationId",str(Idno)) +# else: + endPointsMarkupsNode.SetAttribute("LiverSegments.SegmentationId",str(VascSegmIdno)) + endPointsMarkupsNode.SetAttribute("LiverSegments.VascTerrId",str(VascTerrIdno)) +# endPointsMarkupsNode.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.SegmentationId",str(Idno)) self.ui.endPointsMarkupsSelector.baseName = self.getVesselSegmentName() - logging.info('currentNode: ' + self.ui.endPointsMarkupsSelector.currentNode().GetName()) self.refreshShowHideButton() endPointsMarkupsNode.SetDisplayVisibility(True)#Show current markup points @@ -252,23 +262,33 @@ def updateShowHideButtonText(self): self.ui.showHideButton.setIcon(qt.QIcon("Icons/VisibleOff.png")) def vascular_territory_segmentationNodeSelected(self): - Idno = self.ui.selectedVascularTerritorySegmId.nodeCount() - print('vascular_territory_segmentationNodeSelected(',Idno,')') - if Idno <= 0: + self._updatingGUIFromSegmentationNode = True + count = self.ui.selectedVascularTerritorySegmId.nodeCount() + if count <= 0: self.enableWidgetButtons(False) + self._updatingGUIFromSegmentationNode = False return else: self.enableWidgetButtons(True) - vasc_terr_segmentationNode = self.ui.selectedVascularTerritorySegmId.currentNode() - segmentationNodeName = vasc_terr_segmentationNode.GetName() - vasc_terr_segmentationNode.SetAttribute("LiverSegments.SegmentationId",str(Idno)) + segmId = self.ui.selectedVascularTerritorySegmId.currentNode().GetAttribute("LiverSegments.SegmentationId") - vasc_terr_ID_combox = self.ui.vascularTerritoryId + vasc_terr_segmentationNode = self.ui.selectedVascularTerritorySegmId.currentNode() + vascularTerrSegm = vasc_terr_segmentationNode.GetSegmentation() if vasc_terr_segmentationNode is None: logging.warning('No vascular territory segmentationNode') + self._updatingGUIFromSegmentationNode = False return + if not segmId: + segmId = count + firstSegmentID = 'Vascular Territory ID 1' + vascularTerrSegm.AddEmptySegment(firstSegmentID, firstSegmentID) + + vasc_terr_segmentationNode.SetAttribute("LiverSegments.SegmentationId", str(segmId)) + + segmentationNodeName = vasc_terr_segmentationNode.GetName() + vasc_terr_ID_combox = self.ui.vascularTerritoryId if 'Vascular_Territory_Segmentation' in segmentationNodeName: self.enableWidgetButtons(True) @@ -276,13 +296,33 @@ def vascular_territory_segmentationNodeSelected(self): self.enableWidgetButtons(False) self.updateVascTerrList(vasc_terr_ID_combox, vasc_terr_segmentationNode) + self.ui.vascularTerritoryId.setCurrentIndex(1) displayNode = vasc_terr_segmentationNode.GetDisplayNode() if displayNode: displayNode.SetOpacity3D(0.3) self.updateShowHideButtonText() + # Visualisation of centerline segments + centerlineSegments = slicer.util.getNodesByClass('vtkMRMLModelNode') + for centerlineSegment in centerlineSegments: + SegmIdAttribute = centerlineSegment.GetAttribute("LiverSegments.SegmentationId") + if SegmIdAttribute == segmId: + centerlineSegment.GetDisplayNode().VisibilityOn() + else: + centerlineSegment.GetDisplayNode().VisibilityOff() + # Visualisation of Vascular Territories + segmentationNodes = slicer.util.getNodesByClass('vtkMRMLSegmentationNode') + self._updatingGUIFromSegmentationNode = False + for node in segmentationNodes: + attribute = node.GetAttribute("LiverSegments.SegmentationId") + if attribute != None: + if node.GetDisplayNode(): + node.GetDisplayNode().SetAllSegmentsVisibility(False) + self.ui.endPointsMarkupsPlaceWidget.setPlaceModeEnabled(False) + def updateVascTerrList(self, vasc_terr_ID_list, vascular_territory_segm_node): segments = vascular_territory_segm_node.GetSegmentation().GetSegmentIDs() + vasc_terr_ID_list.blockSignals(True) vasc_terr_ID_list.clear() initString = 'Create new territory ID' vasc_terr_ID_list.addItem(initString) @@ -291,13 +331,12 @@ def updateVascTerrList(self, vasc_terr_ID_list, vascular_territory_segm_node): # No vascular territory segmentations return #Start populating Vascular Territory list - - vasc_terr_ID_list.blockSignals(True) index = 0 for idString in segments: index = index+1 vasc_terr_ID_list.addItem(idString) - self.colormap.SetColorName(index, vasc_terr_ID_list.currentText) + self.colormap.SetColorName(index, idString) + vasc_terr_ID_list.setCurrentIndex(index) self.onSegmentChanged() vasc_terr_ID_list.setCurrentIndex(1) vasc_terr_ID_list.blockSignals(False) @@ -372,7 +411,6 @@ def setParameterNode(self, inputParameterNode): Set and observe parameter node. Observation is needed because when the parameter node is changed then the GUI must be updated immediately. """ - if inputParameterNode: self.logic.setDefaultParameters(inputParameterNode) @@ -418,6 +456,9 @@ def updateGUIFromParameterNode(self, caller=None, event=None): inputSurfaceNode = self._parameterNode.GetNodeReference("InputSurface") if inputSurfaceNode and inputSurfaceNode.IsA("vtkMRMLSegmentationNode"): self.ui.inputSegmentSelectorWidget.setCurrentSegmentID(self._parameterNode.GetParameter("InputSegmentID")) + vascularTerritorySegmNode = self._parameterNode.GetNodeReference("VascularTerritorySegmentation") + if vascularTerritorySegmNode and vascularTerritorySegmNode.IsA("vtkMRMLSegmentationNode"): + self.enableWidgetButtons(True) # All the GUI updates are done self._updatingGUIFromParameterNode = False @@ -452,8 +493,7 @@ def getPreprocessedPolyData(self): surface = self.ui.inputSurfaceSelector.currentNode() segmentId = self.ui.inputSegmentSelectorWidget.currentSegmentID() - centerlineProcessingLogic = self.logic.getCenterlineLogic() - inputSurfacePolyData = centerlineProcessingLogic.polyDataFromNode(surface, segmentId) + inputSurfacePolyData = self.logic.polyDataFromNode(surface, segmentId) if not inputSurfacePolyData or inputSurfacePolyData.GetNumberOfPoints() == 0: raise ValueError("Valid input surface is required") @@ -476,7 +516,6 @@ def createCenterlineNode(self, endPointsMarkupsNode): return centerlineModelNode def getVesselSegmentName(self): - print('getVesselSegmentName()') segmentation = self.ui.inputSegmentSelectorWidget.currentNode().GetSegmentation() segmId = self.ui.inputSegmentSelectorWidget.currentSegmentID() segment = segmentation.GetSegment(segmId) @@ -485,7 +524,6 @@ def getVesselSegmentName(self): return name def newEndpointsListCreated(self): - print('newEndpointsListCreated()') #Set baseName, and use this to create new unique names if endPointsMarkupsNode with this name already exist newName = self.getVesselSegmentName() self.updateSelectorColor() @@ -522,18 +560,40 @@ def useColorFromSelector(self, centerlineModelNode): def onVascularTerritoryIdChanged(self): index = self.ui.vascularTerritoryId.currentIndex + vascularTerrSegmNode = self.ui.selectedVascularTerritorySegmId.currentNode() + VascSegmIdno = vascularTerrSegmNode.GetAttribute("LiverSegments.SegmentationId") + # If the GUI is updating - No action + if self._updatingGUIFromSegmentationNode == True: + return #Add new vascular territory ID if(index == 0): numItems = self.ui.vascularTerritoryId.count idString = "Vascular Territory ID " + str(numItems) self.ui.vascularTerritoryId.addItem(idString) self.ui.vascularTerritoryId.setCurrentIndex(numItems) + self.ui.endPointsMarkupsPlaceWidget.setPlaceModeEnabled(True) + else: + self.ui.endPointsMarkupsPlaceWidget.setPlaceModeEnabled(False) + #Add new Vascular Territory Segmentation + segmentName = self.ui.vascularTerritoryId.currentText + vascularTerrSegm = vascularTerrSegmNode.GetSegmentation() + numberOfSegments = vascularTerrSegm.GetNumberOfSegments() + if numberOfSegments < index: + vascularTerrSegm.AddEmptySegment(segmentName, segmentName) + #Update color in selector self.ui.ColorPickerButton.setColor(self.getCurrentColorQt()) if(index > 0): self.colormap.SetColorName(index, self.ui.vascularTerritoryId.currentText) self.onSegmentChanged()#Also generate new vessel segment point lists when changing territory id + index = self.ui.vascularTerritoryId.currentIndex + vesselPointsSelector = self.ui.endPointsMarkupsSelector + vesselPointsSelector.blockSignals(True) + vesselPointsSelector.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.VascTerrId", str(index)) + vesselPointsSelector.addAttribute("vtkMRMLMarkupsFiducialNode", "LiverSegments.SegmentationId", str(VascSegmIdno)) + vesselPointsSelector.blockSignals(False) + def onColorChanged(self): colorIndex = self.ui.vascularTerritoryId.currentIndex color = self.ui.ColorPickerButton.color @@ -541,19 +601,22 @@ def onColorChanged(self): self.colormap.SetColor(colorIndex, color.redF(), color.greenF(), color.blueF()) #Update index color in colormap. def onAddCenterlineButton(self): - self.onAddCenterline() + self.onAddCenterlineSegment() def onAddSegmentationButton(self): - self.onAddCenterline(addSegmentationInsteadOfLine = True) + self.onAddCenterlineSegment(addSegmentationInsteadOfLine = True) - def onAddCenterline(self, addSegmentationInsteadOfLine = False): + def onAddCenterlineSegment(self, addSegmentationInsteadOfLine = False): if not (self.logic.check_module_Extract_Centerline_installed()): self.ui.endPointsMarkupsPlaceWidget.setPlaceModeEnabled(False) slicer.util.errorDisplay("SlicerVMTK Extension not installed") return endPointsMarkupsNode = self.ui.endPointsMarkupsSelector.currentNode() self.ui.endPointsMarkupsPlaceWidget.setPlaceModeEnabled(False) - endPointsMarkupsNode.SetAttribute("SegmentIndex", str(self.ui.vascularTerritoryId.currentIndex)) + endPointsMarkupsNode.SetAttribute("LiverSegments.VascTerrId", str(self.ui.vascularTerritoryId.currentIndex)) + vascularTerritorySegm = self.ui.selectedVascularTerritorySegmId.currentNode() + vascularTerritorySegmId = vascularTerritorySegm.GetAttribute("LiverSegments.SegmentationId") + endPointsMarkupsNode.SetAttribute("LiverSegments.SegmentationId", str(vascularTerritorySegmId)) slicer.app.pauseRender() qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) @@ -581,6 +644,8 @@ def onAddCenterline(self, addSegmentationInsteadOfLine = False): mergedLines = self.mergePolydata(centerlineModelNode.GetMesh(), decimatedCenterlinePolyData) centerlineModelNode.SetAndObserveMesh(mergedLines) + centerlineModelNode.SetAttribute("LiverSegments.SegmentationId", str(vascularTerritorySegmId)) + centerlineModelNode.SetAttribute("LiverSegments.VascTerrId", str(self.ui.vascularTerritoryId.currentIndex)) centerlineModelNode.CreateDefaultDisplayNodes() self.useColorFromSelector(centerlineModelNode) @@ -605,20 +670,26 @@ def onCalculateVascularTerritoryMapButton(self): startTime = time.time() segmentationNode = self.ui.inputSurfaceSelector.currentNode() - centerlineModel = self.logic.build_centerline_model(self.colormap) + vascTerrSegmentationId = int(self.ui.selectedVascularTerritorySegmId.currentNode().GetAttribute("LiverSegments.SegmentationId")) + centerlineModel = self.logic.build_centerline_model(self.colormap, vascTerrSegmentationId) + centerlineModelPoints = centerlineModel.GetMesh() + numberOfPoints = centerlineModelPoints.GetNumberOfPoints() refVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode") - if not (refVolumeNode): + if not (refVolumeNode) or (numberOfPoints<2): raise ValueError("Missing inputs to calculate vascular segments") slicer.app.pauseRender() qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) vascularTerritorySegmentationNode = self.ui.selectedVascularTerritorySegmId.currentNode() + segmId = vascularTerritorySegmentationNode.GetAttribute("LiverSegments.SegmentationId") + centerlineModel.SetAttribute("LiverSegments.SegmentationId", segmId) try: - self.logic.calculateVascularTerritoryMap(vascularTerritorySegmentationNode, refVolumeNode, segmentationNode, centerlineModel, self.colormap) + self.logic.calculateVascularTerritoryMap(vascularTerritorySegmentationNode, refVolumeNode, segmentationNode, centerlineModel, self.colormap) except ValueError: logging.error("Error: Failing when calculating vascular segments") + slicer.app.resumeRender() qt.QApplication.restoreOverrideCursor() @@ -643,6 +714,7 @@ def __init__(self): from vtkSlicerLiverSegmentsModuleLogicPython import vtkLiverSegmentsLogic # Create the segmentsclassification logic self.scl = vtkLiverSegmentsLogic() + self.scl.SetMRMLScene(slicer.mrmlScene) def check_module_Extract_Centerline_installed(self): module_name = 'ExtractCenterline' @@ -685,89 +757,29 @@ def createCompleteCenterlineModel(self, colormap): return completeCenterlineModelNode - def build_centerline_model(self, colormap): + def build_centerline_model(self, colormap, vascSegmSelected): centerlineModel = self.createCompleteCenterlineModel(colormap) - centerlineSegmentsDict = slicer.util.getNodes("Territory*") + centerlineSegmentsDict = slicer.util.getNodes("*Territory*") for name, segmentObject in centerlineSegmentsDict.items(): if segmentObject.GetClassName() == "vtkMRMLModelNode": - segmentId = int(segmentObject.GetAttribute("SegmentIndex")) - self.scl.MarkSegmentWithID(segmentObject, segmentId) - self.scl.AddSegmentToCenterlineModel(centerlineModel, segmentObject) + VascTerrId = int(segmentObject.GetAttribute("LiverSegments.VascTerrId")) + VascTerrSegmId = int(segmentObject.GetAttribute("LiverSegments.SegmentationId")) + if VascTerrSegmId == vascSegmSelected: + self.scl.MarkSegmentWithID(segmentObject, VascTerrId) + self.scl.AddSegmentToCenterlineModel(centerlineModel, segmentObject) self.scl.InitializeCenterlineSearchModel(centerlineModel) return centerlineModel def calculateVascularTerritoryMap(self, vascularTerritorySegmentationNode, refVolume, segmentation, centerlineModel, colormap): - segmentationIds = vtk.vtkStringArray() - labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode") - - # Get voxels tagged as liver - segmentId = segmentation.GetSegmentation().GetSegmentIdBySegmentName('liver') - #Check metadata for segmentation - segm = segmentation.GetSegmentation() - numberOfSegments = segm.GetNumberOfSegments() - logging.info('Number of segments: ' + str(numberOfSegments)) - liverSegm = segm.GetSegment(segmentId) - if liverSegm is not None: - logging.info('Segment name: ' + liverSegm.GetName()) - logging.info('Segment Label: ' + str(liverSegm.GetLabelValue())) - - segmentationIds.InsertNextValue(segmentId) - slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentation, segmentationIds, labelmapVolumeNode, refVolume) - - result = self.scl.SegmentClassificationProcessing(centerlineModel, labelmapVolumeNode) - if result==0: - logging.error("Corrupt centerline model - Not possible to calculate vascular segments") - - labelmapVolumeNode.GetDisplayNode().SetAndObserveColorNodeID(colormap.GetID()) - slicer.util.arrayFromVolumeModified(labelmapVolumeNode) - vascularTerritorySegmentationNode.Reset(None)#Existing node will be overwritten - - #Create segmentation from labelmap volume - vascularTerritorySegmentationNode.CreateDefaultDisplayNodes() # only needed for display - slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, vascularTerritorySegmentationNode) - vascularTerritorySegmentationNode.CreateClosedSurfaceRepresentation() - slicer.mrmlScene.RemoveNode(labelmapVolumeNode) + self.scl.calculateVascularTerritoryMap(vascularTerritorySegmentationNode, refVolume, segmentation, centerlineModel, colormap) def copyIndex(self, endPointsMarkupsNode, centerlineModelNode): - centerlineModelNode.SetAttribute("SegmentIndex", endPointsMarkupsNode.GetAttribute("SegmentIndex")) + centerlineModelNode.SetAttribute("LiverSegments.VascTerrId", endPointsMarkupsNode.GetAttribute("LiverSegments.VascTerrId")) - #Using code from centerlineProcessingLogic.preprocess def preprocessAndDecimate(self, surfacePolyData): - parameters = {} - inputSurfaceModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "tempInputSurfaceModel") - inputSurfaceModelNode.SetAndObserveMesh(surfacePolyData) - parameters["inputModel"] = inputSurfaceModelNode - outputSurfaceModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "tempDecimatedSurfaceModel") - parameters["outputModel"] = outputSurfaceModelNode - parameters["reductionFactor"] = 0.8 - parameters["method"] = "FastQuadric" - parameters["aggressiveness"] = 4 - decimation = slicer.modules.decimation - cliNode = slicer.cli.runSync(decimation, None, parameters) - surfacePolyData = outputSurfaceModelNode.GetPolyData() - slicer.mrmlScene.RemoveNode(inputSurfaceModelNode) - slicer.mrmlScene.RemoveNode(outputSurfaceModelNode) - slicer.mrmlScene.RemoveNode(cliNode) - - surfaceCleaner = vtk.vtkCleanPolyData() - surfaceCleaner.SetInputData(surfacePolyData) - surfaceCleaner.Update() - - surfaceTriangulator = vtk.vtkTriangleFilter() - surfaceTriangulator.SetInputData(surfaceCleaner.GetOutput()) - surfaceTriangulator.PassLinesOff() - surfaceTriangulator.PassVertsOff() - surfaceTriangulator.Update() - - normals = vtk.vtkPolyDataNormals() - normals.SetInputData(surfaceTriangulator.GetOutput()) - normals.SetAutoOrientNormals(1) - normals.SetFlipNormals(0) - normals.SetConsistency(1) - normals.SplittingOff() - normals.Update() - - return normals.GetOutput() + processedPolyData = vtk.vtkPolyData() + self.scl.preprocessAndDecimate(surfacePolyData, processedPolyData) + return processedPolyData def decimateLine(self, polyDataLine): decimate = vtk.vtkDecimatePolylineFilter() @@ -776,6 +788,23 @@ def decimateLine(self, polyDataLine): decimate.Update() return decimate.GetOutput() + #Using code from SlicerExtension-VMTK + #https://github.com/vmtk/SlicerExtension-VMTK/blob/master/ExtractCenterline/ExtractCenterline.py + def polyDataFromNode(self, surfaceNode, segmentId): + if not surfaceNode: + logging.error("Invalid input surface node") + return None + if surfaceNode.IsA("vtkMRMLModelNode"): + return surfaceNode.GetPolyData() + elif surfaceNode.IsA("vtkMRMLSegmentationNode"): + # Segmentation node + polyData = vtk.vtkPolyData() + surfaceNode.CreateClosedSurfaceRepresentation() + surfaceNode.GetClosedSurfaceRepresentation(segmentId, polyData) + return polyData + else: + logging.error + class LiverSegmentsTest(ScriptedLoadableModuleTest): """ This is the test case for your scripted module. diff --git a/LiverSegments/Logic/CMakeLists.txt b/LiverSegments/Logic/CMakeLists.txt index 5c63e1a..7ca454f 100644 --- a/LiverSegments/Logic/CMakeLists.txt +++ b/LiverSegments/Logic/CMakeLists.txt @@ -17,6 +17,8 @@ set(${KIT}_TARGET_LIBRARIES ${VTK_LIBRARIES} ${ITK_LIBRARIES} MRMLCore +# MRMLLogic + vtkSlicerSegmentationsModuleLogic ) #--------------------------------- @@ -27,3 +29,7 @@ SlicerMacroBuildModuleLogic( SRCS ${${KIT}_SRCS} TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} ) + +if(BUILD_TESTING) + add_subdirectory(Testing) +endif() diff --git a/LiverSegments/Logic/Testing/CMakeLists.txt b/LiverSegments/Logic/Testing/CMakeLists.txt new file mode 100644 index 0000000..35f9732 --- /dev/null +++ b/LiverSegments/Logic/Testing/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Cxx) diff --git a/LiverSegments/Logic/Testing/Cxx/CMakeLists.txt b/LiverSegments/Logic/Testing/Cxx/CMakeLists.txt new file mode 100644 index 0000000..f750e60 --- /dev/null +++ b/LiverSegments/Logic/Testing/Cxx/CMakeLists.txt @@ -0,0 +1,57 @@ +# set(KIT qSlicer${MODULE_NAME}Module) +set(KIT ${PROJECT_NAME}) + +#----------------------------------------------------------------------------- +# set(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/../") +# set(TEMP "${Slicer_BINARY_DIR}/Testing/Temporary") + +#----------------------------------------------------------------------------- +set(KIT_TEST_SRCS + vtkSlicerLiverSegmentsLogicTest1.cxx + ) + +#----------------------------------------------------------------------------- +slicerMacroConfigureModuleCxxTestDriver( + NAME ${KIT} + SOURCES ${KIT_TEST_SRCS} + TARGET_LIBRARIES + ${SlicerExecutionModel_EXTRA_EXECUTABLE_TARGET_LIBRARIES} + WITH_VTK_DEBUG_LEAKS_CHECK + WITH_VTK_ERROR_OUTPUT_CHECK + ) + +# #----------------------------------------------------------------------------- +# slicerMacroConfigureModuleCxxTestDriver( +# NAME ${KIT} +# SOURCES ${KIT_TEST_SRCS} +# # INCLUDE_DIRECTORIES +# # ${qSlicerLiverSegmentsModule_INCLUDE_DIRS} +# # ${CMAKE_CURRENT_SOURCE_DIR}/../../Logic/ +# # ${qSlicerLiverSegmentsModuleLogic_INCLUDE_DIRS} +# # ${vtkSlicerLiverSegmentsModuleLogic_INCLUDE_DIRS} +# # ${CMAKE_CURRENT_SOURCE_DIR}/../../ +# TARGET_LIBRARIES +# # ${VTK_LIBRARIES} +# ${MRML_LIBRARIES} +# # qSlicerLiverSegmentsModule +# # vtkSlicerLiverSegmentsModuleLogic +# # qSlicer${MODULE_NAME}ModuleLogic +# TESTS_TO_RUN_VAR KIT_TESTS_TO_RUN +# WITH_VTK_DEBUG_LEAKS_CHECK +# WITH_VTK_ERROR_OUTPUT_CHECK +# ) + +#----------------------------------------------------------------------------- +# Add all the tests +# foreach(test ${KIT_TESTS_TO_RUN}) +# get_filename_component(testname ${test} NAME_WE) +# simple_test(${testname} +# -D ${INPUT} +# -T ${TEMP} +# #-V Baseline/${testname}.png +# ) +# endforeach() + +#----------------------------------------------------------------------------- +simple_test(vtkSlicerLiverSegmentsLogicTest1) + diff --git a/LiverSegments/Logic/Testing/Cxx/vtkSlicerLiverSegmentsLogicTest1.cxx b/LiverSegments/Logic/Testing/Cxx/vtkSlicerLiverSegmentsLogicTest1.cxx new file mode 100644 index 0000000..bb332ab --- /dev/null +++ b/LiverSegments/Logic/Testing/Cxx/vtkSlicerLiverSegmentsLogicTest1.cxx @@ -0,0 +1,155 @@ +/*============================================================================== + + Distributed under the OSI-approved BSD 3-Clause License. + + Copyright (c) Oslo University Hospital. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Oslo University Hospital nor the names + of Contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + This file was originally developed by Ole Vegard Solberg (SINTEF, Norway) + and was supported by The Research Council of Norway + through the ALive project (grant nr. 311393). + +==============================================================================*/ + +//Using vtkSlicerColorLogicTest1 as example + +// MRMLLogic includes +#include "vtkLiverSegmentsLogic.h" + +// MRML includes +#include "vtkMRMLCoreTestingMacros.h" +#include "vtkMRMLScene.h" +#include + +// VTKSlicer includes +#include + +// VTK includes +#include "qMRMLWidget.h" +#include +#include +#include +#include +#include + + +// LiverSegments includes +// #include "qSlicerLiverSegmentsModule.h" //test to try to link to qSlicerLiverSegmentsModule + +// STD includes + +using namespace vtkAddonTestingUtilities; +using namespace vtkMRMLCoreTestingUtilities; + +//---------------------------------------------------------------------------- +namespace +{ +int TestDefaults(); +int TestFunctionsWithNullInput(); +int TestFunctionsWithDummyData(); +} + +int vtkSlicerLiverSegmentsLogicTest1(int vtkNotUsed(argc), char * vtkNotUsed(argv)[]) +{ + CHECK_EXIT_SUCCESS(TestDefaults()); + CHECK_EXIT_SUCCESS(TestFunctionsWithNullInput()); + CHECK_EXIT_SUCCESS(TestFunctionsWithDummyData()); + return EXIT_SUCCESS; +} +namespace +{ + +//---------------------------------------------------------------------------- +int TestDefaults() +{ + vtkLiverSegmentsLogic* liverSegmentsLogic = vtkLiverSegmentsLogic::New(); + liverSegmentsLogic->Delete(); + return EXIT_SUCCESS; +} + +int TestFunctionsWithNullInput() +{ + vtkLiverSegmentsLogic* liverSegmentsLogic = vtkLiverSegmentsLogic::New(); + + liverSegmentsLogic->MarkSegmentWithID(nullptr, 0); + liverSegmentsLogic->AddSegmentToCenterlineModel(nullptr, nullptr); + liverSegmentsLogic->SegmentClassificationProcessing(nullptr, nullptr); + liverSegmentsLogic->InitializeCenterlineSearchModel(nullptr); + + vtkNew scene; + liverSegmentsLogic->SetMRMLScene(scene); + liverSegmentsLogic->calculateVascularTerritoryMap(nullptr, nullptr, nullptr, nullptr, nullptr); + liverSegmentsLogic->preprocessAndDecimate(nullptr, nullptr); + + liverSegmentsLogic->Delete(); + return EXIT_SUCCESS; +} + +int TestFunctionsWithDummyData() +{ + vtkLiverSegmentsLogic* liverSegmentsLogic = vtkLiverSegmentsLogic::New(); + int segmentId = 1; + + //Init segment + vtkNew segment; + vtkNew source; + segment->SetPolyDataConnection(source->GetOutputPort()); + + //Create dummy data + vtkNew dummyImageData; + int size = 1; + dummyImageData->SetExtent(0, size, 0, size, 0, size); + dummyImageData->SetSpacing(1, 1, 1); + dummyImageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1); + vtkPolyData *spherePolydata = source->GetOutput(); + + //Init labelMap + vtkNew labelMap; + labelMap->SetAndObserveImageData(dummyImageData); + + liverSegmentsLogic->MarkSegmentWithID(segment, segmentId); + liverSegmentsLogic->AddSegmentToCenterlineModel(segment, segment); + liverSegmentsLogic->SegmentClassificationProcessing(segment, labelMap); + liverSegmentsLogic->InitializeCenterlineSearchModel(segment); + + vtkNew scene; + liverSegmentsLogic->SetMRMLScene(scene); + liverSegmentsLogic->calculateVascularTerritoryMap(nullptr, nullptr, nullptr, nullptr, nullptr); + + vtkSmartPointer returnPolyData = vtkSmartPointer::New(); + liverSegmentsLogic->preprocessAndDecimate(spherePolydata, returnPolyData); + CHECK_BOOL(returnPolyData->GetNumberOfPoints() < spherePolydata->GetNumberOfPoints(), true); + CHECK_BOOL(returnPolyData->GetNumberOfPolys() < spherePolydata->GetNumberOfPolys(), true); + + liverSegmentsLogic->Delete(); + + return EXIT_SUCCESS; +} + +} diff --git a/LiverSegments/Logic/vtkLiverSegmentsLogic.cxx b/LiverSegments/Logic/vtkLiverSegmentsLogic.cxx index f4a1bcf..764a8c4 100644 --- a/LiverSegments/Logic/vtkLiverSegmentsLogic.cxx +++ b/LiverSegments/Logic/vtkLiverSegmentsLogic.cxx @@ -42,6 +42,10 @@ #include #include #include +#include + +#include +#include #include #include @@ -54,6 +58,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -82,6 +91,13 @@ void vtkLiverSegmentsLogic::MarkSegmentWithID(vtkMRMLModelNode *segment, int seg { auto idArray = vtkSmartPointer::New(); idArray->SetName("segmentId"); + + if(!segment) //Allow function to run with nullptr as input + { + std::cout << "MarkSegmentWithID Error: No input" << std::endl; + return; + } + auto polydata = segment->GetPolyData(); int numberOfPoints = polydata->GetNumberOfPoints(); for(int i=0;iGetPolyData(); auto centerlineModel = summedCenterline->GetPolyData(); @@ -108,6 +129,13 @@ void vtkLiverSegmentsLogic::AddSegmentToCenterlineModel(vtkMRMLModelNode *summed int vtkLiverSegmentsLogic::SegmentClassificationProcessing(vtkMRMLModelNode *centerlineModel, vtkMRMLLabelMapVolumeNode *labelMap) { auto ijkToRas = vtkSmartPointer::New(); + + if(!centerlineModel || !labelMap) //Allow function to run with nullptr as input + { + std::cout << "SegmentClassificationProcessing Error: No input" << std::endl; + return 0; + } + labelMap->GetIJKToRASMatrix(ijkToRas); auto centerlinePolyData = centerlineModel->GetPolyData(); @@ -150,8 +178,13 @@ int vtkLiverSegmentsLogic::SegmentClassificationProcessing(vtkMRMLModelNode *cen void vtkLiverSegmentsLogic::InitializeCenterlineSearchModel(vtkMRMLModelNode *summedCenterline) { - auto centerlineModel = summedCenterline->GetPolyData(); this->Locator->Initialize(); + if(!summedCenterline) //Allow function to run with nullptr as input + { + std::cout << "InitializeCenterlineSearchModel Error: No input" << std::endl; + return; + } + auto centerlineModel = summedCenterline->GetPolyData(); this->Locator->SetDataSet(vtkPointSet::SafeDownCast(centerlineModel)); if(centerlineModel->GetPointData()->GetNumberOfArrays() > 0) this->Locator->BuildLocator(); @@ -159,4 +192,96 @@ void vtkLiverSegmentsLogic::InitializeCenterlineSearchModel(vtkMRMLModelNode *su std::cout << "Error: No PointData in centerline model" << std::endl; } +void vtkLiverSegmentsLogic::calculateVascularTerritoryMap(vtkMRMLSegmentationNode *vascularTerritorySegmentationNode, + vtkMRMLScalarVolumeNode *refVolume, + vtkMRMLSegmentationNode *segmentation, + vtkMRMLModelNode *centerlineModel, + vtkMRMLColorNode *colormap) +{ + vtkMRMLScene *mrmlScene = this->GetMRMLScene(); + + if (!mrmlScene) + vtkErrorMacro("Error in calculateVascularTerritoryMap: no valid MRML scene."); + + vtkMRMLNode *labelmapNode = mrmlScene->AddNewNodeByClass("vtkMRMLLabelMapVolumeNode"); + vtkMRMLLabelMapVolumeNode *labelmapVolumeNode = vtkMRMLLabelMapVolumeNode::SafeDownCast(labelmapNode); + auto segmentationIds = vtkSmartPointer::New(); + + if(!vascularTerritorySegmentationNode || !segmentation || !colormap) + { + std::cout << "calculateVascularTerritoryMap Error: No input" << std::endl; + return; + } + + //Get voxels tagged as liver + std::string segmentId = segmentation->GetSegmentation()->GetSegmentIdBySegmentName("liver"); + //Check metadata for segmentation + vtkSegmentation* segm = segmentation->GetSegmentation(); + int numberOfSegments = segm->GetNumberOfSegments(); + std::cout << "Liver segmentId: " << segmentId << " numberOfSegments: " << numberOfSegments << std::endl; + vtkSegment *liverSegm = segm->GetSegment(segmentId); + if(liverSegm) + std::cout << "Segment name: " << liverSegm->GetName() << " Segment label: " << liverSegm->GetLabelValue() << std::endl; + + segmentationIds->InsertNextValue(segmentId); + vtkSlicerSegmentationsModuleLogic::ExportSegmentsToLabelmapNode(segmentation, segmentationIds, labelmapVolumeNode, refVolume); + int result = SegmentClassificationProcessing(centerlineModel, labelmapVolumeNode); + if(result == 0) + vtkErrorMacro("Corrupt centerline model - Not possible to calculate vascular segments."); + labelmapVolumeNode->GetDisplayNode()->SetAndObserveColorNodeID(colormap->GetID()); + //slicer.util.arrayFromVolumeModified(labelmapVolumeNode) + labelmapVolumeNode->Modified();//Is this enough, or is more of the code in arrayFromVolumeModified needed? + const char * segmentationId = vascularTerritorySegmentationNode->GetAttribute("LiverSegments.SegmentationId"); + vascularTerritorySegmentationNode->Reset(nullptr); + vascularTerritorySegmentationNode->SetAttribute("LiverSegments.SegmentationId", segmentationId); + vascularTerritorySegmentationNode->CreateDefaultDisplayNodes(); // only needed for display + vtkSlicerSegmentationsModuleLogic::ImportLabelmapToSegmentationNode(labelmapVolumeNode, vascularTerritorySegmentationNode); + vascularTerritorySegmentationNode->CreateClosedSurfaceRepresentation(); + mrmlScene->RemoveNode(labelmapVolumeNode); +} + +void vtkLiverSegmentsLogic::preprocessAndDecimate(vtkPolyData *surfacePolyData, vtkPolyData *returnPolyData) +{ + vtkMRMLScene *mrmlScene = this->GetMRMLScene(); + if (!mrmlScene) + vtkErrorMacro("Error in preprocessAndDecimate: no valid MRML scene."); + if(!surfacePolyData + || (surfacePolyData->GetPointData()->GetNumberOfArrays() == 0 + && surfacePolyData->GetCellData()->GetNumberOfArrays() == 0) ) + { + std::cout << "preprocessAndDecimate Error: no input surfacePolyData." << std::endl; + return; + } + + vtkSmartPointer decimator = vtkSmartPointer::New(); + double decimationFactor = 0.8; + decimator->SetInputData(surfacePolyData); + decimator->SetFeatureAngle(60); + decimator->SplittingOff(); + decimator->PreserveTopologyOn(); + decimator->SetMaximumError(1); + + decimator->SetTargetReduction(decimationFactor); + decimator->Update(); + + vtkSmartPointer surfaceCleaner = vtkSmartPointer::New(); + surfaceCleaner->SetInputData(decimator->GetOutput()); + surfaceCleaner->Update(); + + vtkSmartPointer surfaceTriangulator = vtkSmartPointer::New(); + surfaceTriangulator->SetInputData(surfaceCleaner->GetOutput()); + surfaceTriangulator->PassLinesOff(); + surfaceTriangulator->PassVertsOff(); + surfaceTriangulator->Update(); + + vtkSmartPointer normals = vtkSmartPointer::New(); + normals->SetInputData(surfaceTriangulator->GetOutput()); + normals->SetAutoOrientNormals(1); + normals->SetFlipNormals(0); + normals->SetConsistency(1); + normals->SplittingOff(); + normals->Update(); + + returnPolyData->ShallowCopy(normals->GetOutput()); +} diff --git a/LiverSegments/Logic/vtkLiverSegmentsLogic.h b/LiverSegments/Logic/vtkLiverSegmentsLogic.h index b563164..1671346 100644 --- a/LiverSegments/Logic/vtkLiverSegmentsLogic.h +++ b/LiverSegments/Logic/vtkLiverSegmentsLogic.h @@ -43,6 +43,9 @@ #include "vtkSlicerLiverSegmentsModuleLogicExport.h" +// Slicer include +#include + #include #include @@ -51,10 +54,13 @@ class vtkKdTreePointLocator; class vtkMRMLLabelMapVolumeNode; class vtkMRMLSegmentationNode; class vtkMRMLModelNode; +class vtkMRMLColorNode; +class vtkMRMLScalarVolumeNode; +class vtkPolyData; class VTK_SLICER_LIVERSEGMENTS_MODULE_LOGIC_EXPORT -vtkLiverSegmentsLogic : public vtkObject +vtkLiverSegmentsLogic : public vtkSlicerModuleLogic { private: vtkSmartPointer Locator; @@ -69,6 +75,12 @@ vtkLiverSegmentsLogic : public vtkObject void AddSegmentToCenterlineModel(vtkMRMLModelNode *summedCenterline, vtkMRMLModelNode *segmentCenterline); int SegmentClassificationProcessing(vtkMRMLModelNode *centerlineModel, vtkMRMLLabelMapVolumeNode *labelMap); void InitializeCenterlineSearchModel(vtkMRMLModelNode *summedCenterline); + void calculateVascularTerritoryMap(vtkMRMLSegmentationNode *vascularTerritorySegmentationNode, + vtkMRMLScalarVolumeNode *refVolume, + vtkMRMLSegmentationNode *segmentation, + vtkMRMLModelNode *centerlineModel, + vtkMRMLColorNode *colormap); + void preprocessAndDecimate(vtkPolyData *surfacePolyData, vtkPolyData *returnPolyData); protected: vtkLiverSegmentsLogic(); diff --git a/LiverSegments/Resources/UI/LiverSegments.ui b/LiverSegments/Resources/UI/LiverSegments.ui index cf5de47..946c7d1 100644 --- a/LiverSegments/Resources/UI/LiverSegments.ui +++ b/LiverSegments/Resources/UI/LiverSegments.ui @@ -88,7 +88,7 @@ - + false diff --git a/LiverSegments/Testing/Python/LiverSegmentsTest.py b/LiverSegments/Testing/Python/LiverSegmentsTest.py index 580059f..1cf78af 100644 --- a/LiverSegments/Testing/Python/LiverSegmentsTest.py +++ b/LiverSegments/Testing/Python/LiverSegmentsTest.py @@ -20,18 +20,22 @@ def runTest(self): self.setUp() self.logicFunctionsWithEmptyParameters() self.downloadData() + self.vtkLogicFunctions() - def logicFunctionsWithEmptyParameters(self): + def initPythonLogic(self): logic = LiverSegmentsLogic() - logic.__init__() logic.getCenterlineLogic() logic.setDefaultParameters(logic.getParameterNode()) + return logic + + def logicFunctionsWithEmptyParameters(self): + logic = self.initPythonLogic() colormap = slicer.mrmlScene.GetNodeByID('vtkMRMLColorTableNodeLabels') logic.createCompleteCenterlineModel(colormap) - - centerlineModel = logic.build_centerline_model(colormap) + segmentationId = 1 + centerlineModel = logic.build_centerline_model(colormap, segmentationId) refVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode") segmentation = self.createEmptyvtkMRMLSegmentationNode() @@ -43,6 +47,24 @@ def logicFunctionsWithEmptyParameters(self): logic.preprocessAndDecimate(vtk.vtkPolyData()) logic.decimateLine(vtk.vtkPolyData()) + logic.polyDataFromNode(None, segmentationId) + + def vtkLogicFunctions(self): + from vtkSlicerLiverSegmentsModuleLogicPython import vtkLiverSegmentsLogic + vtkLogic = vtkLiverSegmentsLogic() + vtkLogic.SetMRMLScene(slicer.mrmlScene) + + segmentationVascular = self.createEmptyvtkMRMLSegmentationNode() + segmentation = self.createEmptyvtkMRMLSegmentationNode() + refVolume = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode") + colormap = slicer.mrmlScene.GetNodeByID('vtkMRMLColorTableNodeLabels') + + logic = self.initPythonLogic() + logic.createCompleteCenterlineModel(colormap) + centerlineModel = logic.build_centerline_model(colormap, 1) + vtkLogic.calculateVascularTerritoryMap(segmentationVascular, refVolume, segmentation, centerlineModel, colormap) + vtkLogic.preprocessAndDecimate(None, None) + vtkLogic.preprocessAndDecimate(vtk.vtkPolyData(), vtk.vtkPolyData()) def create2EmptyMarkupsFiducialNodes(self): emptyNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLMarkupsFiducialNode") @@ -62,6 +84,7 @@ def downloadData(self): aliveDataURL ='https://github.com/alive-research/aliveresearchtestingdata/releases/download/' downloads = { 'fileNames': '3D-IRCADb-01_08.nrrd', +# 'fileNames': 'LiverSegmentation000.seg.nrrd', 'loadFiles': True, #'uris': TESTING_DATA_URL + 'SHA256/2e25b8ce2c70cc2e1acd9b3356d0b1291b770274c16fcd0e2a5b69a4587fbf74', 'uris': aliveDataURL + 'SHA256/2e25b8ce2c70cc2e1acd9b3356d0b1291b770274c16fcd0e2a5b69a4587fbf74', @@ -71,5 +94,11 @@ def downloadData(self): import SampleData SampleData.downloadFromURL(**downloads) -# volumeNode = slicer.util.getNode(pattern='Segment_1') + modelNodeDict = slicer.util.getNodes('vtkMRMLModelNode*') + self.assertIsNotNone(modelNodeDict) + counter = 0 + for name, modelNode in modelNodeDict.items(): + self.assertIsNotNone(modelNodeDict) + counter = counter + 1 + self.assertEqual(counter, 3) diff --git a/LiverVolumetry/Logic/vtkLabelMapHelper.cxx b/LiverVolumetry/Logic/vtkLabelMapHelper.cxx index 7da6a43..95ba375 100644 --- a/LiverVolumetry/Logic/vtkLabelMapHelper.cxx +++ b/LiverVolumetry/Logic/vtkLabelMapHelper.cxx @@ -77,7 +77,7 @@ vtkLabelMapHelper::~vtkLabelMapHelper() //------------------------------------------------------------------------------ void vtkLabelMapHelper::PrintSelf(ostream &os, vtkIndent indent) { - + this->vtkObject::PrintSelf(os, indent); } //------------------------------------------------------------------------------ @@ -129,8 +129,7 @@ unsigned int vtkLabelMapHelper:: ProjectPointsOntoItkImage(vtkLabelMapHelper::LabelMapType::Pointer itkImage, vtkPoints *points, - unsigned short projectionValue, - unsigned int radius) + unsigned short projectionValue) { // Check for null pointers if (itkImage.IsNull()) diff --git a/LiverVolumetry/Logic/vtkLabelMapHelper.h b/LiverVolumetry/Logic/vtkLabelMapHelper.h index dade815..ed2f60a 100644 --- a/LiverVolumetry/Logic/vtkLabelMapHelper.h +++ b/LiverVolumetry/Logic/vtkLabelMapHelper.h @@ -66,7 +66,7 @@ vtkLabelMapHelper: public vtkObject public: static vtkLabelMapHelper* New(); vtkTypeMacro(vtkLabelMapHelper, vtkObject); - void PrintSelf(ostream &os, vtkIndent indent); + void PrintSelf(ostream &os, vtkIndent indent) override; //Type definitions typedef itk::Image LabelMapType; @@ -98,8 +98,7 @@ vtkLabelMapHelper: public vtkObject static unsigned int ProjectPointsOntoItkImage(LabelMapType::Pointer itkImage, vtkPoints *points, - unsigned short projectionValue, - unsigned int radius=0); + unsigned short projectionValue); // Description: diff --git a/LiverVolumetry/Logic/vtkLiverVolumetryLogic.cxx b/LiverVolumetry/Logic/vtkLiverVolumetryLogic.cxx index 28e4bb2..5ba6189 100644 --- a/LiverVolumetry/Logic/vtkLiverVolumetryLogic.cxx +++ b/LiverVolumetry/Logic/vtkLiverVolumetryLogic.cxx @@ -122,8 +122,7 @@ void vtkLiverVolumetryLogic::ComputeAdvancedPlanningVolumetry(vtkMRMLLabelMapVol } vtkLabelMapHelper::ProjectPointsOntoItkImage(this->ProjectedTargetSegmentImage, BezierHR->GetOutput()->GetPoints(), - baseValue, - 1); + baseValue); } } @@ -265,7 +264,7 @@ int vtkLiverVolumetryLogic::GetRes(vtkMRMLMarkupsBezierSurfaceNode* bezierSurfac std::vector ControlPointsY; std::vector ControlPointsZ; - for (int p = 0; p < ControlPointsIndexs[l].size();p++){ + for (unsigned int p = 0; p < ControlPointsIndexs[l].size();p++){ double point[3]; bezierSurfaceNode->GetNthControlPointPosition(ControlPointsIndexs[l][p],point); ControlPointsX.push_back(point[0]); diff --git a/README.md b/README.md index 1a51d75..2c0de22 100644 --- a/README.md +++ b/README.md @@ -85,30 +85,28 @@ There are multiple options to create visualizations for the resection (color, op ### Liver Segments -Our approach to liver segments definition consist of the defintion of a segment by the centerline connecting user-defined sets of points. These centerlines will be the base for computation of liver segments in image space. The computation is based on shortest-distance mapping. +Our approach to liver vascular territory segments definition consist of the defintion of a vascular territory by the centerline connecting user-defined sets of points. These centerlines will be the base for computation of liver segments in image space. The computation is based on shortest-distance mapping. The Liver segments can be defined using the following steps: -1. Select the `Segmentation`. -2. Select the hepatic/portal segmentation for `Segment`. -3. You can hide the liver segmentation for better visibility in the 3D view by going to: Modules > search for Data > click on Switch to Module > Click on the eye botton next to the liver segmentation. -4. Switch to *Liver* module again. -5. Create a *new Point List* for `Vessels points`. -6. Click the arrow button next to `Vessel points`," and place fixed landmark points on the hepatic/portal segmentation. These points will be useful for extracting the centralines of user-defined vessel branches. -7. Once all the points are placed, Click `Add Vessel Centerline Segments`. -8. Repeat steps 5 and 6 for creating *new Point List*, i.e extracting new centerlines. -9. Click on `Calculate Vascular Segments`. +1. Select `Vascular Territory Segmentation` +2. Select the `Segmentation`. +3. You can hide the liver segmentation for better visibility in the 3D view by first selecting the liver segmentation, then select `Hide`. +4. Select the hepatic/portal segmentation for `Segment`. The 3D view will then automatically switch to landmark "Place Mode" +and a `Vessel points` list will be automatically generated. +5. Place user-defined markup landmarks along the selected hepatic/portal model in 3D View to mark the part of the vessel where centerlines should be generated. +6. Once all the points are placed, Click `Add Vessel Centerline`. +7. Optionally, repeat steps 4-6 for generation of additional centerline for other vessel segments (portal/hepatic). +8. For generation of several vascular territory segments, select `Vascular Territory` and "Create new territory ID". +9. Repeat steps 4 to 7 for creating centerlines to be used for calculation of the new Vascular Territory. +10. Click on `Calculate Vascular Territory Segmentation`. The generated vascular territories will then be visible in the 3D- and 2D Views. +11. Multiple sets of vascular territories can be generated and stored in separat Vascular Territory Segmentations. +To generate a new set of vascular territories, select "Create New Vascular Territory Segmentation" in `Vascular Territory Segmentation`. +Then repeat steps 2-9. ![Liver segments -- placing fiducials](https://github.com/ALive-research/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_06.png?raw=true) ![Liver segments -- placing fiducials](https://github.com/ALive-research/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_08.png?raw=true) -10. If you want to visualize the liver segments in the 3D view: - 1. Click the search icon on the left of the module selector and write 'Data'. Click switch to module. - 2. Select the created *VascularSegments* labelmap and right click to `Convert label map to segmentation node`. - 3. Click again the search icon and go to `Segmentations` module. - 4. Select the new *VascularSegmentations* as `Active segmentation`. - 5. Click on `Show 3D`. - ![Slicer-Liver_screenshot_14](https://github.com/dalbenzioG/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_14.jpg?raw=true) ## Video Tutorial