def computeStatistics(self, segmentID): import vtkSegmentationCorePython as vtkSegmentationCore requestedKeys = self.getRequestedKeys() segmentationNode = slicer.mrmlScene.GetNodeByID( self.getParameterNode().GetParameter("Segmentation")) if len(requestedKeys) == 0: return {} containsLabelmapRepresentation = segmentationNode.GetSegmentation( ).ContainsRepresentation( vtkSegmentationCore.vtkSegmentationConverter. GetSegmentationBinaryLabelmapRepresentationName()) if not containsLabelmapRepresentation: return {} segmentLabelmap = slicer.vtkOrientedImageData() segmentationNode.GetBinaryLabelmapRepresentation( segmentID, segmentLabelmap) if (not segmentLabelmap or not segmentLabelmap.GetPointData() or not segmentLabelmap.GetPointData().GetScalars()): # No input label data return {} # We need to know exactly the value of the segment voxels, apply threshold to make force the selected label value labelValue = 1 backgroundValue = 0 thresh = vtk.vtkImageThreshold() thresh.SetInputData(segmentLabelmap) thresh.ThresholdByLower(0) thresh.SetInValue(backgroundValue) thresh.SetOutValue(labelValue) thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR) thresh.Update() # Use binary labelmap as a stencil stencil = vtk.vtkImageToImageStencil() stencil.SetInputData(thresh.GetOutput()) stencil.ThresholdByUpper(labelValue) stencil.Update() stat = vtk.vtkImageAccumulate() stat.SetInputData(thresh.GetOutput()) stat.SetStencilData(stencil.GetOutput()) stat.Update() # Add data to statistics list cubicMMPerVoxel = reduce(lambda x, y: x * y, segmentLabelmap.GetSpacing()) ccPerCubicMM = 0.001 stats = {} if "voxel_count" in requestedKeys: stats["voxel_count"] = stat.GetVoxelCount() if "volume_mm3" in requestedKeys: stats["volume_mm3"] = stat.GetVoxelCount() * cubicMMPerVoxel if "volume_cm3" in requestedKeys: stats["volume_cm3"] = stat.GetVoxelCount( ) * cubicMMPerVoxel * ccPerCubicMM return stats
def generateMergedLabelmapInReferenceGeometry(self, segmentationNode, referenceVolumeNode): if segmentationNode is None: logging.error("Invalid segmentation node") return None if referenceVolumeNode is None: logging.error("Invalid reference volume node") return None # Get reference geometry in the segmentation node's coordinate system referenceGeometry_Reference = slicer.vtkOrientedImageData( ) # reference geometry in reference node coordinate system referenceGeometry_Segmentation = slicer.vtkOrientedImageData() mergedLabelmap_Reference = slicer.vtkOrientedImageData() referenceGeometryToSegmentationTransform = vtk.vtkGeneralTransform() # Set reference image geometry referenceGeometry_Reference.SetExtent( referenceVolumeNode.GetImageData().GetExtent()) ijkToRasMatrix = vtk.vtkMatrix4x4() referenceVolumeNode.GetIJKToRASMatrix(ijkToRasMatrix) referenceGeometry_Reference.SetGeometryFromImageToWorldMatrix( ijkToRasMatrix) # Transform it to the segmentation node coordinate system referenceGeometry_Segmentation = slicer.vtkOrientedImageData() referenceGeometry_Segmentation.DeepCopy(referenceGeometry_Reference) # Get transform between reference volume and segmentation node if (referenceVolumeNode.GetParentTransformNode() != segmentationNode.GetParentTransformNode()): slicer.vtkMRMLTransformNode.GetTransformBetweenNodes( referenceVolumeNode.GetParentTransformNode(), segmentationNode.GetParentTransformNode(), referenceGeometryToSegmentationTransform) slicer.vtkOrientedImageDataResample.TransformOrientedImage( referenceGeometry_Segmentation, referenceGeometryToSegmentationTransform, True) # Generate shared labelmap for the exported segments in segmentation coordinates sharedImage_Segmentation = slicer.vtkOrientedImageData() if (not segmentationNode.GenerateMergedLabelmapForAllSegments( sharedImage_Segmentation, 0, None)): logging.error( "ExportSegmentsToLabelmapNode: Failed to generate shared labelmap" ) return None # Transform shared labelmap to reference geometry coordinate system segmentationToReferenceGeometryTransform = referenceGeometryToSegmentationTransform.GetInverse( ) segmentationToReferenceGeometryTransform.Update() slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( sharedImage_Segmentation, referenceGeometry_Reference, mergedLabelmap_Reference, False, False, segmentationToReferenceGeometryTransform) return mergedLabelmap_Reference
def TestSection_MarginEffects(self): logging.info("Running test on margin effect") slicer.modules.segmenteditor.widgetRepresentation().self().editor.effectByName("Margin") self.segmentation.RemoveAllSegments() segment1Id = self.segmentation.AddEmptySegment("Segment_1") segment1 = self.segmentation.GetSegment(segment1Id) segment1.SetLabelValue(1) segment2Id = self.segmentation.AddEmptySegment("Segment_2") segment2 = self.segmentation.GetSegment(segment2Id) segment2.SetLabelValue(2) binaryLabelmapRepresentationName = slicer.vtkSegmentationConverter.GetBinaryLabelmapRepresentationName() dataTypes = [ vtk.VTK_CHAR, vtk.VTK_SIGNED_CHAR, vtk.VTK_UNSIGNED_CHAR, vtk.VTK_SHORT, vtk.VTK_UNSIGNED_SHORT, vtk.VTK_INT, vtk.VTK_UNSIGNED_INT, vtk.VTK_LONG, vtk.VTK_UNSIGNED_LONG, vtk.VTK_FLOAT, vtk.VTK_DOUBLE, #vtk.VTK_LONG_LONG, # These types are unsupported in ITK #vtk.VTK_UNSIGNED_LONG_LONG, ] logging.info("Testing shared labelmaps") for dataType in dataTypes: initialLabelmap = slicer.vtkOrientedImageData() initialLabelmap.SetImageToWorldMatrix(self.ijkToRas) initialLabelmap.SetExtent(0, 10, 0, 10, 0, 10) segment1.AddRepresentation(binaryLabelmapRepresentationName, initialLabelmap) segment2.AddRepresentation(binaryLabelmapRepresentationName, initialLabelmap) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteAllSegments) self.assertEqual(self.segmentation.GetNumberOfLayers(), 1) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteNone) self.assertEqual(self.segmentation.GetNumberOfLayers(), 2) logging.info("Testing separate labelmaps") for dataType in dataTypes: segment1Labelmap = slicer.vtkOrientedImageData() segment1Labelmap.SetImageToWorldMatrix(self.ijkToRas) segment1Labelmap.SetExtent(0, 10, 0, 10, 0, 10) segment1.AddRepresentation(binaryLabelmapRepresentationName, segment1Labelmap) segment2Labelmap = slicer.vtkOrientedImageData() segment2Labelmap.DeepCopy(segment1Labelmap) segment2.AddRepresentation(binaryLabelmapRepresentationName, segment2Labelmap) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteAllSegments) self.assertEqual(self.segmentation.GetNumberOfLayers(), 2) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteNone) self.assertEqual(self.segmentation.GetNumberOfLayers(), 2)
def paintApply(self, viewWidget): # Current limitation: smoothing brush is not implemented for joint smoothing smoothingMethod = self.scriptedEffect.parameter("SmoothingMethod") if smoothingMethod == JOINT_TAUBIN: self.scriptedEffect.clearBrushes() self.scriptedEffect.forceRender(viewWidget) slicer.util.messageBox( "Smoothing brush is not available for 'joint smoothing' method." ) return modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap() maskImage = slicer.vtkOrientedImageData() maskImage.DeepCopy(modifierLabelmap) maskExtent = self.scriptedEffect.paintBrushesIntoLabelmap( maskImage, viewWidget) self.scriptedEffect.clearBrushes() self.scriptedEffect.forceRender(viewWidget) if maskExtent[0] > maskExtent[1] or maskExtent[2] > maskExtent[ 3] or maskExtent[4] > maskExtent[5]: return self.scriptedEffect.saveStateForUndo() self.onApply(maskImage, maskExtent)
def onApply(self): self.delayedAutoUpdateTimer.stop() self.observeSegmentation(False) import vtkSegmentationCorePython as vtkSegmentationCore segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode() segmentationDisplayNode = segmentationNode.GetDisplayNode() previewNode = self.getPreviewNode() self.scriptedEffect.saveStateForUndo() previewContainsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation( slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName()) # Move segments from preview into current segmentation segmentIDs = vtk.vtkStringArray() previewNode.GetSegmentation().GetSegmentIDs(segmentIDs) for index in range(segmentIDs.GetNumberOfValues()): segmentID = segmentIDs.GetValue(index) previewSegmentLabelmap = slicer.vtkOrientedImageData() previewNode.GetBinaryLabelmapRepresentation(segmentID, previewSegmentLabelmap) self.scriptedEffect.modifySegmentByLabelmap(segmentationNode, segmentID, previewSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet) if segmentationDisplayNode is not None and self.isBackgroundLabelmap(previewSegmentLabelmap): # Automatically hide result segments that are background (all eight corners are non-zero) segmentationDisplayNode.SetSegmentVisibility(segmentID, False) previewNode.GetSegmentation().RemoveSegment(segmentID) # delete now to limit memory usage if previewContainsClosedSurfaceRepresentation: segmentationNode.CreateClosedSurfaceRepresentation() self.reset()
def modifySelectedSegmentByLabelmap(self, smoothedImage, selectedSegmentLabelmap, modifierLabelmap, maskImage, maskExtent): if maskImage: smoothedClippedSelectedSegmentLabelmap = slicer.vtkOrientedImageData() smoothedClippedSelectedSegmentLabelmap.ShallowCopy(smoothedImage) smoothedClippedSelectedSegmentLabelmap.CopyDirections(modifierLabelmap) # fill smoothed selected segment outside the painted region to 1 so that in the end the image is not modified by OPERATION_MINIMUM fillValue = 1.0 slicer.vtkOrientedImageDataResample.ApplyImageMask(smoothedClippedSelectedSegmentLabelmap, maskImage, fillValue, False) # set original segment labelmap outside painted region, solid 1 inside painted region slicer.vtkOrientedImageDataResample.ModifyImage(maskImage, selectedSegmentLabelmap, slicer.vtkOrientedImageDataResample.OPERATION_MAXIMUM) slicer.vtkOrientedImageDataResample.ModifyImage(maskImage, smoothedClippedSelectedSegmentLabelmap, slicer.vtkOrientedImageDataResample.OPERATION_MINIMUM) updateExtent = [0, -1, 0, -1, 0, -1] modifierExtent = modifierLabelmap.GetExtent() for i in range(3): updateExtent[2 * i] = min(maskExtent[2 * i], modifierExtent[2 * i]) updateExtent[2 * i + 1] = max(maskExtent[2 * i + 1], modifierExtent[2 * i + 1]) self.scriptedEffect.modifySelectedSegmentByLabelmap(maskImage, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, updateExtent) else: modifierLabelmap.DeepCopy(smoothedImage) self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
def runGrowCut(self, masterImageData, seedLabelmap, outputLabelmap): self.clippedMaskImageData = slicer.vtkOrientedImageData() intensityBasedMasking = self.scriptedEffect.parameterSetNode( ).GetMasterVolumeIntensityMask() segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() success = segmentationNode.GenerateEditMask( self.clippedMaskImageData, self.scriptedEffect.parameterSetNode().GetMaskMode(), masterImageData, # reference geometry "", # edited segment ID self.scriptedEffect.parameterSetNode().GetMaskSegmentID() if self.scriptedEffect.parameterSetNode().GetMaskSegmentID() else "", masterImageData if intensityBasedMasking else None, self.scriptedEffect.parameterSetNode( ).GetMasterVolumeIntensityMaskRange() if intensityBasedMasking else None) import vtkSlicerSegmentationsModuleLogicPython as vtkSlicerSegmentationsModuleLogic self.growCutFilter = vtkSlicerSegmentationsModuleLogic.vtkImageGrowCutSegment( ) self.growCutFilter.SetIntensityVolume(masterImageData) self.growCutFilter.SetSeedLabelVolume(seedLabelmap) self.growCutFilter.SetMaskVolume(self.clippedMaskImageData) self.growCutFilter.Update() outputLabelmap.ShallowCopy(self.growCutFilter.GetOutput())
def effectiveExtentChanged(self): if self.mergedLabelmapGeometryImage is None: return True if self.selectedSegmentIds is None: return True import vtkSegmentationCorePython as vtkSegmentationCore segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode() # The effective extent for the current input segments effectiveGeometryImage = slicer.vtkOrientedImageData() effectiveGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry( vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds) if effectiveGeometryString is None: return True vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(effectiveGeometryString, effectiveGeometryImage) masterImageData = self.scriptedEffect.masterVolumeImageData() masterImageExtent = masterImageData.GetExtent() # The effective extent of the selected segments effectiveLabelExtent = effectiveGeometryImage.GetExtent() # Current extent used for auto-complete preview currentLabelExtent = self.mergedLabelmapGeometryImage.GetExtent() # Determine if the current merged labelmap extent has less than a 3 voxel margin around the effective segment extent (limited by the master image extent) return ((masterImageExtent[0] != currentLabelExtent[0] and currentLabelExtent[0] > effectiveLabelExtent[0] - self.minimumExtentMargin) or (masterImageExtent[1] != currentLabelExtent[1] and currentLabelExtent[1] < effectiveLabelExtent[1] + self.minimumExtentMargin) or (masterImageExtent[2] != currentLabelExtent[2] and currentLabelExtent[2] > effectiveLabelExtent[2] - self.minimumExtentMargin) or (masterImageExtent[3] != currentLabelExtent[3] and currentLabelExtent[3] < effectiveLabelExtent[3] + self.minimumExtentMargin) or (masterImageExtent[4] != currentLabelExtent[4] and currentLabelExtent[4] > effectiveLabelExtent[4] - self.minimumExtentMargin) or (masterImageExtent[5] != currentLabelExtent[5] and currentLabelExtent[5] < effectiveLabelExtent[5] + self.minimumExtentMargin))
def _updateInputPd(self): segment = self.segmentationNode.GetSegmentation().GetSegment( self.segmentId) self._inputPd = vtk.vtkPolyData() # Get input polydata and input spacing if self.segmentationNode.GetSegmentation().GetMasterRepresentationName( ) == slicer.vtkSegmentationConverter( ).GetSegmentationBinaryLabelmapRepresentationName(): # Master representation is binary labelmap # Reconvert to closed surface using chosen chosen smoothing factor originalSurfaceSmoothing = float( self.segmentationNode.GetSegmentation().GetConversionParameter( slicer.vtkBinaryLabelmapToClosedSurfaceConversionRule( ).GetSmoothingFactorParameterName())) if abs(originalSurfaceSmoothing - self.smoothingFactor) > 0.001: self.segmentationNode.GetSegmentation().SetConversionParameter( slicer.vtkBinaryLabelmapToClosedSurfaceConversionRule( ).GetSmoothingFactorParameterName(), str(self.smoothingFactor)) # Force re-conversion self.segmentationNode.RemoveClosedSurfaceRepresentation() self.segmentationNode.CreateClosedSurfaceRepresentation() self.segmentationNode.GetClosedSurfaceRepresentation( self.segmentId, self._inputPd) if self._inputPd.GetNumberOfPoints() == 0: raise ValueError( "Input segment closed surface representation is empty") # Get input spacing inputLabelmap = slicer.vtkOrientedImageData() self.segmentationNode.GetBinaryLabelmapRepresentation( self.segmentId, inputLabelmap) extent = inputLabelmap.GetExtent() if extent[0] > extent[1] or extent[2] > extent[3] or extent[ 4] > extent[5]: raise ValueError( "Input segment labelmap representation is empty") self._inputSpacing = math.sqrt( np.sum(np.array(inputLabelmap.GetSpacing())**2)) else: # Representation is already closed surface self.segmentationNode.CreateClosedSurfaceRepresentation() self.segmentationNode.GetClosedSurfaceRepresentation( self.segmentId, self._inputPd) # set spacing to have an approxmately 250^3 volume # this size is not too large for average computing hardware yet # it is sufficiently detailed for many applications preferredVolumeSizeInVoxels = 250 * 250 * 250 bounds = np.zeros(6) self._inputPd.GetBounds(bounds) volumeSizeInMm3 = (bounds[1] - bounds[0]) * ( bounds[3] - bounds[2]) * (bounds[5] - bounds[4]) self._inputSpacing = pow( volumeSizeInMm3 / preferredVolumeSizeInVoxels, 1 / 3.)
def clipImage(self, inputImage, maskExtent, margin): clipper = vtk.vtkImageClip() clipper.SetOutputWholeExtent(maskExtent[0] - margin[0], maskExtent[1] + margin[0], maskExtent[2] - margin[1], maskExtent[3] + margin[1], maskExtent[4] - margin[2], maskExtent[5] + margin[2]) clipper.SetInputData(inputImage) clipper.SetClipData(True) clipper.Update() clippedImage = slicer.vtkOrientedImageData() clippedImage.ShallowCopy(clipper.GetOutput()) clippedImage.CopyDirections(inputImage) return clippedImage
def sitkImageToVtkOrientedImage(self, img): imgNode = sitkUtils.PushVolumeToSlicer(img) vtkImage = imgNode.GetImageData() vtkOrientedImage = slicer.vtkOrientedImageData() vtkOrientedImage.DeepCopy(vtkImage) dir = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] imgNode.GetIJKToRASDirections(dir) vtkOrientedImage.SetDirections([dir[0], dir[1], dir[2]]) vtkOrientedImage.SetOrigin(imgNode.GetOrigin()) vtkOrientedImage.SetSpacing(imgNode.GetSpacing()) slicer.mrmlScene.RemoveNode(imgNode) return vtkOrientedImage
def checkSegmentVoxelCount(self, segmentIndex, expectedVoxelCount): segment = self.segmentation.GetNthSegment(segmentIndex) self.assertIsNotNone(segment) labelmap = slicer.vtkOrientedImageData() segmentID = self.segmentation.GetNthSegmentID(segmentIndex) self.segmentationNode.GetBinaryLabelmapRepresentation(segmentID, labelmap) imageStat = vtk.vtkImageAccumulate() imageStat.SetInputData(labelmap) imageStat.SetComponentExtent(0,4,0,0,0,0) imageStat.SetComponentOrigin(0,0,0) imageStat.SetComponentSpacing(1,1,1) imageStat.IgnoreZeroOn() imageStat.Update() self.assertEqual(imageStat.GetVoxelCount(), expectedVoxelCount)
def getInvertedBinaryLabelmap(self, modifierLabelmap): fillValue = 1 eraseValue = 0 inverter = vtk.vtkImageThreshold() inverter.SetInputData(modifierLabelmap) inverter.SetInValue(fillValue) inverter.SetOutValue(eraseValue) inverter.ReplaceInOn() inverter.ThresholdByLower(0) inverter.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR) inverter.Update() invertedModifierLabelmap = slicer.vtkOrientedImageData() invertedModifierLabelmap.ShallowCopy(inverter.GetOutput()) imageToWorldMatrix = vtk.vtkMatrix4x4() modifierLabelmap.GetImageToWorldMatrix(imageToWorldMatrix) invertedModifierLabelmap.SetGeometryFromImageToWorldMatrix( imageToWorldMatrix) return invertedModifierLabelmap
def cropOrientedImage(masterImageData, roiNode): """Clip master image data with annotation ROI and return result in a new vtkOrientedImageData""" # This is a utility function, also used in FloodFilling effect. # Probably we should apply relative transform between ROI and master image data node worldToImageMatrix = vtk.vtkMatrix4x4() masterImageData.GetWorldToImageMatrix(worldToImageMatrix) bounds = [0, 0, 0, 0, 0, 0] roiNode.GetRASBounds(bounds) corner1RAS = [bounds[0], bounds[2], bounds[4], 1] corner1IJK = [0, 0, 0, 0] worldToImageMatrix.MultiplyPoint(corner1RAS, corner1IJK) corner2RAS = [bounds[1], bounds[3], bounds[5], 1] corner2IJK = [0, 0, 0, 0] worldToImageMatrix.MultiplyPoint(corner2RAS, corner2IJK) extent = [0, -1, 0, -1, 0, -1] for i in range(3): lowerPoint = min(corner1IJK[i], corner2IJK[i]) upperPoint = max(corner1IJK[i], corner2IJK[i]) extent[2 * i] = int(math.floor(lowerPoint)) extent[2 * i + 1] = int(math.ceil(upperPoint)) imageToWorldMatrix = vtk.vtkMatrix4x4() masterImageData.GetImageToWorldMatrix(imageToWorldMatrix) clippedMasterImageData = slicer.vtkOrientedImageData() padder = vtk.vtkImageConstantPad() padder.SetInputData(masterImageData) padder.SetOutputWholeExtent(extent) padder.Update() clippedMasterImageData.ShallowCopy(padder.GetOutput()) clippedMasterImageData.SetImageToWorldMatrix(imageToWorldMatrix) return clippedMasterImageData
def processInteractionEvents(self, callerInteractor, eventId, viewWidget): import vtkSegmentationCorePython as vtkSegmentationCore abortEvent = False # Only allow in modes where segment selection is needed if not self.currentOperationRequiresSegmentSelection(): return False # Only allow for slice views if viewWidget.className() != "qMRMLSliceWidget": return abortEvent if eventId != vtk.vtkCommand.LeftButtonPressEvent: return abortEvent abortEvent = True # Generate merged labelmap of all visible segments segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() visibleSegmentIds = vtk.vtkStringArray() segmentationNode.GetDisplayNode().GetVisibleSegmentIDs( visibleSegmentIds) if visibleSegmentIds.GetNumberOfValues() == 0: logging.info( "Smoothing operation skipped: there are no visible segments") return abortEvent self.scriptedEffect.saveStateForUndo() # This can be a long operation - indicate it to the user qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) operationName = self.scriptedEffect.parameter("Operation") if operationName == ADD_SELECTED_ISLAND: inputLabelImage = slicer.vtkOrientedImageData() if not segmentationNode.GenerateMergedLabelmapForAllSegments( inputLabelImage, vtkSegmentationCore.vtkSegmentation. EXTENT_UNION_OF_SEGMENTS_PADDED, None, visibleSegmentIds): logging.error( 'Failed to apply smoothing: cannot get list of visible segments' ) qt.QApplication.restoreOverrideCursor() return abortEvent else: selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap( ) # We need to know exactly the value of the segment voxels, apply threshold to make force the selected label value labelValue = 1 backgroundValue = 0 thresh = vtk.vtkImageThreshold() thresh.SetInputData(selectedSegmentLabelmap) thresh.ThresholdByLower(0) thresh.SetInValue(backgroundValue) thresh.SetOutValue(labelValue) thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType()) thresh.Update() # Create oriented image data from output import vtkSegmentationCorePython as vtkSegmentationCore inputLabelImage = slicer.vtkOrientedImageData() inputLabelImage.ShallowCopy(thresh.GetOutput()) selectedSegmentLabelmapImageToWorldMatrix = vtk.vtkMatrix4x4() selectedSegmentLabelmap.GetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) inputLabelImage.SetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) xy = callerInteractor.GetEventPosition() ijk = self.xyToIjk(xy, viewWidget, inputLabelImage, segmentationNode.GetParentTransformNode()) pixelValue = inputLabelImage.GetScalarComponentAsFloat( ijk[0], ijk[1], ijk[2], 0) try: floodFillingFilter = vtk.vtkImageThresholdConnectivity() floodFillingFilter.SetInputData(inputLabelImage) seedPoints = vtk.vtkPoints() origin = inputLabelImage.GetOrigin() spacing = inputLabelImage.GetSpacing() seedPoints.InsertNextPoint(origin[0] + ijk[0] * spacing[0], origin[1] + ijk[1] * spacing[1], origin[2] + ijk[2] * spacing[2]) floodFillingFilter.SetSeedPoints(seedPoints) floodFillingFilter.ThresholdBetween(pixelValue, pixelValue) if operationName == ADD_SELECTED_ISLAND: floodFillingFilter.SetInValue(1) floodFillingFilter.SetOutValue(0) floodFillingFilter.Update() modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap( ) modifierLabelmap.DeepCopy(floodFillingFilter.GetOutput()) self.scriptedEffect.modifySelectedSegmentByLabelmap( modifierLabelmap, slicer. qSlicerSegmentEditorAbstractEffect.ModificationModeAdd) elif pixelValue != 0: # if clicked on empty part then there is nothing to remove or keep if operationName == KEEP_SELECTED_ISLAND: floodFillingFilter.SetInValue(1) floodFillingFilter.SetOutValue(0) else: # operationName == REMOVE_SELECTED_ISLAND: floodFillingFilter.SetInValue(1) floodFillingFilter.SetOutValue(0) floodFillingFilter.Update() modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap( ) modifierLabelmap.DeepCopy(floodFillingFilter.GetOutput()) if operationName == KEEP_SELECTED_ISLAND: self.scriptedEffect.modifySelectedSegmentByLabelmap( modifierLabelmap, slicer. qSlicerSegmentEditorAbstractEffect.ModificationModeSet) else: # operationName == REMOVE_SELECTED_ISLAND: self.scriptedEffect.modifySelectedSegmentByLabelmap( modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect. ModificationModeRemove) except IndexError: logging.error('apply: Failed to threshold master volume!') finally: qt.QApplication.restoreOverrideCursor() return abortEvent
def splitSegments(self, minimumSize=0, maxNumberOfSegments=0, split=True): """ minimumSize: if 0 then it means that all islands are kept, regardless of size maxNumberOfSegments: if 0 then it means that all islands are kept, regardless of how many """ # This can be a long operation - indicate it to the user qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) self.scriptedEffect.saveStateForUndo() # Get modifier labelmap selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap() castIn = vtk.vtkImageCast() castIn.SetInputData(selectedSegmentLabelmap) castIn.SetOutputScalarTypeToUnsignedInt() # Identify the islands in the inverted volume and # find the pixel that corresponds to the background islandMath = vtkITK.vtkITKIslandMath() islandMath.SetInputConnection(castIn.GetOutputPort()) islandMath.SetFullyConnected(False) islandMath.SetMinimumSize(minimumSize) islandMath.Update() islandImage = slicer.vtkOrientedImageData() islandImage.ShallowCopy(islandMath.GetOutput()) selectedSegmentLabelmapImageToWorldMatrix = vtk.vtkMatrix4x4() selectedSegmentLabelmap.GetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) islandImage.SetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) islandCount = islandMath.GetNumberOfIslands() islandOrigCount = islandMath.GetOriginalNumberOfIslands() ignoredIslands = islandOrigCount - islandCount logging.info("%d islands created (%d ignored)" % (islandCount, ignoredIslands)) baseSegmentName = "Label" selectedSegmentID = self.scriptedEffect.parameterSetNode( ).GetSelectedSegmentID() segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() with slicer.util.NodeModify(segmentationNode): segmentation = segmentationNode.GetSegmentation() selectedSegment = segmentation.GetSegment(selectedSegmentID) selectedSegmentName = selectedSegment.GetName() if selectedSegmentName is not None and selectedSegmentName != "": baseSegmentName = selectedSegmentName labelValues = vtk.vtkIntArray() slicer.vtkSlicerSegmentationsModuleLogic.GetAllLabelValues( labelValues, islandImage) # Erase segment from in original labelmap. # Individuall islands will be added back later. threshold = vtk.vtkImageThreshold() threshold.SetInputData(selectedSegmentLabelmap) threshold.ThresholdBetween(0, 0) threshold.SetInValue(0) threshold.SetOutValue(0) threshold.Update() emptyLabelmap = slicer.vtkOrientedImageData() emptyLabelmap.ShallowCopy(threshold.GetOutput()) emptyLabelmap.CopyDirections(selectedSegmentLabelmap) self.scriptedEffect.modifySegmentByLabelmap( segmentationNode, selectedSegmentID, emptyLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet) for i in range(labelValues.GetNumberOfTuples()): if (maxNumberOfSegments > 0 and i >= maxNumberOfSegments): # We only care about the segments up to maxNumberOfSegments. # If we do not want to split segments, we only care about the first. break labelValue = int(labelValues.GetTuple1(i)) segment = selectedSegment segmentID = selectedSegmentID if i != 0 and split: segment = slicer.vtkSegment() name = baseSegmentName + "_" + str(i + 1) segment.SetName(name) segment.AddRepresentation( slicer.vtkSegmentationConverter. GetSegmentationBinaryLabelmapRepresentationName(), selectedSegment.GetRepresentation( slicer.vtkSegmentationConverter. GetSegmentationBinaryLabelmapRepresentationName())) segmentation.AddSegment(segment) segmentID = segmentation.GetSegmentIdBySegment(segment) segment.SetLabelValue( segmentation.GetUniqueLabelValueForSharedLabelmap( selectedSegmentID)) threshold = vtk.vtkImageThreshold() threshold.SetInputData(islandMath.GetOutput()) threshold.ThresholdBetween(labelValue, labelValue) threshold.SetInValue(1) threshold.SetOutValue(0) threshold.Update() modificationMode = slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd if i == 0: modificationMode = slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet # Create oriented image data from output modifierImage = slicer.vtkOrientedImageData() modifierImage.DeepCopy(threshold.GetOutput()) selectedSegmentLabelmapImageToWorldMatrix = vtk.vtkMatrix4x4() selectedSegmentLabelmap.GetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) modifierImage.SetGeometryFromImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) # We could use a single slicer.vtkSlicerSegmentationsModuleLogic.ImportLabelmapToSegmentationNode # method call to import all the resulting segments at once but that would put all the imported segments # in a new layer. By using modifySegmentByLabelmap, the number of layers will not increase. self.scriptedEffect.modifySegmentByLabelmap( segmentationNode, segmentID, modifierImage, modificationMode) qt.QApplication.restoreOverrideCursor()
def onApply(self): import vtkSegmentationCorePython as vtkSegmentationCore self.scriptedEffect.saveStateForUndo() import vtkSegmentationCorePython as vtkSegmentationCore # Get modifier labelmap and parameters operation = self.scriptedEffect.parameter("Operation") bypassMasking = (self.scriptedEffect.integerParameter("BypassMasking") != 0) selectedSegmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID() segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode() segmentation = segmentationNode.GetSegmentation() if operation in self.operationsRequireModifierSegment: # Get modifier segment modifierSegmentID = self.modifierSegmentID() if not modifierSegmentID: logging.error("Operation {0} requires a selected modifier segment".format(operation)) return modifierSegment = segmentation.GetSegment(modifierSegmentID) modifierSegmentLabelmap = slicer.vtkOrientedImageData() segmentationNode.GetBinaryLabelmapRepresentation(modifierSegmentID, modifierSegmentLabelmap) # Get common geometry commonGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry( vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_SEGMENTS, None) if not commonGeometryString: logging.info("Logical operation skipped: all segments are empty") return commonGeometryImage = slicer.vtkOrientedImageData() vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(commonGeometryString, commonGeometryImage, False) # Make sure modifier segment has correct geometry # (if modifier segment has been just copied over from another segment then its geometry may be different) if not vtkSegmentationCore.vtkOrientedImageDataResample.DoGeometriesMatch(commonGeometryImage, modifierSegmentLabelmap): modifierSegmentLabelmap_CommonGeometry = slicer.vtkOrientedImageData() vtkSegmentationCore.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( modifierSegmentLabelmap, commonGeometryImage, modifierSegmentLabelmap_CommonGeometry, False, # nearest neighbor interpolation, True # make sure resampled modifier segment is not cropped ) modifierSegmentLabelmap = modifierSegmentLabelmap_CommonGeometry if operation == LOGICAL_COPY: self.scriptedEffect.modifySelectedSegmentByLabelmap( modifierSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, bypassMasking) elif operation == LOGICAL_UNION: self.scriptedEffect.modifySelectedSegmentByLabelmap( modifierSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd, bypassMasking) elif operation == LOGICAL_SUBTRACT: self.scriptedEffect.modifySelectedSegmentByLabelmap( modifierSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeRemove, bypassMasking) elif operation == LOGICAL_INTERSECT: selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap() intersectionLabelmap = slicer.vtkOrientedImageData() vtkSegmentationCore.vtkOrientedImageDataResample.MergeImage( selectedSegmentLabelmap, modifierSegmentLabelmap, intersectionLabelmap, vtkSegmentationCore.vtkOrientedImageDataResample.OPERATION_MINIMUM, selectedSegmentLabelmap.GetExtent()) selectedSegmentLabelmapExtent = selectedSegmentLabelmap.GetExtent() modifierSegmentLabelmapExtent = modifierSegmentLabelmap.GetExtent() commonExtent = [max(selectedSegmentLabelmapExtent[0], modifierSegmentLabelmapExtent[0]), min(selectedSegmentLabelmapExtent[1], modifierSegmentLabelmapExtent[1]), max(selectedSegmentLabelmapExtent[2], modifierSegmentLabelmapExtent[2]), min(selectedSegmentLabelmapExtent[3], modifierSegmentLabelmapExtent[3]), max(selectedSegmentLabelmapExtent[4], modifierSegmentLabelmapExtent[4]), min(selectedSegmentLabelmapExtent[5], modifierSegmentLabelmapExtent[5])] self.scriptedEffect.modifySelectedSegmentByLabelmap( intersectionLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, commonExtent, bypassMasking) elif operation == LOGICAL_INVERT: selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap() invertedSelectedSegmentLabelmap = self.getInvertedBinaryLabelmap(selectedSegmentLabelmap) self.scriptedEffect.modifySelectedSegmentByLabelmap( invertedSelectedSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, bypassMasking) elif operation == LOGICAL_CLEAR or operation == LOGICAL_FILL: selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap() vtkSegmentationCore.vtkOrientedImageDataResample.FillImage(selectedSegmentLabelmap, 1 if operation == LOGICAL_FILL else 0, selectedSegmentLabelmap.GetExtent()) self.scriptedEffect.modifySelectedSegmentByLabelmap( selectedSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, bypassMasking) else: logging.error("Unknown operation: {0}".format(operation))
def TestSection_ImportExportSegment2(self): # Testing sequential add of individual segments to a segmentation through ImportLabelmapToSegmentationNode logging.info('Test section: Import/export segment 2') # Export body segment to volume node bodySegment = self.inputSegmentationNode.GetSegmentation().GetSegment(self.bodySegmentName) bodyLabelmapNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode', 'BodyLabelmap') result = slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentToRepresentationNode(bodySegment, bodyLabelmapNode) self.assertTrue(result) bodyImageData = bodyLabelmapNode.GetImageData() self.assertIsNotNone(bodyImageData) imageStat = vtk.vtkImageAccumulate() imageStat.SetInputData(bodyImageData) imageStat.Update() self.assertEqual(imageStat.GetVoxelCount(), 792) self.assertEqual(imageStat.GetMin()[0], 0) self.assertEqual(imageStat.GetMax()[0], 1) # Export tumor segment to volume node tumorSegment = self.inputSegmentationNode.GetSegmentation().GetSegment(self.tumorSegmentName) tumorLabelmapNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode', 'TumorLabelmap') result = slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentToRepresentationNode(tumorSegment, tumorLabelmapNode) self.assertTrue(result) tumorImageData = tumorLabelmapNode.GetImageData() self.assertIsNotNone(tumorImageData) imageStat = vtk.vtkImageAccumulate() imageStat.SetInputData(tumorImageData) imageStat.Update() self.assertEqual(imageStat.GetVoxelCount(), 12) self.assertEqual(imageStat.GetMin()[0], 0) self.assertEqual(imageStat.GetMax()[0], 1) # Import single-label labelmap to segmentation singleLabelImportSegmentationNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode', 'SingleLabelImport') singleLabelImportSegmentationNode.GetSegmentation().SetMasterRepresentationName(self.binaryLabelmapReprName) bodySegmentID = singleLabelImportSegmentationNode.GetSegmentation().AddEmptySegment('BodyLabelmap') bodySegmentIDArray = vtk.vtkStringArray() bodySegmentIDArray.SetNumberOfValues(1) bodySegmentIDArray.SetValue(0, bodySegmentID) result = slicer.vtkSlicerSegmentationsModuleLogic.ImportLabelmapToSegmentationNode(bodyLabelmapNode, singleLabelImportSegmentationNode, bodySegmentIDArray) self.assertTrue(result) self.assertEqual(singleLabelImportSegmentationNode.GetSegmentation().GetNumberOfSegments(), 1) tumorSegmentID = singleLabelImportSegmentationNode.GetSegmentation().AddEmptySegment('TumorLabelmap') tumorSegmentIDArray = vtk.vtkStringArray() tumorSegmentIDArray.SetNumberOfValues(1) tumorSegmentIDArray.SetValue(0, tumorSegmentID) result = slicer.vtkSlicerSegmentationsModuleLogic.ImportLabelmapToSegmentationNode(tumorLabelmapNode, singleLabelImportSegmentationNode, tumorSegmentIDArray) self.assertTrue(result) self.assertEqual(singleLabelImportSegmentationNode.GetSegmentation().GetNumberOfSegments(), 2) bodyLabelmap = slicer.vtkOrientedImageData() singleLabelImportSegmentationNode.GetBinaryLabelmapRepresentation(bodySegmentID, bodyLabelmap) imageStat = vtk.vtkImageAccumulate() imageStat.SetInputData(bodyLabelmap) imageStat.Update() self.assertEqual(imageStat.GetVoxelCount(), 792) self.assertEqual(imageStat.GetMin()[0], 0) self.assertEqual(imageStat.GetMax()[0], 1) tumorLabelmap = slicer.vtkOrientedImageData() singleLabelImportSegmentationNode.GetBinaryLabelmapRepresentation(tumorSegmentID, tumorLabelmap) self.assertIsNotNone(tumorLabelmap) imageStat = vtk.vtkImageAccumulate() imageStat.SetInputData(tumorLabelmap) imageStat.Update() self.assertEqual(imageStat.GetVoxelCount(), 12) self.assertEqual(imageStat.GetMin()[0], 0) self.assertEqual(imageStat.GetMax()[0], 1) # Clean up temporary nodes slicer.mrmlScene.RemoveNode(bodyLabelmapNode) slicer.mrmlScene.RemoveNode(tumorLabelmapNode) slicer.mrmlScene.RemoveNode(singleLabelImportSegmentationNode)
def preview(self): # Get master volume image data import vtkSegmentationCorePython as vtkSegmentationCore # Get segmentation segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() previewNode = self.getPreviewNode() previewOpacity = self.getPreviewOpacity() previewShow3D = self.getPreviewShow3D() # If the selectedSegmentIds have been specified, then they shouldn't be overwritten here currentSelectedSegmentIds = self.selectedSegmentIds if self.effectiveExtentChanged(): self.reset() # Restore the selectedSegmentIds self.selectedSegmentIds = currentSelectedSegmentIds if self.selectedSegmentIds is None: self.selectedSegmentIds = vtk.vtkStringArray() segmentationNode.GetDisplayNode().GetVisibleSegmentIDs( self.selectedSegmentIds) if self.selectedSegmentIds.GetNumberOfValues( ) < self.minimumNumberOfSegments: logging.error( "Auto-complete operation skipped: at least {0} visible segments are required" .format(self.minimumNumberOfSegments)) self.selectedSegmentIds = None return # Compute merged labelmap extent (effective extent slightly expanded) if not self.mergedLabelmapGeometryImage: self.mergedLabelmapGeometryImage = slicer.vtkOrientedImageData( ) commonGeometryString = segmentationNode.GetSegmentation( ).DetermineCommonLabelmapGeometry( vtkSegmentationCore.vtkSegmentation. EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds) if not commonGeometryString: logging.info( "Auto-complete operation skipped: all visible segments are empty" ) return vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry( commonGeometryString, self.mergedLabelmapGeometryImage) masterImageData = self.scriptedEffect.masterVolumeImageData() masterImageExtent = masterImageData.GetExtent() labelsEffectiveExtent = self.mergedLabelmapGeometryImage.GetExtent( ) # Margin size is relative to combined seed region size, but minimum of 3 voxels print("self.extentGrowthRatio = {0}".format( self.extentGrowthRatio)) margin = [ int( max( 3, self.extentGrowthRatio * (labelsEffectiveExtent[1] - labelsEffectiveExtent[0]))), int( max( 3, self.extentGrowthRatio * (labelsEffectiveExtent[3] - labelsEffectiveExtent[2]))), int( max( 3, self.extentGrowthRatio * (labelsEffectiveExtent[5] - labelsEffectiveExtent[4]))) ] labelsExpandedExtent = [ max(masterImageExtent[0], labelsEffectiveExtent[0] - margin[0]), min(masterImageExtent[1], labelsEffectiveExtent[1] + margin[0]), max(masterImageExtent[2], labelsEffectiveExtent[2] - margin[1]), min(masterImageExtent[3], labelsEffectiveExtent[3] + margin[1]), max(masterImageExtent[4], labelsEffectiveExtent[4] - margin[2]), min(masterImageExtent[5], labelsEffectiveExtent[5] + margin[2]) ] print("masterImageExtent = " + repr(masterImageExtent)) print("labelsEffectiveExtent = " + repr(labelsEffectiveExtent)) print("labelsExpandedExtent = " + repr(labelsExpandedExtent)) self.mergedLabelmapGeometryImage.SetExtent(labelsExpandedExtent) # Create and setup preview node previewNode = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLSegmentationNode") previewNode.CreateDefaultDisplayNodes() previewNode.GetDisplayNode().SetVisibility2DOutline(False) if segmentationNode.GetParentTransformNode(): previewNode.SetAndObserveTransformNodeID( segmentationNode.GetParentTransformNode().GetID()) self.scriptedEffect.parameterSetNode().SetNodeReferenceID( ResultPreviewNodeReferenceRole, previewNode.GetID()) self.scriptedEffect.setCommonParameter( "SegmentationResultPreviewOwnerEffect", self.scriptedEffect.name) self.setPreviewOpacity(0.6) # Disable smoothing for closed surface generation to make it fast previewNode.GetSegmentation().SetConversionParameter( slicer.vtkBinaryLabelmapToClosedSurfaceConversionRule. GetSmoothingFactorParameterName(), "-0.5") inputContainsClosedSurfaceRepresentation = segmentationNode.GetSegmentation( ).ContainsRepresentation( slicer.vtkSegmentationConverter. GetSegmentationClosedSurfaceRepresentationName()) self.setPreviewShow3D(inputContainsClosedSurfaceRepresentation) if self.clippedMasterImageDataRequired: self.clippedMasterImageData = slicer.vtkOrientedImageData() masterImageClipper = vtk.vtkImageConstantPad() masterImageClipper.SetInputData(masterImageData) masterImageClipper.SetOutputWholeExtent( self.mergedLabelmapGeometryImage.GetExtent()) masterImageClipper.Update() self.clippedMasterImageData.ShallowCopy( masterImageClipper.GetOutput()) self.clippedMasterImageData.CopyDirections( self.mergedLabelmapGeometryImage) self.clippedMaskImageData = None if self.clippedMaskImageDataRequired: self.clippedMaskImageData = slicer.vtkOrientedImageData() intensityBasedMasking = self.scriptedEffect.parameterSetNode( ).GetMasterVolumeIntensityMask() success = segmentationNode.GenerateEditMask( self.clippedMaskImageData, self.scriptedEffect.parameterSetNode().GetMaskMode(), self.clippedMasterImageData, # reference geometry "", # edited segment ID self.scriptedEffect.parameterSetNode().GetMaskSegmentID() if self.scriptedEffect.parameterSetNode().GetMaskSegmentID() else "", self.clippedMasterImageData if intensityBasedMasking else None, self.scriptedEffect.parameterSetNode( ).GetMasterVolumeIntensityMaskRange() if intensityBasedMasking else None) if not success: logging.error("Failed to create edit mask") self.clippedMaskImageData = None previewNode.SetName(segmentationNode.GetName() + " preview") mergedImage = slicer.vtkOrientedImageData() segmentationNode.GenerateMergedLabelmapForAllSegments( mergedImage, vtkSegmentationCore.vtkSegmentation. EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.mergedLabelmapGeometryImage, self.selectedSegmentIds) outputLabelmap = slicer.vtkOrientedImageData() self.computePreviewLabelmap(mergedImage, outputLabelmap) # Write output segmentation results in segments for index in range(self.selectedSegmentIds.GetNumberOfValues()): segmentID = self.selectedSegmentIds.GetValue(index) segment = segmentationNode.GetSegmentation().GetSegment(segmentID) # Disable save with scene? # Get only the label of the current segment from the output image thresh = vtk.vtkImageThreshold() thresh.ReplaceInOn() thresh.ReplaceOutOn() thresh.SetInValue(1) thresh.SetOutValue(0) labelValue = index + 1 # n-th segment label value = n + 1 (background label value is 0) thresh.ThresholdBetween(labelValue, labelValue) thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR) thresh.SetInputData(outputLabelmap) thresh.Update() # Write label to segment newSegmentLabelmap = slicer.vtkOrientedImageData() newSegmentLabelmap.ShallowCopy(thresh.GetOutput()) newSegmentLabelmap.CopyDirections(mergedImage) newSegment = previewNode.GetSegmentation().GetSegment(segmentID) if not newSegment: newSegment = vtkSegmentationCore.vtkSegment() newSegment.SetName(segment.GetName()) color = segmentationNode.GetSegmentation().GetSegment( segmentID).GetColor() newSegment.SetColor(color) previewNode.GetSegmentation().AddSegment(newSegment, segmentID) self.scriptedEffect.modifySegmentByLabelmap( previewNode, segmentID, newSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet) # Automatically hide result segments that are background (all eight corners are non-zero) previewNode.GetDisplayNode().SetSegmentVisibility3D( segmentID, not self.isBackgroundLabelmap(newSegmentLabelmap)) # If the preview was reset, we need to restore the visibility options self.setPreviewOpacity(previewOpacity) self.setPreviewShow3D(previewShow3D) self.updateGUIFromMRML()
def TestSection_MarginEffects(self): logging.info("Running test on margin effect") slicer.modules.segmenteditor.widgetRepresentation().self( ).editor.effectByName("Margin") self.segmentation.RemoveAllSegments() segment1Id = self.segmentation.AddEmptySegment("Segment_1") segment1 = self.segmentation.GetSegment(segment1Id) segment1.SetLabelValue(1) segment2Id = self.segmentation.AddEmptySegment("Segment_2") segment2 = self.segmentation.GetSegment(segment2Id) segment2.SetLabelValue(2) binaryLabelmapRepresentationName = slicer.vtkSegmentationConverter.GetBinaryLabelmapRepresentationName( ) dataTypes = [ vtk.VTK_CHAR, vtk.VTK_SIGNED_CHAR, vtk.VTK_UNSIGNED_CHAR, vtk.VTK_SHORT, vtk.VTK_UNSIGNED_SHORT, vtk.VTK_INT, vtk.VTK_UNSIGNED_INT, #vtk.VTK_LONG, # On linux, VTK_LONG has the same size as VTK_LONG_LONG. This causes issues in vtkImageThreshold. #vtk.VTK_UNSIGNED_LONG, See https://github.com/Slicer/Slicer/issues/5427 #vtk.VTK_FLOAT, # Since float can't represent all int, we jump straight to double. vtk.VTK_DOUBLE, #vtk.VTK_LONG_LONG, # These types are unsupported in ITK #vtk.VTK_UNSIGNED_LONG_LONG, ] logging.info("Testing shared labelmaps") for dataType in dataTypes: initialLabelmap = slicer.vtkOrientedImageData() initialLabelmap.SetImageToWorldMatrix(self.ijkToRas) initialLabelmap.SetExtent(0, 10, 0, 10, 0, 10) initialLabelmap.AllocateScalars(dataType, 1) initialLabelmap.GetPointData().GetScalars().Fill(0) segment1.AddRepresentation(binaryLabelmapRepresentationName, initialLabelmap) segment2.AddRepresentation(binaryLabelmapRepresentationName, initialLabelmap) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteAllSegments) self.assertEqual(self.segmentation.GetNumberOfLayers(), 1) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteNone) self.assertEqual(self.segmentation.GetNumberOfLayers(), 2) logging.info("Testing separate labelmaps") for dataType in dataTypes: segment1Labelmap = slicer.vtkOrientedImageData() segment1Labelmap.SetImageToWorldMatrix(self.ijkToRas) segment1Labelmap.SetExtent(0, 10, 0, 10, 0, 10) segment1Labelmap.AllocateScalars(dataType, 1) segment1Labelmap.GetPointData().GetScalars().Fill(0) segment1.AddRepresentation(binaryLabelmapRepresentationName, segment1Labelmap) segment2Labelmap = slicer.vtkOrientedImageData() segment2Labelmap.DeepCopy(segment1Labelmap) segment2.AddRepresentation(binaryLabelmapRepresentationName, segment2Labelmap) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteAllSegments) self.assertEqual(self.segmentation.GetNumberOfLayers(), 2) self.runMarginEffect(segment1, segment2, dataType, self.segmentEditorNode.OverwriteNone) self.assertEqual(self.segmentation.GetNumberOfLayers(), 2)
def smoothMultipleSegments(self, maskImage=None, maskExtent=None): import vtkSegmentationCorePython as vtkSegmentationCore self.showStatusMessage(f'Joint smoothing ...') # Generate merged labelmap of all visible segments segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() visibleSegmentIds = vtk.vtkStringArray() segmentationNode.GetDisplayNode().GetVisibleSegmentIDs( visibleSegmentIds) if visibleSegmentIds.GetNumberOfValues() == 0: logging.info( "Smoothing operation skipped: there are no visible segments") return mergedImage = slicer.vtkOrientedImageData() if not segmentationNode.GenerateMergedLabelmapForAllSegments( mergedImage, vtkSegmentationCore.vtkSegmentation. EXTENT_UNION_OF_SEGMENTS_PADDED, None, visibleSegmentIds): logging.error( 'Failed to apply smoothing: cannot get list of visible segments' ) return segmentLabelValues = [] # list of [segmentId, labelValue] for i in range(visibleSegmentIds.GetNumberOfValues()): segmentId = visibleSegmentIds.GetValue(i) segmentLabelValues.append([segmentId, i + 1]) # Perform smoothing in voxel space ici = vtk.vtkImageChangeInformation() ici.SetInputData(mergedImage) ici.SetOutputSpacing(1, 1, 1) ici.SetOutputOrigin(0, 0, 0) # Convert labelmap to combined polydata # vtkDiscreteFlyingEdges3D cannot be used here, as in the output of that filter, # each labeled region is completely disconnected from neighboring regions, and # for joint smoothing it is essential for the points to move together. convertToPolyData = vtk.vtkDiscreteMarchingCubes() convertToPolyData.SetInputConnection(ici.GetOutputPort()) convertToPolyData.SetNumberOfContours(len(segmentLabelValues)) contourIndex = 0 for segmentId, labelValue in segmentLabelValues: convertToPolyData.SetValue(contourIndex, labelValue) contourIndex += 1 # Low-pass filtering using Taubin's method smoothingFactor = self.scriptedEffect.doubleParameter( "JointTaubinSmoothingFactor") smoothingIterations = 100 # according to VTK documentation 10-20 iterations could be enough but we use a higher value to reduce chance of shrinking passBand = pow( 10.0, -4.0 * smoothingFactor ) # gives a nice range of 1-0.0001 from a user input of 0-1 smoother = vtk.vtkWindowedSincPolyDataFilter() smoother.SetInputConnection(convertToPolyData.GetOutputPort()) smoother.SetNumberOfIterations(smoothingIterations) smoother.BoundarySmoothingOff() smoother.FeatureEdgeSmoothingOff() smoother.SetFeatureAngle(90.0) smoother.SetPassBand(passBand) smoother.NonManifoldSmoothingOn() smoother.NormalizeCoordinatesOn() # Extract a label threshold = vtk.vtkThreshold() threshold.SetInputConnection(smoother.GetOutputPort()) # Convert to polydata geometryFilter = vtk.vtkGeometryFilter() geometryFilter.SetInputConnection(threshold.GetOutputPort()) # Convert polydata to stencil polyDataToImageStencil = vtk.vtkPolyDataToImageStencil() polyDataToImageStencil.SetInputConnection( geometryFilter.GetOutputPort()) polyDataToImageStencil.SetOutputSpacing(1, 1, 1) polyDataToImageStencil.SetOutputOrigin(0, 0, 0) polyDataToImageStencil.SetOutputWholeExtent(mergedImage.GetExtent()) # Convert stencil to image stencil = vtk.vtkImageStencil() emptyBinaryLabelMap = vtk.vtkImageData() emptyBinaryLabelMap.SetExtent(mergedImage.GetExtent()) emptyBinaryLabelMap.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1) vtkSegmentationCore.vtkOrientedImageDataResample.FillImage( emptyBinaryLabelMap, 0) stencil.SetInputData(emptyBinaryLabelMap) stencil.SetStencilConnection(polyDataToImageStencil.GetOutputPort()) stencil.ReverseStencilOn() stencil.SetBackgroundValue( 1 ) # General foreground value is 1 (background value because of reverse stencil) imageToWorldMatrix = vtk.vtkMatrix4x4() mergedImage.GetImageToWorldMatrix(imageToWorldMatrix) # TODO: Temporarily setting the overwrite mode to OverwriteVisibleSegments is an approach that should be change once additional # layer control options have been implemented. Users may wish to keep segments on separate layers, and not allow them to be separated/merged automatically. # This effect could leverage those options once they have been implemented. oldOverwriteMode = self.scriptedEffect.parameterSetNode( ).GetOverwriteMode() self.scriptedEffect.parameterSetNode().SetOverwriteMode( slicer.vtkMRMLSegmentEditorNode.OverwriteVisibleSegments) for segmentId, labelValue in segmentLabelValues: threshold.ThresholdBetween(labelValue, labelValue) stencil.Update() smoothedBinaryLabelMap = slicer.vtkOrientedImageData() smoothedBinaryLabelMap.ShallowCopy(stencil.GetOutput()) smoothedBinaryLabelMap.SetImageToWorldMatrix(imageToWorldMatrix) self.scriptedEffect.modifySegmentByLabelmap( segmentationNode, segmentId, smoothedBinaryLabelMap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, False) self.scriptedEffect.parameterSetNode().SetOverwriteMode( oldOverwriteMode)
def TestSection_03_qMRMLSegmentationGeometryWidget(self): logging.info('Test section 2: qMRMLSegmentationGeometryWidget') binaryLabelmapReprName = slicer.vtkSegmentationConverter.GetBinaryLabelmapRepresentationName( ) closedSurfaceReprName = slicer.vtkSegmentationConverter.GetClosedSurfaceRepresentationName( ) # Use MRHead and Tinypatient for testing import SampleData mrVolumeNode = SampleData.downloadSample("MRHead") [tinyVolumeNode, tinySegmentationNode] = SampleData.downloadSamples('TinyPatient') # Convert MRHead to oriented image data import vtkSlicerSegmentationsModuleLogicPython as vtkSlicerSegmentationsModuleLogic mrOrientedImageData = vtkSlicerSegmentationsModuleLogic.vtkSlicerSegmentationsModuleLogic.CreateOrientedImageDataFromVolumeNode( mrVolumeNode) mrOrientedImageData.UnRegister(None) # Create segmentation node with binary labelmap master and one segment with MRHead geometry segmentationNode = slicer.mrmlScene.AddNewNodeByClass( 'vtkMRMLSegmentationNode') segmentationNode.GetSegmentation().SetMasterRepresentationName( binaryLabelmapReprName) geometryStr = slicer.vtkSegmentationConverter.SerializeImageGeometry( mrOrientedImageData) segmentationNode.GetSegmentation().SetConversionParameter( slicer.vtkSegmentationConverter. GetReferenceImageGeometryParameterName(), geometryStr) threshold = vtk.vtkImageThreshold() threshold.SetInputData(mrOrientedImageData) threshold.ThresholdByUpper(16.0) threshold.SetInValue(1) threshold.SetOutValue(0) threshold.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR) threshold.Update() segmentOrientedImageData = slicer.vtkOrientedImageData() segmentOrientedImageData.DeepCopy(threshold.GetOutput()) mrImageToWorldMatrix = vtk.vtkMatrix4x4() mrOrientedImageData.GetImageToWorldMatrix(mrImageToWorldMatrix) segmentOrientedImageData.SetImageToWorldMatrix(mrImageToWorldMatrix) segment = slicer.vtkSegment() segment.SetName('Brain') segment.SetColor(0.0, 0.0, 1.0) segment.AddRepresentation(binaryLabelmapReprName, segmentOrientedImageData) segmentationNode.GetSegmentation().AddSegment(segment) # Create geometry widget geometryWidget = slicer.qMRMLSegmentationGeometryWidget() geometryWidget.setSegmentationNode(segmentationNode) geometryWidget.editEnabled = True geometryImageData = slicer.vtkOrientedImageData( ) # To contain the output later # Volume source with no transforms geometryWidget.setSourceNode(tinyVolumeNode) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (49, 49, 23), (248.8439, 248.2890, -123.75), [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 92) # Transformed volume source translationTransformMatrix = vtk.vtkMatrix4x4() translationTransformMatrix.SetElement(0, 3, 24.5) translationTransformMatrix.SetElement(1, 3, 24.5) translationTransformMatrix.SetElement(2, 3, 11.5) translationTransformNode = slicer.vtkMRMLLinearTransformNode() translationTransformNode.SetName('TestTranslation') slicer.mrmlScene.AddNode(translationTransformNode) translationTransformNode.SetMatrixTransformToParent( translationTransformMatrix) tinyVolumeNode.SetAndObserveTransformNodeID( translationTransformNode.GetID()) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (49, 49, 23), (273.3439, 272.7890, -112.25), [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 94) # Volume source with isotropic spacing tinyVolumeNode.SetAndObserveTransformNodeID(None) geometryWidget.setIsotropicSpacing(True) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (23, 23, 23), (248.8439, 248.2890, -123.75), [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 414) # Volume source with oversampling geometryWidget.setIsotropicSpacing(False) geometryWidget.setOversamplingFactor(2.0) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (24.5, 24.5, 11.5), (261.0939, 260.5390, -129.5), [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 751) slicer.util.delayDisplay('Volume source cases - OK') # Segmentation source with binary labelmap master geometryWidget.setOversamplingFactor(1.0) geometryWidget.setSourceNode(tinySegmentationNode) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (49, 49, 23), (248.8439, 248.2890, -123.75), [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 92) # Segmentation source with closed surface master tinySegmentationNode.GetSegmentation().SetConversionParameter( 'Smoothing factor', '0.0') self.assertTrue( tinySegmentationNode.GetSegmentation().CreateRepresentation( closedSurfaceReprName)) tinySegmentationNode.GetSegmentation().SetMasterRepresentationName( closedSurfaceReprName) tinySegmentationNode.Modified( ) # Trigger re-calculation of geometry (only generic Modified event is observed) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (1, 1, 1), (-86.645, 133.929, 116.786), # current origin of the segmentation is kept [[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 5223040) slicer.util.delayDisplay('Segmentation source cases - OK') # Model source with no transform shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode( slicer.mrmlScene) outputFolderId = shNode.CreateFolderItem(shNode.GetSceneItemID(), 'ModelsFolder') success = vtkSlicerSegmentationsModuleLogic.vtkSlicerSegmentationsModuleLogic.ExportVisibleSegmentsToModels( tinySegmentationNode, outputFolderId) self.assertTrue(success) modelNode = slicer.util.getNode('Body_Contour') geometryWidget.setSourceNode(modelNode) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (1, 1, 1), (-86.645, 133.929, 116.786), # current origin of the segmentation is kept [[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 5223040) # Transformed model source rotationTransform = vtk.vtkTransform() rotationTransform.RotateX(45) rotationTransformMatrix = vtk.vtkMatrix4x4() rotationTransform.GetMatrix(rotationTransformMatrix) rotationTransformNode = slicer.vtkMRMLLinearTransformNode() rotationTransformNode.SetName('TestRotation') slicer.mrmlScene.AddNode(rotationTransformNode) rotationTransformNode.SetMatrixTransformToParent( rotationTransformMatrix) modelNode.SetAndObserveTransformNodeID(rotationTransformNode.GetID()) modelNode.Modified() geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (1, 1, 1), (-86.645, 177.282, -12.122), [[0.0, 0.0, 1.0], [-0.7071, -0.7071, 0.0], [0.7071, -0.7071, 0.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 5229164) # ROI source roiNode = slicer.mrmlScene.AddNewNodeByClass( "vtkMRMLAnnotationROINode", 'SourceROI') xyz = [0.0, 0.0, 0.0] center = [0.0, 0.0, 0.0] slicer.vtkMRMLSliceLogic.GetVolumeRASBox(tinyVolumeNode, xyz, center) radius = [x / 2.0 for x in xyz] roiNode.SetXYZ(center) roiNode.SetRadiusXYZ(radius) geometryWidget.setSourceNode(roiNode) geometryWidget.geometryImageData(geometryImageData) self.assertTrue( self.compareOutputGeometry( geometryImageData, (1, 1, 1), (0.0, 0.0, 0.0), [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])) slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( segmentOrientedImageData, geometryImageData, geometryImageData, False, True) self.assertEqual(self.getForegroundVoxelCount(geometryImageData), 5224232) slicer.util.delayDisplay('Model and ROI source cases - OK') slicer.util.delayDisplay('Segmentation geometry widget test passed')
def splitSegments(self, minimumSize=0, maxNumberOfSegments=0, split=True): """ minimumSize: if 0 then it means that all islands are kept, regardless of size maxNumberOfSegments: if 0 then it means that all islands are kept, regardless of how many """ # This can be a long operation - indicate it to the user qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) self.scriptedEffect.saveStateForUndo() # Get modifier labelmap selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap() castIn = vtk.vtkImageCast() castIn.SetInputData(selectedSegmentLabelmap) castIn.SetOutputScalarTypeToUnsignedInt() # Identify the islands in the inverted volume and # find the pixel that corresponds to the background islandMath = vtkITK.vtkITKIslandMath() islandMath.SetInputConnection(castIn.GetOutputPort()) islandMath.SetFullyConnected(False) islandMath.SetMinimumSize(minimumSize) islandMath.Update() # Create a separate image for the first (largest) island labelValue = 1 backgroundValue = 0 thresh = vtk.vtkImageThreshold() if split: thresh.ThresholdBetween(1, 1) else: if maxNumberOfSegments != 0: thresh.ThresholdBetween(1, maxNumberOfSegments) else: thresh.ThresholdByUpper(1) thresh.SetInputData(islandMath.GetOutput()) thresh.SetOutValue(backgroundValue) thresh.SetInValue(labelValue) thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType()) thresh.Update() # Create oriented image data from output import vtkSegmentationCorePython as vtkSegmentationCore largestIslandImage = slicer.vtkOrientedImageData() largestIslandImage.ShallowCopy(thresh.GetOutput()) selectedSegmentLabelmapImageToWorldMatrix = vtk.vtkMatrix4x4() selectedSegmentLabelmap.GetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) largestIslandImage.SetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) if split and (maxNumberOfSegments != 1): thresh2 = vtk.vtkImageThreshold() # 0 is background, 1 is largest island; we need label 2 and higher if maxNumberOfSegments != 0: thresh2.ThresholdBetween(2, maxNumberOfSegments) else: thresh2.ThresholdByUpper(2) thresh2.SetInputData(islandMath.GetOutput()) thresh2.SetOutValue(backgroundValue) thresh2.ReplaceInOff() thresh2.Update() islandCount = islandMath.GetNumberOfIslands() islandOrigCount = islandMath.GetOriginalNumberOfIslands() ignoredIslands = islandOrigCount - islandCount logging.info("%d islands created (%d ignored)" % (islandCount, ignoredIslands)) # Create oriented image data from output import vtkSegmentationCorePython as vtkSegmentationCore multiLabelImage = slicer.vtkOrientedImageData() multiLabelImage.DeepCopy(thresh2.GetOutput()) selectedSegmentLabelmapImageToWorldMatrix = vtk.vtkMatrix4x4() selectedSegmentLabelmap.GetImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) multiLabelImage.SetGeometryFromImageToWorldMatrix( selectedSegmentLabelmapImageToWorldMatrix) # Import multi-label labelmap to segmentation segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() selectedSegmentID = self.scriptedEffect.parameterSetNode( ).GetSelectedSegmentID() selectedSegmentIndex = segmentationNode.GetSegmentation( ).GetSegmentIndex(selectedSegmentID) insertBeforeSegmentID = segmentationNode.GetSegmentation( ).GetNthSegmentID(selectedSegmentIndex + 1) selectedSegmentName = segmentationNode.GetSegmentation( ).GetSegment(selectedSegmentID).GetName() slicer.vtkSlicerSegmentationsModuleLogic.ImportLabelmapToSegmentationNode( multiLabelImage, segmentationNode, selectedSegmentName + " -", insertBeforeSegmentID) self.scriptedEffect.modifySelectedSegmentByLabelmap( largestIslandImage, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet) qt.QApplication.restoreOverrideCursor()
def updateSegmentationMask(self, extreme_points, in_file, modelInfo, overwriteCurrentSegment=False): start = time.time() logging.debug('Update Segmentation Mask from: {}'.format(in_file)) if in_file is None or os.path.exists(in_file) is False: return False segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() segmentation = segmentationNode.GetSegmentation() currentSegment = self.currentSegment() labelImage = sitk.ReadImage(in_file) labelmapVolumeNode = sitkUtils.PushVolumeToSlicer( labelImage, None, className='vtkMRMLLabelMapVolumeNode') numberOfExistingSegments = segmentation.GetNumberOfSegments() slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode( labelmapVolumeNode, segmentationNode) slicer.mrmlScene.RemoveNode(labelmapVolumeNode) modelLabels = modelInfo['labels'] numberOfAddedSegments = segmentation.GetNumberOfSegments( ) - numberOfExistingSegments logging.debug('Adding {} segments'.format(numberOfAddedSegments)) addedSegmentIds = [ segmentation.GetNthSegmentID(numberOfExistingSegments + i) for i in range(numberOfAddedSegments) ] for i, segmentId in enumerate(addedSegmentIds): segment = segmentation.GetSegment(segmentId) if i == 0 and overwriteCurrentSegment and currentSegment: logging.debug( 'Update current segment with id: {} => {}'.format( segmentId, segment.GetName())) # Copy labelmap representation to the current segment then remove the imported segment labelmap = slicer.vtkOrientedImageData() segmentationNode.GetBinaryLabelmapRepresentation( segmentId, labelmap) self.scriptedEffect.modifySelectedSegmentByLabelmap( labelmap, slicer.qSlicerSegmentEditorAbstractEffect. ModificationModeSet) segmentationNode.RemoveSegment(segmentId) else: logging.debug( 'Setting new segmentation with id: {} => {}'.format( segmentId, segment.GetName())) if i < len(modelLabels): segment.SetName(modelLabels[i]) else: # we did not get enough labels (for exampe annotation_mri_prostate_cg_and_pz model returns a labelmap with # 2 labels but in the model infor only 1 label is provided) segment.SetName("unknown {}".format(i)) # Save extreme points into first segment if extreme_points: logging.debug('Extreme Points: {}'.format(extreme_points)) if overwriteCurrentSegment and currentSegment: segment = currentSegment else: segment = segmentation.GetNthSegment(numberOfExistingSegments) if segment: segment.SetTag("AIAA.DExtr3DExtremePoints", json.dumps(extreme_points)) os.unlink(in_file) logging.info( "Time consumed by updateSegmentationMask: {0:3.1f}".format( time.time() - start)) return True
def updateSegmentationMask(self, extreme_points, in_file, modelInfo, overwriteCurrentSegment=False): print('updateSegmentationMask()') start = time.time() logging.debug('Update Segmentation Mask from: {}'.format(in_file)) if in_file is None or os.path.exists(in_file) is False: return False else: segmentationNode = self.scriptedEffect.parameterSetNode( ).GetSegmentationNode() segmentation = segmentationNode.GetSegmentation() currentSegment = self.currentSegment() labelImage = sitk.ReadImage(in_file) labelmapVolumeNode = sitkUtils.PushVolumeToSlicer( labelImage, None, className='vtkMRMLLabelMapVolumeNode') numberOfExistingSegments = segmentation.GetNumberOfSegments() slicer.modules.segmentations.logic( ).ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode) slicer.mrmlScene.RemoveNode(labelmapVolumeNode) modelLabels = modelInfo['labels'] numberOfAddedSegments = segmentation.GetNumberOfSegments( ) - numberOfExistingSegments logging.debug('Adding {} segments'.format(numberOfAddedSegments)) addedSegmentIds = [ segmentation.GetNthSegmentID(numberOfExistingSegments + i) for i in range(numberOfAddedSegments) ] for i, segmentId in enumerate(addedSegmentIds): segment = segmentation.GetSegment(segmentId) if i == 0: if overwriteCurrentSegment: if currentSegment: logging.debug( 'Update current segment with id: {} => {}'. format(segmentId, segment.GetName())) labelmap = slicer.vtkOrientedImageData() segmentationNode.GetBinaryLabelmapRepresentation( segmentId, labelmap) self.scriptedEffect.modifySelectedSegmentByLabelmap( labelmap, slicer.qSlicerSegmentEditorAbstractEffect. ModificationModeSet) segmentationNode.RemoveSegment(segmentId) logging.debug( 'Setting new segmentation with id: {} => {}'.format( segmentId, segment.GetName())) if i < len(modelLabels): segment.SetName(modelLabels[i]) else: segment.SetName('unknown {}'.format(i)) if extreme_points: logging.debug('Extreme Points: {}'.format(extreme_points)) if overwriteCurrentSegment: if currentSegment: segment = currentSegment else: segment = segmentation.GetNthSegment( numberOfExistingSegments) if segment: segment.SetTag('AIAA.DExtr3DExtremePoints', json.dumps(extreme_points)) os.unlink(in_file) logging.info( 'Time consumed by updateSegmentationMask: {0:3.1f}'.format( time.time() - start)) return True
def getBinaryLabelmapRepresentation(segmentationNode, segmentID: str): segmentLabelmap = slicer.vtkOrientedImageData() segmentationNode.GetBinaryLabelmapRepresentation(segmentID, segmentLabelmap) return segmentLabelmap
def applySegmentation(self): if not self.segmentEditorWidget.activeEffect(): # no region growing was done return import time startTime = time.time() self.showStatusMessage('Finalize region growing...') # Ensure closed surface representation is not present (would slow down computations) self.outputSegmentation.RemoveClosedSurfaceRepresentation() effect = self.segmentEditorWidget.activeEffect() effect.self().onApply() segmentEditorNode = self.segmentEditorWidget.mrmlSegmentEditorNode() # disable intensity masking, otherwise vessels do not fill segmentEditorNode.SetMasterVolumeIntensityMask(False) # Prevent confirmation popup for editing a hidden segment previousConfirmEditHiddenSegmentSetting = slicer.app.settings().value( "Segmentations/ConfirmEditHiddenSegment") slicer.app.settings().setValue( "Segmentations/ConfirmEditHiddenSegment", qt.QMessageBox.No) segmentIds = [ self.rightLungSegmentId, self.leftLungSegmentId, self.tracheaSegmentId ] # fill holes for i, segmentId in enumerate(segmentIds): self.showStatusMessage( f'Filling holes ({i+1}/{len(segmentIds)})...') segmentEditorNode.SetSelectedSegmentID(segmentId) self.segmentEditorWidget.setActiveEffectByName("Smoothing") effect = self.segmentEditorWidget.activeEffect() effect.setParameter("SmoothingMethod", "MORPHOLOGICAL_CLOSING") effect.setParameter("KernelSizeMm", "12") effect.self().onApply() # switch to full-resolution segmentation (this is quick, there is no need for progress message) self.outputSegmentation.SetReferenceImageGeometryParameterFromVolumeNode( self.inputVolume) referenceGeometryString = self.outputSegmentation.GetSegmentation( ).GetConversionParameter(slicer.vtkSegmentationConverter. GetReferenceImageGeometryParameterName()) referenceGeometryImageData = slicer.vtkOrientedImageData() slicer.vtkSegmentationConverter.DeserializeImageGeometry( referenceGeometryString, referenceGeometryImageData, False) wasModified = self.outputSegmentation.StartModify() for i, segmentId in enumerate(segmentIds): currentSegment = self.outputSegmentation.GetSegmentation( ).GetSegment(segmentId) # Get master labelmap from segment currentLabelmap = currentSegment.GetRepresentation( "Binary labelmap") # Resample if not slicer.vtkOrientedImageDataResample.ResampleOrientedImageToReferenceOrientedImage( currentLabelmap, referenceGeometryImageData, currentLabelmap, False, True): raise ValueError( "Failed to resample segment " << currentSegment.GetName()) self.segmentEditorWidget.setMasterVolumeNode(self.inputVolume) # Trigger display update self.outputSegmentation.Modified() self.outputSegmentation.EndModify(wasModified) # Final smoothing for i, segmentId in enumerate(segmentIds): if self.detailedAirways and segmentId == self.tracheaSegmentId: print('Not smooth airways ...') # do not smooth the airways else: self.showStatusMessage( f'Final smoothing ({i+1}/{len(segmentIds)})...') segmentEditorNode.SetSelectedSegmentID(segmentId) self.segmentEditorWidget.setActiveEffectByName("Smoothing") effect = self.segmentEditorWidget.activeEffect() effect.setParameter("SmoothingMethod", "GAUSSIAN") effect.setParameter("KernelSizeMm", "2") effect.self().onApply() self.outputSegmentation.GetDisplayNode().SetOpacity3D(0.5) self.outputSegmentation.GetDisplayNode().SetVisibility(True) self.outputSegmentation.CreateClosedSurfaceRepresentation() # Restore confirmation popup setting for editing a hidden segment slicer.app.settings().setValue( "Segmentations/ConfirmEditHiddenSegment", previousConfirmEditHiddenSegmentSetting) slicer.mrmlScene.RemoveNode(self.rightLungFiducials) slicer.mrmlScene.RemoveNode(self.leftLungFiducials) slicer.mrmlScene.RemoveNode(self.tracheaFiducials) self.removeTemporaryObjects() self.segmentationStarted = False self.segmentationFinished = True stopTime = time.time() logging.info( 'ApplySegmentation completed in {0:.2f} seconds'.format(stopTime - startTime))
def apply(self, ijkPoints): kernelSizePixel = self.getKernelSizePixel() if kernelSizePixel[0]<=0 and kernelSizePixel[1]<=0 and kernelSizePixel[2]<=0: return qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) # Get parameter set node parameterSetNode = self.scriptedEffect.parameterSetNode() # Get parameters minimumThreshold = self.scriptedEffect.doubleParameter("MinimumThreshold") maximumThreshold = self.scriptedEffect.doubleParameter("MaximumThreshold") # Get modifier labelmap modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap() # Get master volume image data masterImageData = self.scriptedEffect.masterVolumeImageData() # Set intensity range oldMasterVolumeIntensityMask = parameterSetNode.GetMasterVolumeIntensityMask() parameterSetNode.MasterVolumeIntensityMaskOn() oldIntensityMaskRange = parameterSetNode.GetMasterVolumeIntensityMaskRange() intensityRange = [265.00, 1009.00] if oldMasterVolumeIntensityMask: intensityRange = [max(oldIntensityMaskRange[0], minimumThreshold), min(oldIntensityMaskRange[1], maximumThreshold)] parameterSetNode.SetMasterVolumeIntensityMaskRange(intensityRange) roiNode = lumbarSeed ##self.roiSelector.currentNode() clippedMasterImageData = masterImageData if roiNode is not None: worldToImageMatrix = vtk.vtkMatrix4x4() masterImageData.GetWorldToImageMatrix(worldToImageMatrix) bounds = [0,0,0,0,0,0] roiNode.GetRASBounds(bounds) corner1RAS = [bounds[0], bounds[2], bounds[4], 1] corner1IJK = [0, 0, 0, 0] worldToImageMatrix.MultiplyPoint(corner1RAS, corner1IJK) corner2RAS = [bounds[1], bounds[3], bounds[5], 1] corner2IJK = [0, 0, 0, 0] worldToImageMatrix.MultiplyPoint(corner2RAS, corner2IJK) extent = [0, -1, 0, -1, 0, -1] for i in range(3): lowerPoint = min(corner1IJK[i], corner2IJK[i]) upperPoint = max(corner1IJK[i], corner2IJK[i]) extent[2*i] = int(math.floor(lowerPoint)) extent[2*i+1] = int(math.ceil(upperPoint)) imageToWorldMatrix = vtk.vtkMatrix4x4() masterImageData.GetImageToWorldMatrix(imageToWorldMatrix) clippedMasterImageData = slicer.vtkOrientedImageData() self.padder = vtk.vtkImageConstantPad() self.padder.SetInputData(masterImageData) self.padder.SetOutputWholeExtent(extent) self.padder.Update() clippedMasterImageData.ShallowCopy(self.padder.GetOutput()) clippedMasterImageData.SetImageToWorldMatrix(imageToWorldMatrix) # Pipeline self.thresh = vtk.vtkImageThreshold() self.thresh.SetInValue(LABEL_VALUE) self.thresh.SetOutValue(BACKGROUND_VALUE) self.thresh.SetInputData(clippedMasterImageData) self.thresh.ThresholdBetween(minimumThreshold, maximumThreshold) self.thresh.SetOutputScalarTypeToUnsignedChar() self.thresh.Update() self.erode = vtk.vtkImageDilateErode3D() self.erode.SetInputConnection(self.thresh.GetOutputPort()) self.erode.SetDilateValue(BACKGROUND_VALUE) self.erode.SetErodeValue(LABEL_VALUE) self.erode.SetKernelSize( kernelSizePixel[0], kernelSizePixel[1], kernelSizePixel[2]) self.erodeCast = vtk.vtkImageCast() self.erodeCast.SetInputConnection(self.erode.GetOutputPort()) self.erodeCast.SetOutputScalarTypeToUnsignedInt() self.erodeCast.Update() # Remove small islands self.islandMath = vtkITK.vtkITKIslandMath() self.islandMath.SetInputConnection(self.erodeCast.GetOutputPort()) self.islandMath.SetFullyConnected(False) self.islandMath.SetMinimumSize(125) # remove regions smaller than 5x5x5 voxels self.islandThreshold = vtk.vtkImageThreshold() self.islandThreshold.SetInputConnection(self.islandMath.GetOutputPort()) self.islandThreshold.ThresholdByLower(BACKGROUND_VALUE) self.islandThreshold.SetInValue(BACKGROUND_VALUE) self.islandThreshold.SetOutValue(LABEL_VALUE) self.islandThreshold.SetOutputScalarTypeToUnsignedChar() self.islandThreshold.Update() # Points may be outside the region after it is eroded. # Snap the points to LABEL_VALUE voxels, snappedIJKPoints = self.snapIJKPointsToLabel(ijkPoints, self.islandThreshold.GetOutput()) if snappedIJKPoints.GetNumberOfPoints() == 0: qt.QApplication.restoreOverrideCursor() return # Convert points to real data coordinates. Required for vtkImageThresholdConnectivity. seedPoints = vtk.vtkPoints() origin = masterImageData.GetOrigin() spacing = masterImageData.GetSpacing() for i in range(snappedIJKPoints.GetNumberOfPoints()): ijkPoint = snappedIJKPoints.GetPoint(i) seedPoints.InsertNextPoint( origin[0]+ijkPoint[0]*spacing[0], origin[1]+ijkPoint[1]*spacing[1], origin[2]+ijkPoint[2]*spacing[2]) segmentationAlgorithm = self.scriptedEffect.parameter(SEGMENTATION_ALGORITHM_PARAMETER_NAME) if segmentationAlgorithm == SEGMENTATION_ALGORITHM_MASKING: self.runMasking(seedPoints, self.islandThreshold.GetOutput(), modifierLabelmap) else: self.floodFillingFilterIsland = vtk.vtkImageThresholdConnectivity() self.floodFillingFilterIsland.SetInputConnection(self.islandThreshold.GetOutputPort()) self.floodFillingFilterIsland.SetInValue(SELECTED_ISLAND_VALUE) self.floodFillingFilterIsland.ReplaceInOn() self.floodFillingFilterIsland.ReplaceOutOff() self.floodFillingFilterIsland.ThresholdBetween(265.00, 1009.00) self.floodFillingFilterIsland.SetSeedPoints(seedPoints) self.floodFillingFilterIsland.Update() self.maskCast = vtk.vtkImageCast() self.maskCast.SetInputData(self.thresh.GetOutput()) self.maskCast.SetOutputScalarTypeToUnsignedChar() self.maskCast.Update() self.imageMask = vtk.vtkImageMask() self.imageMask.SetInputConnection(self.floodFillingFilterIsland.GetOutputPort()) self.imageMask.SetMaskedOutputValue(OUTSIDE_THRESHOLD_VALUE) self.imageMask.SetMaskInputData(self.maskCast.GetOutput()) self.imageMask.Update() imageMaskOutput = slicer.vtkOrientedImageData() imageMaskOutput.ShallowCopy(self.imageMask.GetOutput()) imageMaskOutput.CopyDirections(clippedMasterImageData) imageToWorldMatrix = vtk.vtkMatrix4x4() imageMaskOutput.GetImageToWorldMatrix(imageToWorldMatrix) segmentOutputLabelmap = slicer.vtkOrientedImageData() if segmentationAlgorithm == SEGMENTATION_ALGORITHM_GROWCUT: self.runGrowCut(clippedMasterImageData, imageMaskOutput, segmentOutputLabelmap) elif segmentationAlgorithm == SEGMENTATION_ALGORITHM_WATERSHED: self.runWatershed(clippedMasterImageData, imageMaskOutput, segmentOutputLabelmap) else: logging.error("Unknown segmentation algorithm: \"" + segmentationAlgorithm + "\"") segmentOutputLabelmap.SetImageToWorldMatrix(imageToWorldMatrix) self.selectedSegmentThreshold = vtk.vtkImageThreshold() self.selectedSegmentThreshold.SetInputData(segmentOutputLabelmap) self.selectedSegmentThreshold.ThresholdBetween(SELECTED_ISLAND_VALUE, SELECTED_ISLAND_VALUE) self.selectedSegmentThreshold.SetInValue(LABEL_VALUE) self.selectedSegmentThreshold.SetOutValue(BACKGROUND_VALUE) self.selectedSegmentThreshold.SetOutputScalarType(modifierLabelmap.GetScalarType()) self.selectedSegmentThreshold.Update() modifierLabelmap.ShallowCopy(self.selectedSegmentThreshold.GetOutput()) self.scriptedEffect.saveStateForUndo() self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd) parameterSetNode.SetMasterVolumeIntensityMask(oldMasterVolumeIntensityMask) parameterSetNode.SetMasterVolumeIntensityMaskRange(oldIntensityMaskRange) qt.QApplication.restoreOverrideCursor()
def computeStatistics(self, segmentID): import vtkSegmentationCorePython as vtkSegmentationCore requestedKeys = self.getRequestedKeys() segmentationNode = slicer.mrmlScene.GetNodeByID( self.getParameterNode().GetParameter("Segmentation")) if len(requestedKeys) == 0: return {} containsLabelmapRepresentation = segmentationNode.GetSegmentation( ).ContainsRepresentation( vtkSegmentationCore.vtkSegmentationConverter. GetSegmentationBinaryLabelmapRepresentationName()) if not containsLabelmapRepresentation: return {} segmentLabelmap = slicer.vtkOrientedImageData() segmentationNode.GetBinaryLabelmapRepresentation( segmentID, segmentLabelmap) if (not segmentLabelmap or not segmentLabelmap.GetPointData() or not segmentLabelmap.GetPointData().GetScalars()): # No input label data return {} # We need to know exactly the value of the segment voxels, apply threshold to make force the selected label value labelValue = 1 backgroundValue = 0 thresh = vtk.vtkImageThreshold() thresh.SetInputData(segmentLabelmap) thresh.ThresholdByLower(0) thresh.SetInValue(backgroundValue) thresh.SetOutValue(labelValue) thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR) thresh.Update() # Use binary labelmap as a stencil stencil = vtk.vtkImageToImageStencil() stencil.SetInputData(thresh.GetOutput()) stencil.ThresholdByUpper(labelValue) stencil.Update() stat = vtk.vtkImageAccumulate() stat.SetInputData(thresh.GetOutput()) stat.SetStencilData(stencil.GetOutput()) stat.Update() # Add data to statistics list cubicMMPerVoxel = reduce(lambda x, y: x * y, segmentLabelmap.GetSpacing()) ccPerCubicMM = 0.001 stats = {} if "voxel_count" in requestedKeys: stats["voxel_count"] = stat.GetVoxelCount() if "volume_mm3" in requestedKeys: stats["volume_mm3"] = stat.GetVoxelCount() * cubicMMPerVoxel if "volume_cm3" in requestedKeys: stats["volume_cm3"] = stat.GetVoxelCount( ) * cubicMMPerVoxel * ccPerCubicMM calculateShapeStats = False for shapeKey in self.shapeKeys: if shapeKey in requestedKeys: calculateShapeStats = True break if calculateShapeStats: directions = vtk.vtkMatrix4x4() segmentLabelmap.GetDirectionMatrix(directions) # Remove oriented bounding box from requested keys and replace with individual keys requestedOptions = requestedKeys statFilterOptions = self.shapeKeys calculateOBB = ("obb_diameter_mm" in requestedKeys or "obb_origin_ras" in requestedKeys or "obb_direction_ras_x" in requestedKeys or "obb_direction_ras_y" in requestedKeys or "obb_direction_ras_z" in requestedKeys) if calculateOBB: temp = statFilterOptions statFilterOptions = [] for option in temp: if not option in self.obbKeys: statFilterOptions.append(option) statFilterOptions.append("oriented_bounding_box") temp = requestedOptions requestedOptions = [] for option in temp: if not option in self.obbKeys: requestedOptions.append(option) requestedOptions.append("oriented_bounding_box") shapeStat = vtkITK.vtkITKLabelShapeStatistics() shapeStat.SetInputData(thresh.GetOutput()) shapeStat.SetDirections(directions) for shapeKey in statFilterOptions: shapeStat.SetComputeShapeStatistic( self.keyToShapeStatisticNames[shapeKey], shapeKey in requestedOptions) shapeStat.Update() # If segmentation node is transformed, apply that transform to get RAS coordinates transformSegmentToRas = vtk.vtkGeneralTransform() slicer.vtkMRMLTransformNode.GetTransformBetweenNodes( segmentationNode.GetParentTransformNode(), None, transformSegmentToRas) statTable = shapeStat.GetOutput() if "centroid_ras" in requestedKeys: centroidRAS = [0, 0, 0] centroidArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["centroid_ras"]) centroid = centroidArray.GetTuple(0) transformSegmentToRas.TransformPoint(centroid, centroidRAS) stats["centroid_ras"] = centroidRAS if "roundness" in requestedKeys: roundnessArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["roundness"]) roundness = roundnessArray.GetTuple(0)[0] stats["roundness"] = roundness if "flatness" in requestedKeys: flatnessArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["flatness"]) flatness = flatnessArray.GetTuple(0)[0] stats["flatness"] = flatness if "feret_diameter_mm" in requestedKeys: feretDiameterArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["feret_diameter_mm"]) feretDiameter = feretDiameterArray.GetTuple(0)[0] stats["feret_diameter_mm"] = feretDiameter if "surface_area_mm2" in requestedKeys: perimeterArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["surface_area_mm2"]) perimeter = perimeterArray.GetTuple(0)[0] stats["surface_area_mm2"] = perimeter if "obb_origin_ras" in requestedKeys: obbOriginRAS = [0, 0, 0] obbOriginArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_origin_ras"]) obbOrigin = obbOriginArray.GetTuple(0) transformSegmentToRas.TransformPoint(obbOrigin, obbOriginRAS) stats["obb_origin_ras"] = obbOriginRAS if "obb_diameter_mm" in requestedKeys: obbDiameterArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_diameter_mm"]) obbDiameterMM = list(obbDiameterArray.GetTuple(0)) stats["obb_diameter_mm"] = obbDiameterMM if "obb_direction_ras_x" in requestedKeys: obbOriginArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_origin_ras"]) obbOrigin = obbOriginArray.GetTuple(0) obbDirectionXArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_direction_ras_x"]) obbDirectionX = list(obbDirectionXArray.GetTuple(0)) transformSegmentToRas.TransformVectorAtPoint( obbOrigin, obbDirectionX, obbDirectionX) stats["obb_direction_ras_x"] = obbDirectionX if "obb_direction_ras_y" in requestedKeys: obbOriginArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_origin_ras"]) obbOrigin = obbOriginArray.GetTuple(0) obbDirectionYArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_direction_ras_y"]) obbDirectionY = list(obbDirectionYArray.GetTuple(0)) transformSegmentToRas.TransformVectorAtPoint( obbOrigin, obbDirectionY, obbDirectionY) stats["obb_direction_ras_y"] = obbDirectionY if "obb_direction_ras_z" in requestedKeys: obbOriginArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_origin_ras"]) obbOrigin = obbOriginArray.GetTuple(0) obbDirectionZArray = statTable.GetColumnByName( self.keyToShapeStatisticNames["obb_direction_ras_z"]) obbDirectionZ = list(obbDirectionZArray.GetTuple(0)) transformSegmentToRas.TransformVectorAtPoint( obbOrigin, obbDirectionZ, obbDirectionZ) stats["obb_direction_ras_z"] = obbDirectionZ return stats
def createMeshFromSegmentationCleaver(self, inputSegmentation, outputMeshNode, additionalParameters = None, removeBackgroundMesh = False, paddingRatio = 0.10): if additionalParameters is None: additionalParameters="--scale 0.2 --multiplier 2 --grading 5" self.abortRequested = False tempDir = self.createTempDirectory() self.addLog('Mesh generation using Cleaver is started in working directory: '+tempDir) inputParamsCleaver = [] # Write inputs qt.QDir().mkpath(tempDir) # Create temporary labelmap node. It will be used both for storing reference geometry # and resulting merged labelmap. labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') parentTransformNode = inputSegmentation.GetParentTransformNode() labelmapVolumeNode.SetAndObserveTransformNodeID(parentTransformNode.GetID() if parentTransformNode else None) # Create binary labelmap representation using default parameters if not inputSegmentation.GetSegmentation().CreateRepresentation(slicer.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName()): self.addLog('Failed to create binary labelmap representation') return # Set reference geometry in labelmapVolumeNode referenceGeometry_Segmentation = slicer.vtkOrientedImageData() inputSegmentation.GetSegmentation().SetImageGeometryFromCommonLabelmapGeometry(referenceGeometry_Segmentation, None, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY) slicer.modules.segmentations.logic().CopyOrientedImageDataToVolumeNode(referenceGeometry_Segmentation, labelmapVolumeNode) # Add margin extent = labelmapVolumeNode.GetImageData().GetExtent() paddedExtent = [0, -1, 0, -1, 0, -1] for axisIndex in range(3): paddingSizeVoxels = int((extent[axisIndex * 2 + 1] - extent[axisIndex * 2]) * paddingRatio) paddedExtent[axisIndex * 2] = extent[axisIndex * 2] - paddingSizeVoxels paddedExtent[axisIndex * 2 + 1] = extent[axisIndex * 2 + 1] + paddingSizeVoxels labelmapVolumeNode.GetImageData().SetExtent(paddedExtent) labelmapVolumeNode.ShiftImageDataExtentToZeroStart() # Get merged labelmap segmentIdList = vtk.vtkStringArray() slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(inputSegmentation, segmentIdList, labelmapVolumeNode, labelmapVolumeNode) inputLabelmapVolumeFilePath = os.path.join(tempDir, "inputLabelmap.nrrd") slicer.util.saveNode(labelmapVolumeNode, inputLabelmapVolumeFilePath, {"useCompression": False}) inputParamsCleaver.extend(["--input_files", inputLabelmapVolumeFilePath]) inputParamsCleaver.append("--segmentation") # Keep IJK to RAS matrix, we'll need it later unscaledIjkToRasMatrix = vtk.vtkMatrix4x4() labelmapVolumeNode.GetIJKToRASDirectionMatrix(unscaledIjkToRasMatrix) origin = labelmapVolumeNode.GetOrigin() for i in range(3): unscaledIjkToRasMatrix.SetElement(i,3, origin[i]) # Keep color node, we'll need it later colorTableNode = labelmapVolumeNode.GetDisplayNode().GetColorNode() # Background color is transparent by default which is not ideal for 3D display colorTableNode.SetColor(0,0.6,0.6,0.6,1.0) slicer.mrmlScene.RemoveNode(labelmapVolumeNode) slicer.mrmlScene.RemoveNode(colorTableNode) # Set up output format inputParamsCleaver.extend(["--output_path", tempDir+"/"]) inputParamsCleaver.extend(["--output_format", "vtkUSG"]) # VTK unstructed grid inputParamsCleaver.append("--fix_tet_windup") # prevent inside-out tets inputParamsCleaver.append("--strip_exterior") # remove temporary elements that are added to make the volume cubic inputParamsCleaver.append("--verbose") # Quality inputParamsCleaver.extend(slicer.util.toVTKString(additionalParameters).split(' ')) # Run Cleaver ep = self.startMesher(inputParamsCleaver, self.getCleaverPath()) self.logProcessOutput(ep, self.cleaverFilename) # Read results if not self.abortRequested: outputVolumetricMeshPath = os.path.join(tempDir, "output.vtk") outputReader = vtk.vtkUnstructuredGridReader() outputReader.SetFileName(outputVolumetricMeshPath) outputReader.ReadAllScalarsOn() outputReader.ReadAllVectorsOn() outputReader.ReadAllNormalsOn() outputReader.ReadAllTensorsOn() outputReader.ReadAllColorScalarsOn() outputReader.ReadAllTCoordsOn() outputReader.ReadAllFieldsOn() outputReader.Update() # Cleaver returns the mesh in voxel coordinates, need to transform to RAS space transformer = vtk.vtkTransformFilter() transformer.SetInputData(outputReader.GetOutput()) ijkToRasTransform = vtk.vtkTransform() ijkToRasTransform.SetMatrix(unscaledIjkToRasMatrix) transformer.SetTransform(ijkToRasTransform) if removeBackgroundMesh: transformer.Update() mesh = transformer.GetOutput() cellData = mesh.GetCellData() cellData.SetActiveScalars("labels") backgroundMeshRemover = vtk.vtkThreshold() backgroundMeshRemover.SetInputData(mesh) backgroundMeshRemover.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS, vtk.vtkDataSetAttributes.SCALARS) backgroundMeshRemover.ThresholdByUpper(1) outputMeshNode.SetUnstructuredGridConnection(backgroundMeshRemover.GetOutputPort()) else: outputMeshNode.SetUnstructuredGridConnection(transformer.GetOutputPort()) outputMeshDisplayNode = outputMeshNode.GetDisplayNode() if not outputMeshDisplayNode: # Initial setup of display node outputMeshNode.CreateDefaultDisplayNodes() outputMeshDisplayNode = outputMeshNode.GetDisplayNode() outputMeshDisplayNode.SetEdgeVisibility(True) outputMeshDisplayNode.SetClipping(True) colorTableNode = slicer.mrmlScene.AddNode(colorTableNode) outputMeshDisplayNode.SetAndObserveColorNodeID(colorTableNode.GetID()) outputMeshDisplayNode.ScalarVisibilityOn() outputMeshDisplayNode.SetActiveScalarName('labels') outputMeshDisplayNode.SetActiveAttributeLocation(vtk.vtkAssignAttribute.CELL_DATA) outputMeshDisplayNode.SetSliceIntersectionVisibility(True) outputMeshDisplayNode.SetSliceIntersectionOpacity(0.5) outputMeshDisplayNode.SetScalarRangeFlag(slicer.vtkMRMLDisplayNode.UseColorNodeScalarRange) else: currentColorNode = outputMeshDisplayNode.GetColorNode() if currentColorNode is not None and currentColorNode.GetType() == currentColorNode.User and currentColorNode.IsA("vtkMRMLColorTableNode"): # current color table node can be overwritten currentColorNode.Copy(colorTableNode) else: colorTableNode = slicer.mrmlScene.AddNode(colorTableNode) outputMeshDisplayNode.SetAndObserveColorNodeID(colorTableNode.GetID()) # Flip clipping setting twice, this workaround forces update of the display pipeline # when switching between surface and volumetric mesh outputMeshDisplayNode.SetClipping(not outputMeshDisplayNode.GetClipping()) outputMeshDisplayNode.SetClipping(not outputMeshDisplayNode.GetClipping()) # Clean up if self.deleteTemporaryFiles: import shutil shutil.rmtree(tempDir) self.addLog("Model generation is completed")