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/Paper/bibliography.bib b/Paper/bibliography.bib index e30570a..98fda44 100644 --- a/Paper/bibliography.bib +++ b/Paper/bibliography.bib @@ -151,8 +151,6 @@ @misc{3DSlicerExtensionsManager:2024 year={2024} } - - @article{Bismuth:1982, title={Surgical anatomy and anatomical surgery of the liver}, author={Bismuth, Henri}, diff --git a/README.md b/README.md index 1a51d75..a5688dd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ - [Slicer-Liver Extension Usage](#slicer-liver-extension-usage) - [Distance Map Computation](#distance-map-computation) - [Resections](#resections) + - [Resectogram](#resectogram) - [Liver Segments](#liver-segments) + - [Resection Volumetry](#resection-volumetry) - [Video Tutorial](#video-tutorial) - [Developers](#developers) - [Compilation](#compilation) @@ -17,99 +19,159 @@ SlicerLiver is an extension for the medical research software [3D Slicer](https: The extension provides a fast and accurate solution for: -- Computation and visualization of liver vascular territories (liver segments). - Definition of surgical resection in 3D using deformable surfaces, as well as the visualization of resection margins (risk areas). +- Computation and visualization of liver vascular territories (liver segments). +- Liver volumetry analysis. ### Installing the extension -1. Download and install 3D Slicer according to your operating system from here : https://download.slicer.org/. -2. Open Slicer. -3. Press Ctrl+4 to open the extension manager. Or click the blue upper-right icon with the letter `E`. -4. Once the Extension Manager pops up, make sure to select the `Install Extensions` tab. -5. On the upper-right search box write *"Liver"* -6. Click `Install` and give okay to install other extensions if asked. -7. Restart Slicer. -8. Once 3D Slicer restarts, click the search icon on the left of the module selector and write 'Liver'. Click `Switch to module`. +1. Download and install 3D Slicer according to your operating system from here : https://download.slicer.org/. +2. Open Slicer. +3. Press Ctrl+4 to open the extension manager. +4. Once the Extension Manager pops up, make sure to select the `Install Extensions` tab. +5. On the upper-right search box write *"slicerliver"* +6. Click `Install` and give okay to install other extensions if asked. + +![Slicer-Liver_screenshot_22.jpg](Screenshots_tutorial/Slicer-Liver_screenshot_22.png) -![Slicer-Liver_screenshot_10](https://github.com/dalbenzioG/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_10.jpg?raw=true) +7. Restart Slicer. +8. Once 3D Slicer restarts, click the search icon on the left of the module selector and write 'Liver'. Click `Switch to module`. +![Slicer-Liver_screenshot_10.jpg](Screenshots_tutorial/Slicer-Liver_screenshot_10.jpg) ### Sample Data To test the extension, the LiverVolume and LiverSementation data can be loaded from the Sample Data module, after installing Slicer-Liver. To properly load the data in the plugin, it is advised to first open the extension and afterwards to navigate to the Sample module and to load the data. - -![Slicer-Liver_screenshot_9](https://github.com/dalbenzioG/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_9.jpg?raw=true) +![Slicer-Liver_screenshot_09.jpg](Screenshots_tutorial/Slicer-Liver_screenshot_09.jpg) ## Slicer-Liver Extension Usage -The extension is separated in the following three sections: +The extension is separated in the following five sections: - Distance Map Computation: projection of the safety margins in real-time onto the resection surface, which allows the user to modify the resection proposal until the safety requirement are met. - Resections: computation of the first approximation (planar Bézier) of the resection surface which can be subsequently modified through 16 control points. +- Resectogram: visualization of the resection plan in a 2D view. - Liver Segments: calculation and visualization of liver vascular territories (liver segments). +- Resection Volumetry: computation of the volume of the resection and the remaining liver. Each section is oriented towards one part of the liver resection planning workflow but, if desired, can work independently of the other ones. -At the end of the workflow, the distance map, resection plan and liver segments can be saved to a given output directory. +At the end of the workflow, the distance map, resection plan, liver segments and volumetry tables can be saved to a given output directory. ### Distance Map Computation -The computation of the Distance Map can be done using the following steps: - -1. Select the `Reference Volume` CT data. -2. Select the `Segmentation` , i.e the binary labelmap representation which stores the segmentation of the liver, tumour and vascular territories. -3. Select the `Tumor` segmentation. -4. Select the `Liver` segmentation. -5. Create new *VectorVolume* for `Output Distance Map`. -6. Finally, click on `Compute Distance Map`. +Follow these steps to compute the Distance Map: + +1. **Reference Volume**: + - Select the CT data from the dropdown menu. +2. **Segmentation**: + - Choose the binary labelmap representing the segmentation of the liver, tumor, and vascular territories. +3. **Tumor**: + - Select the tumor segmentation node. +4. **Liver**: + - Select the liver segmentation node. +5. **Hepatic** (if applicable): + - Select the hepatic segmentation node. +6. **Portal** (if applicable): + - Select the portal segmentation node. +7. **Output Distance Map**: + - Create a new `VectorVolume` for the output distance map. +8. **Downsampling Rate** (optional): + - Adjust the downsampling rate if needed (default is 1.00). +9. **Compute Distance Map**: + - Click the `Compute Distance Maps` button to start the computation. + +Ensure all required fields are filled; otherwise, the `Compute Distance Maps` button will remain inactive. ### Resections The liver resection can be planned through the following process: -1. Create a new *Liver Resection* for `Resection`. -2. Select the labelmap (the same used in step 2 for the Distance Map Computation) for `Liver Segmentation`. -3. Select the `Liver` segmentation. -4. Optional: the user has the possibility to select the *Distance Map* computed at the end of the first section thought the collapsible button `Distance Map`. -5. In the 3D View, slide the contour surrounding the liver in the desired position through the *MarkupSlidingContour*. -6. The initial resection plane will appear after dropping the mouse. -7. The resection can be deformed using the control points (4x4). It is also possible to modify the `Resection grid`, `Resection margin` and `Uncertainty margin` as desired. -8. Check `Preview resection` checkbox if you want to visualize the final resection plan. - -![Slicer-Liver_screenshot_11](https://github.com/dalbenzioG/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_11.jpg?raw=true) - -![Slicer-Liver_screenshot_12](https://github.com/dalbenzioG/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_12.jpg?raw=true) +1. *Create a new LiverResection* for `Resection`. +2. Choose the labelmap used in step 2 of the Distance Map Computation for `Liver Segmentation`. +3. Select the `Liver` segmentation from the dropdown. +4. Choose the contour initialization method: `Flat`, `Curved`, or `MarkupClosedCurve`. +5. *(Optional)* Select the *Distance Map* from the first section using the `Distance Map` collapsible button. +6. In the 3D View, use the *MarkupSlidingContour* or *MarkupDistanceContour* tool to adjust the contour around the liver, or draw a closed curve with the *MarkupClosedCurve* tool. +7. Release the mouse to display the initial resection plane. +8. Adjust the resection using the control points. Modify the `Resection grid`, `Resection margin`, and `Uncertainty margin` as needed. +9. Check the `Preview resection` box to visualize the final plan. +![Slicer-Liver_screenshot_15.jpg](Screenshots_tutorial/Slicer-Liver_screenshot_15.jpg) There are multiple options to create visualizations for the resection (color, opacity, configurable grid, etc). - -![Liver planning visualization](https://github.com/ALive-research/Slicer-Liver/blob/master/Screenshots/Slicer-Liver_screenshot_04.png?raw=true) +

+ Slicer-Liver_screenshot_16_1 + Slicer-Liver_screenshot_16_2 +

+ +### Resectogram +The Resectogram section in Slicer-Liver allows users to configure various options for visualizing the resectogram. Below are the available settings and their descriptions: + +1. **Enable Resectogram:** + - Check this box to enable the resectogram visualization. +2. **Mirror Resectogram Display**: + - Check this box to mirror the display of the resectogram. +3. **Enable Flexible Boundary:** + - Check this box to enable a flexible boundary for the resectogram. +4. **Resectogram Size Scale:** + - Use the slider or the input box to adjust the size scale of the resectogram. The value can be set between 0 and 1. +5. **Hepatic Contour Size (in mm):** + - Adjust the size of the hepatic contour by using the input box. The size can be configured in millimeters. +6. **Color Picker:** + - Click on the color box to choose a different color for the hepatic contour. +7. **Portal resection contour size (in mm):** + - Adjust the size of the portal resection contour by using the input box. The size can be configured in millimeters. +8. **Color Picker:** + - Click on the color box to choose a different color for the portal resection contour. +9. **Vascular Segments Volume:** + - Use the dropdown menu to select the vascular segments volume. The available options depend on the pre-loaded volumes in the software. + +The resectogram can only be used after distance maps have been calculated and uploaded. +The following GIF demonstrates the usage of the Resectogram section in Slicer-Liver: + +![Slicer-Liver_resectogram.gif](Screenshots_tutorial/Slicer-Liver_resectogram.gif) ### 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. -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`. - -![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) +Our method for liver segment classification involves defining segments using centerlines created from user-specified points. These centerlines serve as the foundation for computing liver segments within the image space. The computation leverages shortest-distance mapping + +1. **Vascular Territory Segmentation**: + - Create or Select a vascular territory segmentation from the dropdown menu. +2. **Vascular Territory**: + - Create a new territory ID. +3. **Segmentation**: + - Select the segmentation node representing the hepatic/portal vessels, and a new Point List for marking `Vessel points` will be created automatically. +4. **Hide Unnecessary Segments**: + - Use the `Show/Hide` button to hide the liver and/or tumor segmentation nodes if they obstruct the view. This step is not required for creating centerlines on vessel branches but can improve visibility. +5. **Vessel Points**: + - Place landmark points on the hepatic/portal segmentation. These points will be added to `Vessel points` and used to extract the centerlines of user-defined vessel branches. +6. **Add Vessel Centerline**: + - After placing the points, click `Add Vessel Centerline` to generate the centerlines. +7. **Create multiple Vascular Territories** + - Repeat step 1-6 to create multiple Vascular Territories. +8. **Calculate Vascular Territory Segmentation**: + - Once all points are placed and centerlines are added, click `Calculate Vascular Territory Segmentation` to compute the liver segments. + +![Slicer-Liver_screenshot_18.jpg](Screenshots_tutorial/Slicer-Liver_screenshot_18.jpg) + +### Resection Volumetry +1. **Volumetry Output Table:** + - Select or create an output table. You can rename the table or switch between different tables. +2. **Reference Volume:** + - Select a liver volume node. +3. **Segmentation:** + - Select a liver segmentation node (Labelmap node). This can be liver segmentation (vessel, tumor, liver), liver anatomy segments, or self-defined liver segments (adapted from the vessel segments module). You can select all segments in the dataset or only those of interest. +4. **Total Volume:** + - Define the total volume by selecting the segments you wish to count (optional; by default, it is the sum of all segments in the data). +5. **ROI Marker List:** + - Select or create a points list. You can place points onto 2D slices or 3D models to mark the area you want to measure. If no point list is provided, the volume of all segments will be calculated. +6. **Resection (Optional):** + - Choose one or more resections for the same liver model and calculate the remnant/resected volume by placing marker points onto these areas. + - This can be combined with liver anatomy segments or self-defined liver segments to gain a deeper understanding of liver volumetry after different resection approaches (anatomical, atypical, etc.). + - It can also be used to compare different resection plans for the same tumor or to provide a combined view for one surgery with multiple resections. + +![Slicer-Liver_screenshot_19.png](Screenshots_tutorial/Slicer-Liver_screenshot_19.png) +![Slicer-Liver_screenshot_20.png](Screenshots_tutorial/Slicer-Liver_screenshot_20.png) + ## Video Tutorial [Slicer-Liver tutorial](https://www.youtube.com/watch?v=oRu624mtQZE) diff --git a/Screenshots_tutorial/Slicer-Liver_resectogram.gif b/Screenshots_tutorial/Slicer-Liver_resectogram.gif new file mode 100644 index 0000000..dcd826f Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_resectogram.gif differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_01.png b/Screenshots_tutorial/Slicer-Liver_screenshot_01.png new file mode 100644 index 0000000..d4be82f Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_01.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_02.png b/Screenshots_tutorial/Slicer-Liver_screenshot_02.png new file mode 100644 index 0000000..cb75f6c Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_02.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_03.png b/Screenshots_tutorial/Slicer-Liver_screenshot_03.png new file mode 100644 index 0000000..8e8ed46 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_03.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_04.png b/Screenshots_tutorial/Slicer-Liver_screenshot_04.png new file mode 100644 index 0000000..180d5b3 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_04.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_05.png b/Screenshots_tutorial/Slicer-Liver_screenshot_05.png new file mode 100644 index 0000000..5991084 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_05.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_06.png b/Screenshots_tutorial/Slicer-Liver_screenshot_06.png new file mode 100644 index 0000000..40274f9 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_06.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_07.png b/Screenshots_tutorial/Slicer-Liver_screenshot_07.png new file mode 100644 index 0000000..8440f1c Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_07.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_08.png b/Screenshots_tutorial/Slicer-Liver_screenshot_08.png new file mode 100644 index 0000000..706b066 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_08.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_09.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_09.jpg new file mode 100644 index 0000000..deee539 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_09.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_10.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_10.jpg new file mode 100644 index 0000000..4001a64 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_10.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_11.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_11.jpg new file mode 100644 index 0000000..c606232 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_11.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_12.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_12.jpg new file mode 100644 index 0000000..2c33016 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_12.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_13.png b/Screenshots_tutorial/Slicer-Liver_screenshot_13.png new file mode 100644 index 0000000..78488b3 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_13.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_14.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_14.jpg new file mode 100644 index 0000000..43f3c92 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_14.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_15.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_15.jpg new file mode 100644 index 0000000..e916328 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_15.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_16_1.png b/Screenshots_tutorial/Slicer-Liver_screenshot_16_1.png new file mode 100644 index 0000000..60e61cb Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_16_1.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_16_2.png b/Screenshots_tutorial/Slicer-Liver_screenshot_16_2.png new file mode 100644 index 0000000..18f58cc Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_16_2.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_17.png b/Screenshots_tutorial/Slicer-Liver_screenshot_17.png new file mode 100644 index 0000000..c75f97c Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_17.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_18.jpg b/Screenshots_tutorial/Slicer-Liver_screenshot_18.jpg new file mode 100644 index 0000000..0ebeaa1 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_18.jpg differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_19.png b/Screenshots_tutorial/Slicer-Liver_screenshot_19.png new file mode 100644 index 0000000..ff09817 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_19.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_20.png b/Screenshots_tutorial/Slicer-Liver_screenshot_20.png new file mode 100644 index 0000000..ff09817 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_20.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_21.png b/Screenshots_tutorial/Slicer-Liver_screenshot_21.png new file mode 100644 index 0000000..7b1a005 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_21.png differ diff --git a/Screenshots_tutorial/Slicer-Liver_screenshot_22.png b/Screenshots_tutorial/Slicer-Liver_screenshot_22.png new file mode 100644 index 0000000..28ff5f9 Binary files /dev/null and b/Screenshots_tutorial/Slicer-Liver_screenshot_22.png differ