Slicer  5.3
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorMaskVolumeEffect.py
Go to the documentation of this file.
1 import os
2 import vtk, qt, ctk, slicer
3 import logging
4 from SegmentEditorEffects import *
5 
6 
8  """This effect fills a selected volume node inside and/or outside a segment with a chosen value.
9  """
10 
11  def __init__(self, scriptedEffect):
12  scriptedEffect.name = 'Mask volume'
13  scriptedEffect.perSegment = True # this effect operates on a single selected segment
14  AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
15 
16  # Effect-specific members
18 
19  def clone(self):
20  # It should not be necessary to modify this method
21  import qSlicerSegmentationsEditorEffectsPythonQt as effects
22  clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
23  clonedEffect.setPythonSource(__file__.replace('\\', '/'))
24  return clonedEffect
25 
26  def icon(self):
27  # It should not be necessary to modify this method
28  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/MaskVolume.png')
29  if os.path.exists(iconPath):
30  return qt.QIcon(iconPath)
31  return qt.QIcon()
32 
33  def helpText(self):
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.
37 </html>"""
38 
39  def setupOptionsFrame(self):
41  self.updatingGUIFromMRML = False
42  self.visibleIcon = qt.QIcon(":/Icons/Small/SlicerVisible.png")
43  self.invisibleIcon = qt.QIcon(":/Icons/Small/SlicerInvisible.png")
44 
45  # Fill operation buttons
46  self.fillInsideButton = qt.QRadioButton("Fill inside")
47  self.operationRadioButtons.append(self.fillInsideButton)
48  self.buttonToOperationNameMap[self.fillInsideButton] = 'FILL_INSIDE'
49 
50  self.fillOutsideButton = qt.QRadioButton("Fill outside")
52  self.buttonToOperationNameMap[self.fillOutsideButton] = 'FILL_OUTSIDE'
53 
54  self.binaryMaskFillButton = qt.QRadioButton("Fill inside and outside")
55  self.binaryMaskFillButton.setToolTip("Create a labelmap volume with specified inside and outside fill values.")
57  self.buttonToOperationNameMap[self.binaryMaskFillButton] = 'FILL_INSIDE_AND_OUTSIDE'
58 
59  # Operation buttons layout
60  operationLayout = qt.QGridLayout()
61  operationLayout.addWidget(self.fillInsideButton, 0, 0)
62  operationLayout.addWidget(self.fillOutsideButton, 1, 0)
63  operationLayout.addWidget(self.binaryMaskFillButton, 0, 1)
64  self.scriptedEffect.addLabeledOptionsWidget("Operation:", operationLayout)
65 
66  # fill value
67  self.fillValueEdit = ctk.ctkDoubleSpinBox()
68  self.fillValueEdit.setToolTip("Choose the voxel intensity that will be used to fill the masked region.")
69  self.fillValueLabel = qt.QLabel("Fill value: ")
70 
71  # Binary mask fill outside value
72  self.binaryMaskFillOutsideEdit = ctk.ctkDoubleSpinBox()
73  self.binaryMaskFillOutsideEdit.setToolTip("Choose the voxel intensity that will be used to fill outside the mask.")
74  self.fillOutsideLabel = qt.QLabel("Outside fill value: ")
75 
76  # Binary mask fill outside value
77  self.binaryMaskFillInsideEdit = ctk.ctkDoubleSpinBox()
78  self.binaryMaskFillInsideEdit.setToolTip("Choose the voxel intensity that will be used to fill inside the mask.")
79  self.fillInsideLabel = qt.QLabel(" Inside fill value: ")
80 
81  for fillValueEdit in [self.fillValueEdit, self.binaryMaskFillOutsideEdit, self.binaryMaskFillInsideEdit]:
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)
85  fillValueEdit.connect("valueChanged(double)", self.fillValueChanged)
86 
87  # Fill value layouts
88  fillValueLayout = qt.QFormLayout()
89  fillValueLayout.addRow(self.fillValueLabel, self.fillValueEdit)
90 
91  fillOutsideLayout = qt.QFormLayout()
92  fillOutsideLayout.addRow(self.fillOutsideLabel, self.binaryMaskFillOutsideEdit)
93 
94  fillInsideLayout = qt.QFormLayout()
95  fillInsideLayout.addRow(self.fillInsideLabel, self.binaryMaskFillInsideEdit)
96 
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)
104 
105  # input volume selector
106  self.inputVolumeSelector = slicer.qMRMLNodeComboBox()
107  self.inputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
108  self.inputVolumeSelector.selectNodeUponCreation = True
109  self.inputVolumeSelector.addEnabled = True
110  self.inputVolumeSelector.removeEnabled = True
111  self.inputVolumeSelector.noneEnabled = True
112  self.inputVolumeSelector.noneDisplay = "(Source volume)"
113  self.inputVolumeSelector.showHidden = False
114  self.inputVolumeSelector.setMRMLScene(slicer.mrmlScene)
115  self.inputVolumeSelector.setToolTip("Volume to mask. Default is current source volume node.")
116  self.inputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onInputVolumeChanged)
117 
118  self.inputVisibilityButton = qt.QToolButton()
119  self.inputVisibilityButton.setIcon(self.invisibleIcon)
120  self.inputVisibilityButton.connect('clicked()', self.onInputVisibilityButtonClicked)
121  inputLayout = qt.QHBoxLayout()
122  inputLayout.addWidget(self.inputVisibilityButton)
123  inputLayout.addWidget(self.inputVolumeSelector)
124  self.scriptedEffect.addLabeledOptionsWidget("Input Volume: ", inputLayout)
125 
126  # output volume selector
127  self.outputVolumeSelector = slicer.qMRMLNodeComboBox()
128  self.outputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", "vtkMRMLLabelMapVolumeNode"]
129  self.outputVolumeSelector.selectNodeUponCreation = True
130  self.outputVolumeSelector.addEnabled = True
131  self.outputVolumeSelector.removeEnabled = True
132  self.outputVolumeSelector.renameEnabled = True
133  self.outputVolumeSelector.noneEnabled = True
134  self.outputVolumeSelector.noneDisplay = "(Create new Volume)"
135  self.outputVolumeSelector.showHidden = False
136  self.outputVolumeSelector.setMRMLScene(slicer.mrmlScene)
137  self.outputVolumeSelector.setToolTip("Masked output volume. It may be the same as the input volume for cumulative masking.")
138  self.outputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onOutputVolumeChanged)
139 
140  self.outputVisibilityButton = qt.QToolButton()
141  self.outputVisibilityButton.setIcon(self.invisibleIcon)
142  self.outputVisibilityButton.connect('clicked()', self.onOutputVisibilityButtonClicked)
143  outputLayout = qt.QHBoxLayout()
144  outputLayout.addWidget(self.outputVisibilityButton)
145  outputLayout.addWidget(self.outputVolumeSelector)
146  self.scriptedEffect.addLabeledOptionsWidget("Output Volume: ", outputLayout)
147 
148  # Apply button
149  self.applyButton = qt.QPushButton("Apply")
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)
153  self.applyButton.connect('clicked()', self.onApply)
154 
155  for button in self.operationRadioButtons:
156  button.connect('toggled(bool)',
157  lambda toggle, widget=self.buttonToOperationNameMap[button]: self.onOperationSelectionChanged(widget, toggle))
158 
159  def createCursor(self, widget):
160  # Turn off effect-specific cursor for this effect
161  return slicer.util.mainWindow().cursor
162 
163  def setMRMLDefaults(self):
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")
168 
169  def isVolumeVisible(self, volumeNode):
170  if not volumeNode:
171  return False
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():
178  return True
179  return False
180 
181  def updateGUIFromMRML(self):
182  self.updatingGUIFromMRML = True
183 
184  self.fillValueEdit.setValue(float(self.scriptedEffect.parameter("FillValue")) if self.scriptedEffect.parameter("FillValue") else 0)
185  self.binaryMaskFillOutsideEdit.setValue(float(self.scriptedEffect.parameter("BinaryMaskFillValueOutside"))
186  if self.scriptedEffect.parameter("BinaryMaskFillValueOutside") else 0)
187  self.binaryMaskFillInsideEdit.setValue(float(self.scriptedEffect.parameter("BinaryMaskFillValueInside"))
188  if self.scriptedEffect.parameter("BinaryMaskFillValueInside") else 1)
189  operationName = self.scriptedEffect.parameter("Operation")
190  if operationName:
191  operationButton = list(self.buttonToOperationNameMap.keys())[list(self.buttonToOperationNameMap.values()).index(operationName)]
192  operationButton.setChecked(True)
193 
194  inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.InputVolume")
195  self.inputVolumeSelector.setCurrentNode(inputVolume)
196  outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.OutputVolume")
197  self.outputVolumeSelector.setCurrentNode(outputVolume)
198 
199  sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
200  if inputVolume is None:
201  inputVolume = sourceVolume
202 
203  self.fillValueEdit.setVisible(operationName in ["FILL_INSIDE", "FILL_OUTSIDE"])
204  self.fillValueLabel.setVisible(operationName in ["FILL_INSIDE", "FILL_OUTSIDE"])
205  self.binaryMaskFillInsideEdit.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
206  self.fillInsideLabel.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
207  self.binaryMaskFillOutsideEdit.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
208  self.fillOutsideLabel.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
209  if operationName in ["FILL_INSIDE", "FILL_OUTSIDE"]:
210  if self.outputVolumeSelector.noneDisplay != "(Create new Volume)":
211  self.outputVolumeSelector.noneDisplay = "(Create new Volume)"
212  self.outputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", "vtkMRMLLabelMapVolumeNode"]
213  else:
214  if self.outputVolumeSelector.noneDisplay != "(Create new Labelmap Volume)":
215  self.outputVolumeSelector.noneDisplay = "(Create new Labelmap Volume)"
216  self.outputVolumeSelector.nodeTypes = ["vtkMRMLLabelMapVolumeNode", "vtkMRMLScalarVolumeNode"]
217 
218  self.inputVisibilityButton.setIcon(self.visibleIcon if self.isVolumeVisible(inputVolume) else self.invisibleIcon)
219  self.outputVisibilityButton.setIcon(self.visibleIcon if self.isVolumeVisible(outputVolume) else self.invisibleIcon)
220 
221  self.updatingGUIFromMRML = False
222 
223  def updateMRMLFromGUI(self):
224  if self.updatingGUIFromMRML:
225  return
226  self.scriptedEffect.setParameter("FillValue", self.fillValueEdit.value)
227  self.scriptedEffect.setParameter("BinaryMaskFillValueInside", self.binaryMaskFillInsideEdit.value)
228  self.scriptedEffect.setParameter("BinaryMaskFillValueOutside", self.binaryMaskFillOutsideEdit.value)
229  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.InputVolume", self.inputVolumeSelector.currentNodeID)
230  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.OutputVolume", self.outputVolumeSelector.currentNodeID)
231 
232  def activate(self):
233  self.scriptedEffect.setParameter("InputVisibility", "True")
234 
235  def deactivate(self):
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())
239 
240  def onOperationSelectionChanged(self, operationName, toggle):
241  if not toggle:
242  return
243  self.scriptedEffect.setParameter("Operation", operationName)
244 
245  def getInputVolume(self):
246  inputVolume = self.inputVolumeSelector.currentNode()
247  if inputVolume is None:
248  inputVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
249  return inputVolume
250 
252  inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.InputVolume")
253  sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
254  if inputVolume is None:
255  inputVolume = sourceVolume
256  if inputVolume:
257  slicer.util.setSliceViewerLayers(background=inputVolume)
258  self.updateGUIFromMRML()
259 
261  outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.OutputVolume")
262  if outputVolume:
263  slicer.util.setSliceViewerLayers(background=outputVolume)
264  self.updateGUIFromMRML()
265 
267  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.InputVolume", self.inputVolumeSelector.currentNodeID)
268  self.updateGUIFromMRML() # node reference changes are not observed, update GUI manually
269 
271  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.OutputVolume", self.outputVolumeSelector.currentNodeID)
272  self.updateGUIFromMRML() # node reference changes are not observed, update GUI manually
273 
274  def fillValueChanged(self):
275  self.updateMRMLFromGUI()
276 
277  def onApply(self):
278  inputVolume = self.getInputVolume()
279  outputVolume = self.outputVolumeSelector.currentNode()
280  operationMode = self.scriptedEffect.parameter("Operation")
281  if not outputVolume:
282  # Create new node for output
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)
288  else:
289  outputVolumeName = inputVolume.GetName() + " masked"
290  outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName, False)
291  self.outputVolumeSelector.setCurrentNode(outputVolume)
292 
293  if operationMode in ["FILL_INSIDE", "FILL_OUTSIDE"]:
294  fillValues = [self.fillValueEdit.value]
295  else:
296  fillValues = [self.binaryMaskFillInsideEdit.value, self.binaryMaskFillOutsideEdit.value]
297 
298  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
299  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
300 
301  slicer.app.setOverrideCursor(qt.Qt.WaitCursor)
302  SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume)
303 
304  slicer.util.setSliceViewerLayers(background=outputVolume)
305  qt.QApplication.restoreOverrideCursor()
306 
307  self.updateGUIFromMRML()
308 
309  @staticmethod
310  def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None):
311  """
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.
316  """
317 
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")
323  return False
324 
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)
330  return False
331 
332  if maskExtent:
333  img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
334  img.UnRegister(None)
335  import vtkSegmentationCorePython as vtkSegmentationCore
336  vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
337 
338  maskToStencil = vtk.vtkImageToImageStencil()
339  maskToStencil.ThresholdByLower(0)
340  maskToStencil.SetInputData(maskVolumeNode.GetImageData())
341 
342  stencil = vtk.vtkImageStencil()
343 
344  if operationMode == "FILL_INSIDE_AND_OUTSIDE":
345  # Set input to constant value
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())
352  thresh.Update()
353  stencil.SetInputData(thresh.GetOutput())
354  else:
355  stencil.SetInputData(inputVolumeNode.GetImageData())
356 
357  stencil.SetStencilConnection(maskToStencil.GetOutputPort())
358  stencil.SetReverseStencil(operationMode == "FILL_OUTSIDE")
359  stencil.SetBackgroundValue(fillValues[0])
360  stencil.Update()
361 
362  outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
363 
364  # Set the same geometry and parent transform as the input volume
365  ijkToRas = vtk.vtkMatrix4x4()
366  inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
367  outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
368  inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
369 
370  slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
371  slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
372  slicer.mrmlScene.RemoveNode(maskVolumeNode)
373  return True
def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None)