2 import vtk, qt, ctk, slicer
4 from SegmentEditorEffects
import *
8 """This effect fills a selected volume node inside and/or outside a segment with a chosen value. 12 scriptedEffect.name =
'Mask volume' 13 scriptedEffect.perSegment =
True 14 AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
21 import qSlicerSegmentationsEditorEffectsPythonQt
as effects
22 clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(
None)
23 clonedEffect.setPythonSource(__file__.replace(
'\\',
'/'))
28 iconPath = os.path.join(os.path.dirname(__file__),
'Resources/Icons/MaskVolume.png')
29 if os.path.exists(iconPath):
30 return qt.QIcon(iconPath)
34 return """<html>Use the currently selected segment as a mask to blank out regions in a volume. 35 <br> The mask is applied to the source volume by default.<p> 36 Fill inside and outside operation creates a binary labelmap volume as output, with the inside and outside fill values modifiable. 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)
115 self.
inputVolumeSelector.setToolTip(
"Volume to mask. Default is current source volume node.")
121 inputLayout = qt.QHBoxLayout()
124 self.scriptedEffect.addLabeledOptionsWidget(
"Input Volume: ", inputLayout)
137 self.
outputVolumeSelector.setToolTip(
"Masked output volume. It may be the same as the input volume for cumulative masking.")
143 outputLayout = qt.QHBoxLayout()
146 self.scriptedEffect.addLabeledOptionsWidget(
"Output Volume: ", outputLayout)
150 self.
applyButton.objectName = self.__class__.__name__ +
'Apply' 151 self.
applyButton.setToolTip(
"Apply segment as volume mask. No undo operation available once applied.")
152 self.scriptedEffect.addOptionsWidget(self.
applyButton)
156 button.connect(
'toggled(bool)',
161 return slicer.util.mainWindow().cursor
164 self.scriptedEffect.setParameterDefault(
"FillValue",
"0")
165 self.scriptedEffect.setParameterDefault(
"BinaryMaskFillValueInside",
"1")
166 self.scriptedEffect.setParameterDefault(
"BinaryMaskFillValueOutside",
"0")
167 self.scriptedEffect.setParameterDefault(
"Operation",
"FILL_OUTSIDE")
172 volumeNodeID = volumeNode.GetID()
173 lm = slicer.app.layoutManager()
174 sliceViewNames = lm.sliceViewNames()
175 for sliceViewName
in sliceViewNames:
176 sliceWidget = lm.sliceWidget(sliceViewName)
177 if volumeNodeID == sliceWidget.mrmlSliceCompositeNode().GetBackgroundVolumeID():
184 self.
fillValueEdit.setValue(float(self.scriptedEffect.parameter(
"FillValue"))
if self.scriptedEffect.parameter(
"FillValue")
else 0)
186 if self.scriptedEffect.parameter(
"BinaryMaskFillValueOutside")
else 0)
188 if self.scriptedEffect.parameter(
"BinaryMaskFillValueInside")
else 1)
189 operationName = self.scriptedEffect.parameter(
"Operation")
192 operationButton.setChecked(
True)
194 inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference(
"Mask volume.InputVolume")
196 outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference(
"Mask volume.OutputVolume")
199 sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
200 if inputVolume
is None:
201 inputVolume = sourceVolume
203 self.
fillValueEdit.setVisible(operationName
in [
"FILL_INSIDE",
"FILL_OUTSIDE"])
204 self.
fillValueLabel.setVisible(operationName
in [
"FILL_INSIDE",
"FILL_OUTSIDE"])
206 self.
fillInsideLabel.setVisible(operationName ==
"FILL_INSIDE_AND_OUTSIDE")
208 self.
fillOutsideLabel.setVisible(operationName ==
"FILL_INSIDE_AND_OUTSIDE")
209 if operationName
in [
"FILL_INSIDE",
"FILL_OUTSIDE"]:
226 self.scriptedEffect.setParameter(
"FillValue", self.
fillValueEdit.value)
229 self.scriptedEffect.parameterSetNode().SetNodeReferenceID(
"Mask volume.InputVolume", self.
inputVolumeSelector.currentNodeID)
230 self.scriptedEffect.parameterSetNode().SetNodeReferenceID(
"Mask volume.OutputVolume", self.
outputVolumeSelector.currentNodeID)
233 self.scriptedEffect.setParameter(
"InputVisibility",
"True")
236 if self.
outputVolumeSelector.currentNode()
is not self.scriptedEffect.parameterSetNode().GetSourceVolumeNode():
237 self.scriptedEffect.setParameter(
"OutputVisibility",
"False")
238 slicer.util.setSliceViewerLayers(background=self.scriptedEffect.parameterSetNode().GetSourceVolumeNode())
243 self.scriptedEffect.setParameter(
"Operation", operationName)
247 if inputVolume
is None:
248 inputVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
252 inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference(
"Mask volume.InputVolume")
253 sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
254 if inputVolume
is None:
255 inputVolume = sourceVolume
257 slicer.util.setSliceViewerLayers(background=inputVolume)
261 outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference(
"Mask volume.OutputVolume")
263 slicer.util.setSliceViewerLayers(background=outputVolume)
267 self.scriptedEffect.parameterSetNode().SetNodeReferenceID(
"Mask volume.InputVolume", self.
inputVolumeSelector.currentNodeID)
271 self.scriptedEffect.parameterSetNode().SetNodeReferenceID(
"Mask volume.OutputVolume", self.
outputVolumeSelector.currentNodeID)
280 operationMode = self.scriptedEffect.parameter(
"Operation")
283 volumesLogic = slicer.modules.volumes.logic()
284 scene = inputVolume.GetScene()
285 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
286 outputVolumeName = inputVolume.GetName() +
" label" 287 outputVolume = volumesLogic.CreateAndAddLabelVolume(inputVolume, outputVolumeName)
289 outputVolumeName = inputVolume.GetName() +
" masked" 290 outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName,
False)
293 if operationMode
in [
"FILL_INSIDE",
"FILL_OUTSIDE"]:
298 segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
299 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
301 slicer.app.setOverrideCursor(qt.Qt.WaitCursor)
302 SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume)
304 slicer.util.setSliceViewerLayers(background=outputVolume)
305 qt.QApplication.restoreOverrideCursor()
310 def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None):
312 Fill voxels of the input volume inside/outside the masking model with the provided fill value 313 maskExtent: optional output to return computed mask extent (expected input is a 6-element list) 314 fillValues: list containing one or two fill values. If fill mode is inside or outside then only one value is specified in the list. 315 If fill mode is inside&outside then the list must contain two values: first is the inside fill, second is the outside fill value. 318 segmentIDs = vtk.vtkStringArray()
319 segmentIDs.InsertNextValue(segmentID)
320 maskVolumeNode = slicer.modules.volumes.logic().CreateAndAddLabelVolume(inputVolumeNode,
"TemporaryVolumeMask")
321 if not maskVolumeNode:
322 logging.error(
"maskVolumeWithSegment failed: invalid maskVolumeNode")
325 if not slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIDs, maskVolumeNode, inputVolumeNode):
326 logging.error(
"maskVolumeWithSegment failed: ExportSegmentsToLabelmapNode error")
327 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
328 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
329 slicer.mrmlScene.RemoveNode(maskVolumeNode)
333 img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
335 import vtkSegmentationCorePython
as vtkSegmentationCore
336 vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
338 maskToStencil = vtk.vtkImageToImageStencil()
339 maskToStencil.ThresholdByLower(0)
340 maskToStencil.SetInputData(maskVolumeNode.GetImageData())
342 stencil = vtk.vtkImageStencil()
344 if operationMode ==
"FILL_INSIDE_AND_OUTSIDE":
346 thresh = vtk.vtkImageThreshold()
347 thresh.SetInputData(inputVolumeNode.GetImageData())
348 thresh.ThresholdByLower(0)
349 thresh.SetInValue(fillValues[1])
350 thresh.SetOutValue(fillValues[1])
351 thresh.SetOutputScalarType(inputVolumeNode.GetImageData().GetScalarType())
353 stencil.SetInputData(thresh.GetOutput())
355 stencil.SetInputData(inputVolumeNode.GetImageData())
357 stencil.SetStencilConnection(maskToStencil.GetOutputPort())
358 stencil.SetReverseStencil(operationMode ==
"FILL_OUTSIDE")
359 stencil.SetBackgroundValue(fillValues[0])
362 outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
365 ijkToRas = vtk.vtkMatrix4x4()
366 inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
367 outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
368 inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
370 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
371 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
372 slicer.mrmlScene.RemoveNode(maskVolumeNode)
def updateMRMLFromGUI(self)
def __init__(self, scriptedEffect)
def onInputVolumeChanged(self)
def isVolumeVisible(self, volumeNode)
def setupOptionsFrame(self)
def onOutputVolumeChanged(self)
binaryMaskFillOutsideEdit
def updateGUIFromMRML(self)
def createCursor(self, widget)
def fillValueChanged(self)
def onOutputVisibilityButtonClicked(self)
def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None)
def onInputVisibilityButtonClicked(self)
def setMRMLDefaults(self)
def onOperationSelectionChanged(self, operationName, toggle)