def xyzToIjk(self, xyz, viewWidget, image): import vtkSegmentationCorePython as vtkSegmentationCore xyzVector = qt.QVector3D(xyz[0], xyz[1], xyz[2]) ijkVector = self.scriptedEffect.xyzToIjk(xyzVector, viewWidget, image) return [int(ijkVector.x()), int(ijkVector.y()), int(ijkVector.z())]
def xyzToRas(self, xyz, viewWidget): xyzVector = qt.QVector3D(xyz[0], xyz[1], xyz[2]) rasVector = self.scriptedEffect.xyzToRas(xyzVector, viewWidget) return [rasVector.x(), rasVector.y(), rasVector.z()]
def rasToXy(self, ras, viewWidget): rasVector = qt.QVector3D(ras[0], ras[1], ras[2]) xyPoint = self.scriptedEffect.rasToXy(rasVector, viewWidget) return [xyPoint.x(), xyPoint.y()]
def process(self): import time startTime = time.time() logging.info('Processing started') slicer.util.showStatusMessage("Segment editor setup") slicer.app.processEvents() """ Find segment editor widgets. Use a dedicated class to store widget references once only. Not reasonable to dig through the UI on every run. """ if not self.segmentEditorWidgets: self.segmentEditorWidgets = SegmentEditorWidgets() self.segmentEditorWidgets.findWidgets() # Create a new segmentation if none is specified. if not self.outputSegmentation: segmentation = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLSegmentationNode") self.outputSegmentation = segmentation else: # Prefer a local reference for readability segmentation = self.outputSegmentation # Local direct reference to slicer.modules.SegmentEditorWidget.editor seWidgetEditor = self.segmentEditorWidgets.widgetEditor # Get volume node sliceWidget = slicer.app.layoutManager().sliceWidget( self.inputSliceNode.GetName()) volumeNode = sliceWidget.sliceLogic().GetBackgroundLayer( ).GetVolumeNode() # Set segment editor controls seWidgetEditor.setSegmentationNode(segmentation) seWidgetEditor.setMasterVolumeNode(volumeNode) """ This geometry update does the speed-up magic ! No need to crop the master volume. We don't strictly need it right here because it is the first master volume of the segmentation. It's however required below each time the master volume node is changed. https://discourse.slicer.org/t/resampled-segmentation-limited-by-a-bounding-box-not-the-whole-volume/18772/3 """ segmentation.SetReferenceImageGeometryParameterFromVolumeNode( volumeNode) # Go to Segment Editor. mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('SegmentEditor') # Show the input curve. Colour of control points change on selection, helps to wait. self.inputCurveNode.SetDisplayVisibility(True) # Reset segment editor masking widgets. Values set by previous work must not interfere here. self.segmentEditorWidgets.resetMaskingWidgets() #---------------------- Draw tube with VTK--------------------- # https://discourse.slicer.org/t/converting-markupscurve-to-markupsfiducial/20246/3 tube = vtk.vtkTubeFilter() tube.SetInputData(self.inputCurveNode.GetCurveWorld()) tube.SetRadius(self.tubeDiameter / 2) tube.SetNumberOfSides(30) tube.CappingOn() tube.Update() segmentation.AddSegmentFromClosedSurfaceRepresentation( tube.GetOutput(), "TubeMask") # Select it so that Split Volume can work on this specific segment only. seWidgetEditor.setCurrentSegmentID("TubeMask") #---------------------- Split volume --------------------- slicer.util.showStatusMessage("Split volume") slicer.app.processEvents() seWidgetEditor.setActiveEffectByName("Split volume") svEffect = seWidgetEditor.activeEffect() svEffect.setParameter("FillValue", -1000) # Work on the TubeMask segment only. svEffect.setParameter("ApplyToAllVisibleSegments", 0) svEffect.self().onApply() seWidgetEditor.setActiveEffectByName(None) # Get output split volume allScalarVolumeNodes = slicer.mrmlScene.GetNodesByClass( "vtkMRMLScalarVolumeNode") outputSplitVolumeNode = allScalarVolumeNodes.GetItemAsObject( allScalarVolumeNodes.GetNumberOfItems() - 1) # Remove no longer needed drawn tube segment segment = segmentation.GetSegmentation().GetSegment("TubeMask") segmentation.GetSegmentation().RemoveSegment(segment) # Replace master volume of segmentation seWidgetEditor.setMasterVolumeNode(outputSplitVolumeNode) segmentation.SetReferenceImageGeometryParameterFromVolumeNode( outputSplitVolumeNode) """ Split Volume creates a folder that contains the segmentation node, and the split volume(s) it creates. Here, we need to get rid of the split volume. There is no reason to keep around the created folder, that takes owneship of the segmentation node. So we'll later move the segmentation node to the Scene node and remove the residual empty folder. """ shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode( slicer.mrmlScene) shSplitVolumeId = shNode.GetItemByDataNode(outputSplitVolumeNode) shSplitVolumeParentId = shNode.GetItemParent(shSplitVolumeId) shSegmentationId = shNode.GetItemByDataNode(segmentation) shSceneId = shNode.GetSceneItemID() #---------------------- Manage segment -------------------- # Remove a segment node and keep its color segment = None segmentColor = [] """ Control the segment ID. It will be the same in all segmentations. We can reach it precisely. """ segmentID = "Segment_" + self.inputCurveNode.GetID() segment = segmentation.GetSegmentation().GetSegment(segmentID) if segment: segmentColor = segment.GetColor() segmentation.GetSegmentation().RemoveSegment(segment) # Add a new segment, with controlled ID and known color. object = segmentation.GetSegmentation().AddEmptySegment(segmentID) segment = segmentation.GetSegmentation().GetSegment(object) # Visually identify the segment by the input fiducial name segmentName = "Segment_" + self.inputCurveNode.GetName() segment.SetName(segmentName) if len(segmentColor): segment.SetColor(segmentColor) # Select new segment seWidgetEditor.setCurrentSegmentID(segmentID) #---------------------- Flood filling --------------------- # Set parameters seWidgetEditor.setActiveEffectByName("Flood filling") ffEffect = seWidgetEditor.activeEffect() ffEffect.setParameter("IntensityTolerance", self.intensityTolerance) ffEffect.setParameter("NeighborhoodSizeMm", self.neighbourhoodSize) # +++ If an alien ROI is set, segmentation may fail and take an infinite time. ffEffect.parameterSetNode().SetNodeReferenceID("FloodFilling.ROI", None) ffEffect.updateGUIFromMRML() # Get input curve control points curveControlPoints = vtk.vtkPoints() self.inputCurveNode.GetControlPointPositionsWorld(curveControlPoints) numberOfCurveControlPoints = curveControlPoints.GetNumberOfPoints() # Apply flood filling at curve control points. Ignore first and last point as the resulting segment would be a big lump. The voxels of split volume at -1000 would be included in the segment. for i in range(1, numberOfCurveControlPoints - 1): # Show progress in status bar. Helpful to wait. t = time.time() msg = f'Flood filling : {t-startTime:.2f} seconds - ' self.showStatusMessage( (msg, str(i + 1), "/", str(numberOfCurveControlPoints))) rasPoint = curveControlPoints.GetPoint(i) slicer.vtkMRMLSliceNode.JumpSlice( sliceWidget.sliceLogic().GetSliceNode(), *rasPoint) point3D = qt.QVector3D(rasPoint[0], rasPoint[1], rasPoint[2]) point2D = ffEffect.rasToXy(point3D, sliceWidget) qIjkPoint = ffEffect.xyToIjk( point2D, sliceWidget, ffEffect.self().getClippedMasterImageData()) ffEffect.self().floodFillFromPoint( (int(qIjkPoint.x()), int(qIjkPoint.y()), int(qIjkPoint.z()))) # Switch off active effect seWidgetEditor.setActiveEffect(None) # Replace master volume of segmentation seWidgetEditor.setMasterVolumeNode(volumeNode) segmentation.SetReferenceImageGeometryParameterFromVolumeNode( volumeNode) # Remove no longer needed split volume. slicer.mrmlScene.RemoveNode(outputSplitVolumeNode) # Remove folder created by Split Volume. # First, reparent the segmentation item to scene item. shNode.SetItemParent(shSegmentationId, shSceneId) """ Remove an empty folder directly. Keep it if there are volumes from other work. """ if shNode.GetNumberOfItemChildren(shSplitVolumeParentId) == 0: if shNode.GetItemLevel(shSplitVolumeParentId) == "Folder": shNode.RemoveItem(shSplitVolumeParentId) if not self.extractCenterlines: stopTime = time.time() message = f'Processing completed in {stopTime-startTime:.2f} seconds' logging.info(message) slicer.util.showStatusMessage(message, 5000) return #---------------------- Extract centerlines --------------------- slicer.util.showStatusMessage("Extract centerline setup") slicer.app.processEvents() mainWindow.moduleSelector().selectModule('ExtractCenterline') if not self.extractCenterlineWidgets: self.extractCenterlineWidgets = ExtractCenterlineWidgets() self.extractCenterlineWidgets.findWidgets() inputSurfaceComboBox = self.extractCenterlineWidgets.inputSurfaceComboBox inputSegmentSelectorWidget = self.extractCenterlineWidgets.inputSegmentSelectorWidget endPointsMarkupsSelector = self.extractCenterlineWidgets.endPointsMarkupsSelector outputCenterlineModelSelector = self.extractCenterlineWidgets.outputCenterlineModelSelector outputCenterlineCurveSelector = self.extractCenterlineWidgets.outputCenterlineCurveSelector preprocessInputSurfaceModelCheckBox = self.extractCenterlineWidgets.preprocessInputSurfaceModelCheckBox applyButton = self.extractCenterlineWidgets.applyButton # Set input segmentation inputSurfaceComboBox.setCurrentNode(segmentation) inputSegmentSelectorWidget.setCurrentSegmentID(segmentID) # Create 2 fiducial endpoints, at start and end of input curve. We call it output because it is not user input. outputFiducialNode = self.outputFiducialNode if not outputFiducialNode: outputFiducialNode = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLMarkupsFiducialNode") # Visually identify the segment by the input fiducial name outputFiducialNode.SetName("Endpoints_" + self.inputCurveNode.GetName()) firstInputCurveControlPoint = self.inputCurveNode.GetNthControlPointPositionVector( 0) outputFiducialNode.AddControlPointWorld( firstInputCurveControlPoint) endPointsMarkupsSelector.setCurrentNode(outputFiducialNode) lastInputCurveControlPoint = self.inputCurveNode.GetNthControlPointPositionVector( curveControlPoints.GetNumberOfPoints() - 1) outputFiducialNode.AddControlPointWorld(lastInputCurveControlPoint) endPointsMarkupsSelector.setCurrentNode(outputFiducialNode) self.outputFiducialNode = outputFiducialNode # Account for rename. Control points are not remaned though. outputFiducialNode.SetName("Endpoints_" + self.inputCurveNode.GetName()) # Output centerline model. A single node throughout. centerlineModel = self.outputCenterlineModel if not centerlineModel: centerlineModel = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLModelNode") # Visually identify the segment by the input fiducial name centerlineModel.SetName("Centerline_model_" + self.inputCurveNode.GetName()) self.outputCenterlineModel = centerlineModel # Account for rename centerlineModel.SetName("Centerline_model_" + self.inputCurveNode.GetName()) outputCenterlineModelSelector.setCurrentNode(centerlineModel) # Output centerline curve. A single node throughout. centerlineCurve = self.outputCenterlineCurve if not centerlineCurve: centerlineCurve = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLMarkupsCurveNode") # Visually identify the segment by the input fiducial name centerlineCurve.SetName("Centerline_curve_" + self.inputCurveNode.GetName()) self.outputCenterlineCurve = centerlineCurve # Account for rename centerlineCurve.SetName("Centerline_curve_" + self.inputCurveNode.GetName()) outputCenterlineCurveSelector.setCurrentNode(centerlineCurve) """ Don't preprocess input surface. Decimation error may crash Slicer. Quadric method for decimation is slower but more reliable. """ preprocessInputSurfaceModelCheckBox.setChecked(False) # Apply applyButton.click() # Hide the input curve to show the centerlines self.inputCurveNode.SetDisplayVisibility(False) # Close network pane; we don't use this here. self.extractCenterlineWidgets.outputNetworkGroupBox.collapsed = True stopTime = time.time() message = f'Processing completed in {stopTime-startTime:.2f} seconds' logging.info(message) slicer.util.showStatusMessage(message, 5000)
def process(self): import time startTime = time.time() logging.info('Processing started') slicer.util.showStatusMessage("Segment editor setup") slicer.app.processEvents() """ Find segment editor widgets. Use a dedicated class to store widget references once only. Not reasonable to dig through the UI on every run. """ if not self.segmentEditorWidgets: self.segmentEditorWidgets = SegmentEditorWidgets() self.segmentEditorWidgets.findWidgets() # Create a new segmentation if none is specified. if not self.outputSegmentation: segmentation=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode") self.outputSegmentation = segmentation else: # Prefer a local reference for readability segmentation = self.outputSegmentation # Local direct reference to slicer.modules.SegmentEditorWidget.editor seWidgetEditor=self.segmentEditorWidgets.widgetEditor # Get volume node sliceWidget = slicer.app.layoutManager().sliceWidget(self.inputSliceNode.GetName()) volumeNode = sliceWidget.sliceLogic().GetBackgroundLayer().GetVolumeNode() # Set segment editor controls seWidgetEditor.setSegmentationNode(segmentation) seWidgetEditor.setMasterVolumeNode(volumeNode) # Go to Segment Editor. mainWindow = slicer.util.mainWindow() mainWindow.moduleSelector().selectModule('SegmentEditor') #---------------------- Manage segment -------------------- # Remove a segment node and keep its color segment = None segmentColor = [] """ Control the segment ID. It will be the same in all segmentations. We can reach it precisely. """ segmentID = "Segment_" + self.inputFiducialNode.GetID() segment = segmentation.GetSegmentation().GetSegment(segmentID) if segment: segmentColor = segment.GetColor() segmentation.GetSegmentation().RemoveSegment(segment) # Add a new segment, with controlled ID and known color. object = segmentation.GetSegmentation().AddEmptySegment(segmentID) segment = segmentation.GetSegmentation().GetSegment(object) # Visually identify the segment by the input fiducial name segmentName = "Segment_" + self.inputFiducialNode.GetName() segment.SetName(segmentName) if len(segmentColor): segment.SetColor(segmentColor) # Select new segment seWidgetEditor.setCurrentSegmentID(segmentID) #---------------------- Flood filling -------------------- # Each fiducial point will be a user click. # Set parameters seWidgetEditor.setActiveEffectByName("Flood filling") ffEffect = seWidgetEditor.activeEffect() ffEffect.setParameter("IntensityTolerance", self.intensityTolerance) ffEffect.setParameter("NeighborhoodSizeMm", self.neighbourhoodSize) ffEffect.parameterSetNode().SetNodeReferenceID("FloodFilling.ROI", self.inputROINode.GetID() if self.inputROINode else None) ffEffect.updateGUIFromMRML() # Reset segment editor masking widgets. Values set by previous work must not interfere here. self.segmentEditorWidgets.resetMaskingWidgets() # Apply flood filling at each fiducial point. points=vtk.vtkPoints() self.inputFiducialNode.GetControlPointPositionsWorld(points) numberOfFiducialControlPoints = points.GetNumberOfPoints() for i in range(numberOfFiducialControlPoints): # Show progress in status bar. Helpful to wait. t = time.time() msg = f'Flood filling : {t-startTime:.2f} seconds - ' self.showStatusMessage((msg, str(i + 1), "/", str(numberOfFiducialControlPoints))) rasPoint = points.GetPoint(i) slicer.vtkMRMLSliceNode.JumpSlice(sliceWidget.sliceLogic().GetSliceNode(), *rasPoint) point3D = qt.QVector3D(rasPoint[0], rasPoint[1], rasPoint[2]) point2D = ffEffect.rasToXy(point3D, sliceWidget) qIjkPoint = ffEffect.xyToIjk(point2D, sliceWidget, ffEffect.self().getClippedMasterImageData()) ffEffect.self().floodFillFromPoint((int(qIjkPoint.x()), int(qIjkPoint.y()), int(qIjkPoint.z()))) # Switch off active effect seWidgetEditor.setActiveEffect(None) # Show segment show3DctkMenuButton = self.segmentEditorWidgets.show3DctkMenuButton # Don't use click() here, smoothing options make a mess. show3DctkMenuButton.setChecked(True) # Hide ROI if self.inputROINode: self.inputROINode.SetDisplayVisibility(False) if not self.extractCenterlines: stopTime = time.time() message = f'Processing completed in {stopTime-startTime:.2f} seconds' logging.info(message) slicer.util.showStatusMessage(message, 5000) return #---------------------- Extract centerlines --------------------- slicer.util.showStatusMessage("Extract centerline setup") slicer.app.processEvents() mainWindow.moduleSelector().selectModule('ExtractCenterline') if not self.extractCenterlineWidgets: self.extractCenterlineWidgets = ExtractCenterlineWidgets() self.extractCenterlineWidgets.findWidgets() inputSurfaceComboBox = self.extractCenterlineWidgets.inputSurfaceComboBox inputSegmentSelectorWidget = self.extractCenterlineWidgets.inputSegmentSelectorWidget endPointsMarkupsSelector = self.extractCenterlineWidgets.endPointsMarkupsSelector outputCenterlineModelSelector = self.extractCenterlineWidgets.outputCenterlineModelSelector outputCenterlineCurveSelector = self.extractCenterlineWidgets.outputCenterlineCurveSelector preprocessInputSurfaceModelCheckBox = self.extractCenterlineWidgets.preprocessInputSurfaceModelCheckBox applyButton = self.extractCenterlineWidgets.applyButton # Set input segmentation and endpoints inputSurfaceComboBox.setCurrentNode(segmentation) inputSegmentSelectorWidget.setCurrentSegmentID(segmentID) endPointsMarkupsSelector.setCurrentNode(self.inputFiducialNode) # Output centerline model. A single node throughout. centerlineModel = self.outputCenterlineModel if not centerlineModel: centerlineModel = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode") # Visually identify the segment by the input fiducial name centerlineModel.SetName("Centerline_model_" + self.inputFiducialNode.GetName()) self.outputCenterlineModel = centerlineModel # Account for rename centerlineModel.SetName("Centerline_model_" + self.inputFiducialNode.GetName()) outputCenterlineModelSelector.setCurrentNode(centerlineModel) # Output centerline curve. A single node throughout. centerlineCurve = self.outputCenterlineCurve if not centerlineCurve: centerlineCurve = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsCurveNode") # Visually identify the segment by the input fiducial name centerlineCurve.SetName("Centerline_curve_" + self.inputFiducialNode.GetName()) self.outputCenterlineCurve = centerlineCurve # Account for rename centerlineCurve.SetName("Centerline_curve_" + self.inputFiducialNode.GetName()) outputCenterlineCurveSelector.setCurrentNode(centerlineCurve) """ Don't preprocess input surface. Decimation error may crash Slicer. Quadric method for decimation is slower but more reliable. """ preprocessInputSurfaceModelCheckBox.setChecked(False) # Apply applyButton.click() # Close network pane; we don't use this here. self.extractCenterlineWidgets.outputNetworkGroupBox.collapsed = True stopTime = time.time() message = f'Processing completed in {stopTime-startTime:.2f} seconds' logging.info(message) slicer.util.showStatusMessage(message, 5000)
def xyzToIjk(self, xyz, viewWidget, image, parentTransformNode=None): xyzVector = qt.QVector3D(xyz[0], xyz[1], xyz[2]) ijkVector = self.scriptedEffect.xyzToIjk(xyzVector, viewWidget, image, parentTransformNode) return [int(ijkVector.x()), int(ijkVector.y()), int(ijkVector.z())]