42 def setupOptionsFrame(self):
45 self.
visibleIcon = qt.QIcon(
":/Icons/Small/SlicerVisible.png")
58 self.
binaryMaskFillButton.setToolTip(_(
"Create a labelmap volume with specified inside and outside fill values."))
63 operationLayout = qt.QGridLayout()
67 self.
scriptedEffect.addLabeledOptionsWidget(_(
"Operation:"), operationLayout)
71 self.
fillValueEdit.setToolTip(_(
"Choose the voxel intensity that will be used to fill the masked region."))
85 fillValueEdit.decimalsOption = ctk.ctkDoubleSpinBox.DecimalsByValue + ctk.ctkDoubleSpinBox.DecimalsByKey + ctk.ctkDoubleSpinBox.InsertDecimals
86 fillValueEdit.minimum = vtk.vtkDoubleArray().GetDataTypeMin(vtk.VTK_DOUBLE)
87 fillValueEdit.maximum = vtk.vtkDoubleArray().GetDataTypeMax(vtk.VTK_DOUBLE)
91 fillValueLayout = qt.QFormLayout()
94 fillOutsideLayout = qt.QFormLayout()
97 fillInsideLayout = qt.QFormLayout()
100 binaryMaskFillLayout = qt.QHBoxLayout()
101 binaryMaskFillLayout.addLayout(fillOutsideLayout)
102 binaryMaskFillLayout.addLayout(fillInsideLayout)
103 fillValuesSpinBoxLayout = qt.QFormLayout()
104 fillValuesSpinBoxLayout.addRow(binaryMaskFillLayout)
105 fillValuesSpinBoxLayout.addRow(fillValueLayout)
111 self.
softEdgeMmSpinBox.setToolTip(_(
"Standard deviation of the Gaussian function that blurs the edge of the mask."
112 " Higher value makes the edge softer."))
130 self.
inputVolumeSelector.setToolTip(_(
"Volume to mask. Default is current source volume node."))
136 inputLayout = qt.QHBoxLayout()
139 self.
scriptedEffect.addLabeledOptionsWidget(_(
"Input Volume: "), inputLayout)
152 self.
outputVolumeSelector.setToolTip(_(
"Masked output volume. It may be the same as the input volume for cumulative masking."))
158 outputLayout = qt.QHBoxLayout()
161 self.
scriptedEffect.addLabeledOptionsWidget(_(
"Output Volume: "), outputLayout)
165 self.
applyButton.objectName = self.__class__.__name__ +
"Apply"
166 self.
applyButton.setToolTip(_(
"Apply segment as volume mask. No undo operation available once applied."))
171 button.connect(
"toggled(bool)",
301 with slicer.util.tryWithErrorDisplay(_(
"Failed to apply mask to volume."), waitCursor=
True):
307 volumesLogic = slicer.modules.volumes.logic()
308 scene = inputVolume.GetScene()
309 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
310 outputVolumeName = inputVolume.GetName() +
" label"
311 outputVolume = volumesLogic.CreateAndAddLabelVolume(inputVolume, outputVolumeName)
313 outputVolumeName = inputVolume.GetName() +
" masked"
314 outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName,
False)
317 if operationMode
in [
"FILL_INSIDE",
"FILL_OUTSIDE"]:
322 segmentID = self.
scriptedEffect.parameterSetNode().GetSelectedSegmentID()
323 segmentationNode = self.
scriptedEffect.parameterSetNode().GetSegmentationNode()
327 SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume,
328 softEdgeMm=softEdgeMm)
330 slicer.util.setSliceViewerLayers(background=outputVolume)
335 def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None, softEdgeMm=0.0):
337 Fill voxels of the input volume inside/outside the masking model with the provided fill value
338 maskExtent: optional output to return computed mask extent (expected input is a 6-element list)
339 fillValues: list containing one or two fill values. If fill mode is inside or outside then only one value is specified in the list.
340 If fill mode is inside&outside then the list must contain two values: first is the inside fill, second is the outside fill value.
345 segmentIDs = vtk.vtkStringArray()
346 segmentIDs.InsertNextValue(segmentID)
347 maskVolumeNode = slicer.modules.volumes.logic().CreateAndAddLabelVolume(inputVolumeNode,
"TemporaryVolumeMask")
348 if not maskVolumeNode:
349 logging.error(
"maskVolumeWithSegment failed: invalid maskVolumeNode")
352 if not slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIDs, maskVolumeNode, inputVolumeNode):
353 logging.error(
"maskVolumeWithSegment failed: ExportSegmentsToLabelmapNode error")
354 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
355 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
356 slicer.mrmlScene.RemoveNode(maskVolumeNode)
360 img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
363 import vtkSegmentationCorePython
as vtkSegmentationCore
365 vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
369 maskToStencil = vtk.vtkImageToImageStencil()
370 maskToStencil.ThresholdByLower(0)
371 maskToStencil.SetInputData(maskVolumeNode.GetImageData())
373 stencil = vtk.vtkImageStencil()
375 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
377 thresh = vtk.vtkImageThreshold()
378 thresh.SetInputData(inputVolumeNode.GetImageData())
379 thresh.ThresholdByLower(0)
380 thresh.SetInValue(fillValues[1])
381 thresh.SetOutValue(fillValues[1])
382 thresh.SetOutputScalarType(inputVolumeNode.GetImageData().GetScalarType())
384 stencil.SetInputData(thresh.GetOutput())
386 stencil.SetInputData(inputVolumeNode.GetImageData())
388 stencil.SetStencilConnection(maskToStencil.GetOutputPort())
389 stencil.SetReverseStencil(operationMode ==
"FILL_OUTSIDE")
390 stencil.SetBackgroundValue(fillValues[0])
392 outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
396 thresh = vtk.vtkImageThreshold()
399 thresh.SetOutputScalarTypeToUnsignedChar()
400 thresh.SetInputData(maskVolumeNode.GetImageData())
401 thresh.ThresholdByLower(0)
402 thresh.SetInValue(maskMin)
403 thresh.SetOutValue(maskMax)
406 gaussianFilter = vtk.vtkImageGaussianSmooth()
407 spacing = maskVolumeNode.GetSpacing()
408 standardDeviationPixel = [1.0, 1.0, 1.0]
410 standardDeviationPixel[idx] = softEdgeMm / spacing[idx]
411 gaussianFilter.SetInputConnection(thresh.GetOutputPort())
412 gaussianFilter.SetStandardDeviations(*standardDeviationPixel)
417 gaussianFilter.SetRadiusFactor(3.0)
418 gaussianFilter.Update()
420 import vtk.util.numpy_support
422 maskImage = gaussianFilter.GetOutput()
423 nshape = tuple(reversed(maskImage.GetDimensions()))
424 maskArray = vtk.util.numpy_support.vtk_to_numpy(maskImage.GetPointData().GetScalars()).reshape(nshape)
427 maskMin = maskArray.min()
428 maskMax = maskArray.max()
429 mask = (maskArray.astype(float) - maskMin) / float(maskMax - maskMin)
431 inputArray = slicer.util.arrayFromVolume(inputVolumeNode)
433 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
435 resultArray = fillValues[0] + (fillValues[1] - fillValues[0]) * mask[:]
438 if operationMode ==
"FILL_INSIDE":
440 resultArray = inputArray[:] * mask[:] + float(fillValues[0]) * (1.0 - mask[:])
442 slicer.util.updateVolumeFromArray(outputVolumeNode, resultArray.astype(inputArray.dtype))
445 ijkToRas = vtk.vtkMatrix4x4()
446 inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
447 outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
448 inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
450 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
451 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
452 slicer.mrmlScene.RemoveNode(maskVolumeNode)