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)",
305 with slicer.util.tryWithErrorDisplay(_(
"Failed to apply mask to volume."), waitCursor=
True):
311 volumesLogic = slicer.modules.volumes.logic()
312 scene = inputVolume.GetScene()
313 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
314 outputVolumeName = inputVolume.GetName() +
" label"
315 outputVolume = volumesLogic.CreateAndAddLabelVolume(inputVolume, outputVolumeName)
317 outputVolumeName = inputVolume.GetName() +
" masked"
318 outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName,
False)
321 if operationMode
in [
"FILL_INSIDE",
"FILL_OUTSIDE"]:
326 segmentID = self.
scriptedEffect.parameterSetNode().GetSelectedSegmentID()
327 segmentationNode = self.
scriptedEffect.parameterSetNode().GetSegmentationNode()
331 SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume,
332 softEdgeMm=softEdgeMm)
334 slicer.util.setSliceViewerLayers(background=outputVolume)
339 def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None, softEdgeMm=0.0):
341 Fill voxels of the input volume inside/outside the masking model with the provided fill value
342 maskExtent: optional output to return computed mask extent (expected input is a 6-element list)
343 fillValues: list containing one or two fill values. If fill mode is inside or outside then only one value is specified in the list.
344 If fill mode is inside&outside then the list must contain two values: first is the inside fill, second is the outside fill value.
349 segmentIDs = vtk.vtkStringArray()
350 segmentIDs.InsertNextValue(segmentID)
351 maskVolumeNode = slicer.modules.volumes.logic().CreateAndAddLabelVolume(inputVolumeNode,
"TemporaryVolumeMask")
352 if not maskVolumeNode:
353 logging.error(
"maskVolumeWithSegment failed: invalid maskVolumeNode")
356 if not slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIDs, maskVolumeNode, inputVolumeNode):
357 logging.error(
"maskVolumeWithSegment failed: ExportSegmentsToLabelmapNode error")
358 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
359 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
360 slicer.mrmlScene.RemoveNode(maskVolumeNode)
364 img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
367 import vtkSegmentationCorePython
as vtkSegmentationCore
369 vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
373 maskToStencil = vtk.vtkImageToImageStencil()
374 maskToStencil.ThresholdByLower(0)
375 maskToStencil.SetInputData(maskVolumeNode.GetImageData())
377 stencil = vtk.vtkImageStencil()
379 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
381 thresh = vtk.vtkImageThreshold()
382 thresh.SetInputData(inputVolumeNode.GetImageData())
383 thresh.ThresholdByLower(0)
384 thresh.SetInValue(fillValues[1])
385 thresh.SetOutValue(fillValues[1])
386 thresh.SetOutputScalarType(inputVolumeNode.GetImageData().GetScalarType())
388 stencil.SetInputData(thresh.GetOutput())
390 stencil.SetInputData(inputVolumeNode.GetImageData())
392 stencil.SetStencilConnection(maskToStencil.GetOutputPort())
393 stencil.SetReverseStencil(operationMode ==
"FILL_OUTSIDE")
394 stencil.SetBackgroundValue(fillValues[0])
396 outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
400 thresh = vtk.vtkImageThreshold()
403 thresh.SetOutputScalarTypeToUnsignedChar()
404 thresh.SetInputData(maskVolumeNode.GetImageData())
405 thresh.ThresholdByLower(0)
406 thresh.SetInValue(maskMin)
407 thresh.SetOutValue(maskMax)
410 gaussianFilter = vtk.vtkImageGaussianSmooth()
411 spacing = maskVolumeNode.GetSpacing()
412 standardDeviationPixel = [1.0, 1.0, 1.0]
414 standardDeviationPixel[idx] = softEdgeMm / spacing[idx]
415 gaussianFilter.SetInputConnection(thresh.GetOutputPort())
416 gaussianFilter.SetStandardDeviations(*standardDeviationPixel)
421 gaussianFilter.SetRadiusFactor(3.0)
422 gaussianFilter.Update()
424 import vtk.util.numpy_support
426 maskImage = gaussianFilter.GetOutput()
427 nshape = tuple(reversed(maskImage.GetDimensions()))
428 maskArray = vtk.util.numpy_support.vtk_to_numpy(maskImage.GetPointData().GetScalars()).reshape(nshape)
431 maskMin = maskArray.min()
432 maskMax = maskArray.max()
433 mask = (maskArray.astype(float) - maskMin) / float(maskMax - maskMin)
435 inputArray = slicer.util.arrayFromVolume(inputVolumeNode)
437 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
439 resultArray = fillValues[0] + (fillValues[1] - fillValues[0]) * mask[:]
442 if operationMode ==
"FILL_INSIDE":
444 resultArray = inputArray[:] * mask[:] + float(fillValues[0]) * (1.0 - mask[:])
446 slicer.util.updateVolumeFromArray(outputVolumeNode, resultArray.astype(inputArray.dtype))
449 ijkToRas = vtk.vtkMatrix4x4()
450 inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
451 outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
452 inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
454 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
455 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
456 slicer.mrmlScene.RemoveNode(maskVolumeNode)