42 self.
visibleIcon = qt.QIcon(
":/Icons/Small/SlicerVisible.png")
55 self.
binaryMaskFillButton.setToolTip(
"Create a labelmap volume with specified inside and outside fill values.")
60 operationLayout = qt.QGridLayout()
64 self.scriptedEffect.addLabeledOptionsWidget(
"Operation:", operationLayout)
68 self.
fillValueEdit.setToolTip(
"Choose the voxel intensity that will be used to fill the masked region.")
82 fillValueEdit.decimalsOption = ctk.ctkDoubleSpinBox.DecimalsByValue + ctk.ctkDoubleSpinBox.DecimalsByKey + ctk.ctkDoubleSpinBox.InsertDecimals
83 fillValueEdit.minimum = vtk.vtkDoubleArray().GetDataTypeMin(vtk.VTK_DOUBLE)
84 fillValueEdit.maximum = vtk.vtkDoubleArray().GetDataTypeMax(vtk.VTK_DOUBLE)
88 fillValueLayout = qt.QFormLayout()
91 fillOutsideLayout = qt.QFormLayout()
94 fillInsideLayout = qt.QFormLayout()
97 binaryMaskFillLayout = qt.QHBoxLayout()
98 binaryMaskFillLayout.addLayout(fillOutsideLayout)
99 binaryMaskFillLayout.addLayout(fillInsideLayout)
100 fillValuesSpinBoxLayout = qt.QFormLayout()
101 fillValuesSpinBoxLayout.addRow(binaryMaskFillLayout)
102 fillValuesSpinBoxLayout.addRow(fillValueLayout)
103 self.scriptedEffect.addOptionsWidget(fillValuesSpinBoxLayout)
108 self.
softEdgeMmSpinBox.setToolTip(
"Standard deviation of the Gaussian function that blurs the edge of the mask."
109 " Higher value makes the edge softer.")
127 self.
inputVolumeSelector.setToolTip(
"Volume to mask. Default is current source volume node.")
133 inputLayout = qt.QHBoxLayout()
136 self.scriptedEffect.addLabeledOptionsWidget(
"Input Volume: ", inputLayout)
149 self.
outputVolumeSelector.setToolTip(
"Masked output volume. It may be the same as the input volume for cumulative masking.")
155 outputLayout = qt.QHBoxLayout()
158 self.scriptedEffect.addLabeledOptionsWidget(
"Output Volume: ", outputLayout)
162 self.
applyButton.objectName = self.__class__.__name__ +
'Apply'
163 self.
applyButton.setToolTip(
"Apply segment as volume mask. No undo operation available once applied.")
164 self.scriptedEffect.addOptionsWidget(self.
applyButton)
168 button.connect(
'toggled(bool)',
197 self.
fillValueEdit.setValue(float(self.scriptedEffect.parameter(
"FillValue"))
if self.scriptedEffect.parameter(
"FillValue")
else 0)
199 if self.scriptedEffect.parameter(
"BinaryMaskFillValueOutside")
else 0)
201 if self.scriptedEffect.parameter(
"BinaryMaskFillValueInside")
else 1)
202 operationName = self.scriptedEffect.parameter(
"Operation")
205 operationButton.setChecked(
True)
207 self.
softEdgeMmSpinBox.setValue(float(self.scriptedEffect.parameter(
"SoftEdgeMm"))
208 if self.scriptedEffect.parameter(
"SoftEdgeMm")
else 0)
210 inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference(
"Mask volume.InputVolume")
212 outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference(
"Mask volume.OutputVolume")
215 sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
216 if inputVolume
is None:
217 inputVolume = sourceVolume
219 self.
fillValueEdit.setVisible(operationName
in [
"FILL_INSIDE",
"FILL_OUTSIDE"])
220 self.
fillValueLabel.setVisible(operationName
in [
"FILL_INSIDE",
"FILL_OUTSIDE"])
222 self.
fillInsideLabel.setVisible(operationName ==
"FILL_INSIDE_AND_OUTSIDE")
224 self.
fillOutsideLabel.setVisible(operationName ==
"FILL_INSIDE_AND_OUTSIDE")
225 if operationName
in [
"FILL_INSIDE",
"FILL_OUTSIDE"]:
298 with slicer.util.tryWithErrorDisplay(
"Failed to apply mask to volume.", waitCursor=
True):
301 operationMode = self.scriptedEffect.parameter(
"Operation")
304 volumesLogic = slicer.modules.volumes.logic()
305 scene = inputVolume.GetScene()
306 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
307 outputVolumeName = inputVolume.GetName() +
" label"
308 outputVolume = volumesLogic.CreateAndAddLabelVolume(inputVolume, outputVolumeName)
310 outputVolumeName = inputVolume.GetName() +
" masked"
311 outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName,
False)
314 if operationMode
in [
"FILL_INSIDE",
"FILL_OUTSIDE"]:
319 segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
320 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
322 softEdgeMm = self.scriptedEffect.doubleParameter(
"SoftEdgeMm")
324 SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume,
325 softEdgeMm=softEdgeMm)
327 slicer.util.setSliceViewerLayers(background=outputVolume)
332 def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None, softEdgeMm=0.0):
334 Fill voxels of the input volume inside/outside the masking model with the provided fill value
335 maskExtent: optional output to return computed mask extent (expected input is a 6-element list)
336 fillValues: list containing one or two fill values. If fill mode is inside or outside then only one value is specified in the list.
337 If fill mode is inside&outside then the list must contain two values: first is the inside fill, second is the outside fill value.
341 segmentIDs = vtk.vtkStringArray()
342 segmentIDs.InsertNextValue(segmentID)
343 maskVolumeNode = slicer.modules.volumes.logic().CreateAndAddLabelVolume(inputVolumeNode,
"TemporaryVolumeMask")
344 if not maskVolumeNode:
345 logging.error(
"maskVolumeWithSegment failed: invalid maskVolumeNode")
348 if not slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIDs, maskVolumeNode, inputVolumeNode):
349 logging.error(
"maskVolumeWithSegment failed: ExportSegmentsToLabelmapNode error")
350 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
351 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
352 slicer.mrmlScene.RemoveNode(maskVolumeNode)
356 img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
358 import vtkSegmentationCorePython
as vtkSegmentationCore
359 vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
363 maskToStencil = vtk.vtkImageToImageStencil()
364 maskToStencil.ThresholdByLower(0)
365 maskToStencil.SetInputData(maskVolumeNode.GetImageData())
367 stencil = vtk.vtkImageStencil()
369 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
371 thresh = vtk.vtkImageThreshold()
372 thresh.SetInputData(inputVolumeNode.GetImageData())
373 thresh.ThresholdByLower(0)
374 thresh.SetInValue(fillValues[1])
375 thresh.SetOutValue(fillValues[1])
376 thresh.SetOutputScalarType(inputVolumeNode.GetImageData().GetScalarType())
378 stencil.SetInputData(thresh.GetOutput())
380 stencil.SetInputData(inputVolumeNode.GetImageData())
382 stencil.SetStencilConnection(maskToStencil.GetOutputPort())
383 stencil.SetReverseStencil(operationMode ==
"FILL_OUTSIDE")
384 stencil.SetBackgroundValue(fillValues[0])
386 outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
390 thresh = vtk.vtkImageThreshold()
393 thresh.SetOutputScalarTypeToUnsignedChar()
394 thresh.SetInputData(maskVolumeNode.GetImageData())
395 thresh.ThresholdByLower(0)
396 thresh.SetInValue(maskMin)
397 thresh.SetOutValue(maskMax)
400 gaussianFilter = vtk.vtkImageGaussianSmooth()
401 spacing = maskVolumeNode.GetSpacing()
402 standardDeviationPixel = [1.0, 1.0, 1.0]
404 standardDeviationPixel[idx] = softEdgeMm / spacing[idx]
405 gaussianFilter.SetInputConnection(thresh.GetOutputPort())
406 gaussianFilter.SetStandardDeviations(*standardDeviationPixel)
411 gaussianFilter.SetRadiusFactor(3.0)
412 gaussianFilter.Update()
414 import vtk.util.numpy_support
415 maskImage = gaussianFilter.GetOutput()
416 nshape = tuple(reversed(maskImage.GetDimensions()))
417 maskArray = vtk.util.numpy_support.vtk_to_numpy(maskImage.GetPointData().GetScalars()).reshape(nshape)
420 maskMin = maskArray.min()
421 maskMax = maskArray.max()
422 mask = (maskArray.astype(float) - maskMin) / float(maskMax - maskMin)
424 inputArray = slicer.util.arrayFromVolume(inputVolumeNode)
426 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
428 resultArray = fillValues[0] + (fillValues[1] - fillValues[0]) * mask[:]
431 if operationMode ==
"FILL_INSIDE":
433 resultArray = inputArray[:] * mask[:] + float(fillValues[0]) * (1.0 - mask[:])
435 slicer.util.updateVolumeFromArray(outputVolumeNode, resultArray.astype(inputArray.dtype))
438 ijkToRas = vtk.vtkMatrix4x4()
439 inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
440 outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
441 inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
443 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
444 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
445 slicer.mrmlScene.RemoveNode(maskVolumeNode)