Slicer  5.0
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.<br> The mask is applied to the master volume by default.<p>
35 Fill inside and outside operation creates a binary labelmap volume as output, with the inside and outside fill values modifiable.
36 </html>"""
37 
38  def setupOptionsFrame(self):
40  self.updatingGUIFromMRML = False
41  self.visibleIcon = qt.QIcon(":/Icons/Small/SlicerVisible.png")
42  self.invisibleIcon = qt.QIcon(":/Icons/Small/SlicerInvisible.png")
43 
44  # Fill operation buttons
45  self.fillInsideButton = qt.QRadioButton("Fill inside")
46  self.operationRadioButtons.append(self.fillInsideButton)
47  self.buttonToOperationNameMap[self.fillInsideButton] = 'FILL_INSIDE'
48 
49  self.fillOutsideButton = qt.QRadioButton("Fill outside")
51  self.buttonToOperationNameMap[self.fillOutsideButton] = 'FILL_OUTSIDE'
52 
53  self.binaryMaskFillButton = qt.QRadioButton("Fill inside and outside")
54  self.binaryMaskFillButton.setToolTip("Create a labelmap volume with specified inside and outside fill values.")
56  self.buttonToOperationNameMap[self.binaryMaskFillButton] = 'FILL_INSIDE_AND_OUTSIDE'
57 
58  # Operation buttons layout
59  operationLayout = qt.QGridLayout()
60  operationLayout.addWidget(self.fillInsideButton, 0, 0)
61  operationLayout.addWidget(self.fillOutsideButton, 1, 0)
62  operationLayout.addWidget(self.binaryMaskFillButton, 0, 1)
63  self.scriptedEffect.addLabeledOptionsWidget("Operation:", operationLayout)
64 
65  # fill value
66  self.fillValueEdit = ctk.ctkDoubleSpinBox()
67  self.fillValueEdit.setToolTip("Choose the voxel intensity that will be used to fill the masked region.")
68  self.fillValueLabel = qt.QLabel("Fill value: ")
69 
70  # Binary mask fill outside value
71  self.binaryMaskFillOutsideEdit = ctk.ctkDoubleSpinBox()
72  self.binaryMaskFillOutsideEdit.setToolTip("Choose the voxel intensity that will be used to fill outside the mask.")
73  self.fillOutsideLabel = qt.QLabel("Outside fill value: ")
74 
75  # Binary mask fill outside value
76  self.binaryMaskFillInsideEdit = ctk.ctkDoubleSpinBox()
77  self.binaryMaskFillInsideEdit.setToolTip("Choose the voxel intensity that will be used to fill inside the mask.")
78  self.fillInsideLabel = qt.QLabel(" Inside fill value: ")
79 
80  for fillValueEdit in [self.fillValueEdit, self.binaryMaskFillOutsideEdit, self.binaryMaskFillInsideEdit]:
81  fillValueEdit.decimalsOption = ctk.ctkDoubleSpinBox.DecimalsByValue + ctk.ctkDoubleSpinBox.DecimalsByKey + ctk.ctkDoubleSpinBox.InsertDecimals
82  fillValueEdit.minimum = vtk.vtkDoubleArray().GetDataTypeMin(vtk.VTK_DOUBLE)
83  fillValueEdit.maximum = vtk.vtkDoubleArray().GetDataTypeMax(vtk.VTK_DOUBLE)
84  fillValueEdit.connect("valueChanged(double)", self.fillValueChanged)
85 
86  # Fill value layouts
87  fillValueLayout = qt.QFormLayout()
88  fillValueLayout.addRow(self.fillValueLabel, self.fillValueEdit)
89 
90  fillOutsideLayout = qt.QFormLayout()
91  fillOutsideLayout.addRow(self.fillOutsideLabel, self.binaryMaskFillOutsideEdit)
92 
93  fillInsideLayout = qt.QFormLayout()
94  fillInsideLayout.addRow(self.fillInsideLabel, self.binaryMaskFillInsideEdit)
95 
96  binaryMaskFillLayout = qt.QHBoxLayout()
97  binaryMaskFillLayout.addLayout(fillOutsideLayout)
98  binaryMaskFillLayout.addLayout(fillInsideLayout)
99  fillValuesSpinBoxLayout = qt.QFormLayout()
100  fillValuesSpinBoxLayout.addRow(binaryMaskFillLayout)
101  fillValuesSpinBoxLayout.addRow(fillValueLayout)
102  self.scriptedEffect.addOptionsWidget(fillValuesSpinBoxLayout)
103 
104  # input volume selector
105  self.inputVolumeSelector = slicer.qMRMLNodeComboBox()
106  self.inputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
107  self.inputVolumeSelector.selectNodeUponCreation = True
108  self.inputVolumeSelector.addEnabled = True
109  self.inputVolumeSelector.removeEnabled = True
110  self.inputVolumeSelector.noneEnabled = True
111  self.inputVolumeSelector.noneDisplay = "(Master volume)"
112  self.inputVolumeSelector.showHidden = False
113  self.inputVolumeSelector.setMRMLScene(slicer.mrmlScene)
114  self.inputVolumeSelector.setToolTip("Volume to mask. Default is current master volume node.")
115  self.inputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onInputVolumeChanged)
116 
117  self.inputVisibilityButton = qt.QToolButton()
118  self.inputVisibilityButton.setIcon(self.invisibleIcon)
119  self.inputVisibilityButton.connect('clicked()', self.onInputVisibilityButtonClicked)
120  inputLayout = qt.QHBoxLayout()
121  inputLayout.addWidget(self.inputVisibilityButton)
122  inputLayout.addWidget(self.inputVolumeSelector)
123  self.scriptedEffect.addLabeledOptionsWidget("Input Volume: ", inputLayout)
124 
125  # output volume selector
126  self.outputVolumeSelector = slicer.qMRMLNodeComboBox()
127  self.outputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", "vtkMRMLLabelMapVolumeNode"]
128  self.outputVolumeSelector.selectNodeUponCreation = True
129  self.outputVolumeSelector.addEnabled = True
130  self.outputVolumeSelector.removeEnabled = True
131  self.outputVolumeSelector.renameEnabled = True
132  self.outputVolumeSelector.noneEnabled = True
133  self.outputVolumeSelector.noneDisplay = "(Create new Volume)"
134  self.outputVolumeSelector.showHidden = False
135  self.outputVolumeSelector.setMRMLScene(slicer.mrmlScene)
136  self.outputVolumeSelector.setToolTip("Masked output volume. It may be the same as the input volume for cumulative masking.")
137  self.outputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onOutputVolumeChanged)
138 
139  self.outputVisibilityButton = qt.QToolButton()
140  self.outputVisibilityButton.setIcon(self.invisibleIcon)
141  self.outputVisibilityButton.connect('clicked()', self.onOutputVisibilityButtonClicked)
142  outputLayout = qt.QHBoxLayout()
143  outputLayout.addWidget(self.outputVisibilityButton)
144  outputLayout.addWidget(self.outputVolumeSelector)
145  self.scriptedEffect.addLabeledOptionsWidget("Output Volume: ", outputLayout)
146 
147  # Apply button
148  self.applyButton = qt.QPushButton("Apply")
149  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
150  self.applyButton.setToolTip("Apply segment as volume mask. No undo operation available once applied.")
151  self.scriptedEffect.addOptionsWidget(self.applyButton)
152  self.applyButton.connect('clicked()', self.onApply)
153 
154  for button in self.operationRadioButtons:
155  button.connect('toggled(bool)',
156  lambda toggle, widget=self.buttonToOperationNameMap[button]: self.onOperationSelectionChanged(widget, toggle))
157 
158  def createCursor(self, widget):
159  # Turn off effect-specific cursor for this effect
160  return slicer.util.mainWindow().cursor
161 
162  def setMRMLDefaults(self):
163  self.scriptedEffect.setParameterDefault("FillValue", "0")
164  self.scriptedEffect.setParameterDefault("BinaryMaskFillValueInside", "1")
165  self.scriptedEffect.setParameterDefault("BinaryMaskFillValueOutside", "0")
166  self.scriptedEffect.setParameterDefault("Operation", "FILL_OUTSIDE")
167 
168  def isVolumeVisible(self, volumeNode):
169  if not volumeNode:
170  return False
171  volumeNodeID = volumeNode.GetID()
172  lm = slicer.app.layoutManager()
173  sliceViewNames = lm.sliceViewNames()
174  for sliceViewName in sliceViewNames:
175  sliceWidget = lm.sliceWidget(sliceViewName)
176  if volumeNodeID == sliceWidget.mrmlSliceCompositeNode().GetBackgroundVolumeID():
177  return True
178  return False
179 
180  def updateGUIFromMRML(self):
181  self.updatingGUIFromMRML = True
182 
183  self.fillValueEdit.setValue(float(self.scriptedEffect.parameter("FillValue")) if self.scriptedEffect.parameter("FillValue") else 0)
184  self.binaryMaskFillOutsideEdit.setValue(float(self.scriptedEffect.parameter("BinaryMaskFillValueOutside"))
185  if self.scriptedEffect.parameter("BinaryMaskFillValueOutside") else 0)
186  self.binaryMaskFillInsideEdit.setValue(float(self.scriptedEffect.parameter("BinaryMaskFillValueInside"))
187  if self.scriptedEffect.parameter("BinaryMaskFillValueInside") else 1)
188  operationName = self.scriptedEffect.parameter("Operation")
189  if operationName:
190  operationButton = list(self.buttonToOperationNameMap.keys())[list(self.buttonToOperationNameMap.values()).index(operationName)]
191  operationButton.setChecked(True)
192 
193  inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.InputVolume")
194  self.inputVolumeSelector.setCurrentNode(inputVolume)
195  outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.OutputVolume")
196  self.outputVolumeSelector.setCurrentNode(outputVolume)
197 
198  masterVolume = self.scriptedEffect.parameterSetNode().GetMasterVolumeNode()
199  if inputVolume is None:
200  inputVolume = masterVolume
201 
202  self.fillValueEdit.setVisible(operationName in ["FILL_INSIDE", "FILL_OUTSIDE"])
203  self.fillValueLabel.setVisible(operationName in ["FILL_INSIDE", "FILL_OUTSIDE"])
204  self.binaryMaskFillInsideEdit.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
205  self.fillInsideLabel.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
206  self.binaryMaskFillOutsideEdit.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
207  self.fillOutsideLabel.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
208  if operationName in ["FILL_INSIDE", "FILL_OUTSIDE"]:
209  if self.outputVolumeSelector.noneDisplay != "(Create new Volume)":
210  self.outputVolumeSelector.noneDisplay = "(Create new Volume)"
211  self.outputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", "vtkMRMLLabelMapVolumeNode"]
212  else:
213  if self.outputVolumeSelector.noneDisplay != "(Create new Labelmap Volume)":
214  self.outputVolumeSelector.noneDisplay = "(Create new Labelmap Volume)"
215  self.outputVolumeSelector.nodeTypes = ["vtkMRMLLabelMapVolumeNode", "vtkMRMLScalarVolumeNode"]
216 
217  self.inputVisibilityButton.setIcon(self.visibleIcon if self.isVolumeVisible(inputVolume) else self.invisibleIcon)
218  self.outputVisibilityButton.setIcon(self.visibleIcon if self.isVolumeVisible(outputVolume) else self.invisibleIcon)
219 
220  self.updatingGUIFromMRML = False
221 
222  def updateMRMLFromGUI(self):
223  if self.updatingGUIFromMRML:
224  return
225  self.scriptedEffect.setParameter("FillValue", self.fillValueEdit.value)
226  self.scriptedEffect.setParameter("BinaryMaskFillValueInside", self.binaryMaskFillInsideEdit.value)
227  self.scriptedEffect.setParameter("BinaryMaskFillValueOutside", self.binaryMaskFillOutsideEdit.value)
228  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.InputVolume", self.inputVolumeSelector.currentNodeID)
229  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.OutputVolume", self.outputVolumeSelector.currentNodeID)
230 
231  def activate(self):
232  self.scriptedEffect.setParameter("InputVisibility", "True")
233 
234  def deactivate(self):
235  if self.outputVolumeSelector.currentNode() is not self.scriptedEffect.parameterSetNode().GetMasterVolumeNode():
236  self.scriptedEffect.setParameter("OutputVisibility", "False")
237  slicer.util.setSliceViewerLayers(background=self.scriptedEffect.parameterSetNode().GetMasterVolumeNode())
238 
239  def onOperationSelectionChanged(self, operationName, toggle):
240  if not toggle:
241  return
242  self.scriptedEffect.setParameter("Operation", operationName)
243 
244  def getInputVolume(self):
245  inputVolume = self.inputVolumeSelector.currentNode()
246  if inputVolume is None:
247  inputVolume = self.scriptedEffect.parameterSetNode().GetMasterVolumeNode()
248  return inputVolume
249 
251  inputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.InputVolume")
252  masterVolume = self.scriptedEffect.parameterSetNode().GetMasterVolumeNode()
253  if inputVolume is None:
254  inputVolume = masterVolume
255  if inputVolume:
256  slicer.util.setSliceViewerLayers(background=inputVolume)
257  self.updateGUIFromMRML()
258 
260  outputVolume = self.scriptedEffect.parameterSetNode().GetNodeReference("Mask volume.OutputVolume")
261  if outputVolume:
262  slicer.util.setSliceViewerLayers(background=outputVolume)
263  self.updateGUIFromMRML()
264 
266  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.InputVolume", self.inputVolumeSelector.currentNodeID)
267  self.updateGUIFromMRML() # node reference changes are not observed, update GUI manually
268 
270  self.scriptedEffect.parameterSetNode().SetNodeReferenceID("Mask volume.OutputVolume", self.outputVolumeSelector.currentNodeID)
271  self.updateGUIFromMRML() # node reference changes are not observed, update GUI manually
272 
273  def fillValueChanged(self):
274  self.updateMRMLFromGUI()
275 
276  def onApply(self):
277  inputVolume = self.getInputVolume()
278  outputVolume = self.outputVolumeSelector.currentNode()
279  operationMode = self.scriptedEffect.parameter("Operation")
280  if not outputVolume:
281  # Create new node for output
282  volumesLogic = slicer.modules.volumes.logic()
283  scene = inputVolume.GetScene()
284  if operationMode == "FILL_INSIDE_AND_OUTSIDE":
285  outputVolumeName = inputVolume.GetName() + " label"
286  outputVolume = volumesLogic.CreateAndAddLabelVolume(inputVolume, outputVolumeName)
287  else:
288  outputVolumeName = inputVolume.GetName() + " masked"
289  outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName, False)
290  self.outputVolumeSelector.setCurrentNode(outputVolume)
291 
292  if operationMode in ["FILL_INSIDE", "FILL_OUTSIDE"]:
293  fillValues = [self.fillValueEdit.value]
294  else:
295  fillValues = [self.binaryMaskFillInsideEdit.value, self.binaryMaskFillOutsideEdit.value]
296 
297  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
298  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
299 
300  slicer.app.setOverrideCursor(qt.Qt.WaitCursor)
301  SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume)
302 
303  slicer.util.setSliceViewerLayers(background=outputVolume)
304  qt.QApplication.restoreOverrideCursor()
305 
306  self.updateGUIFromMRML()
307 
308  @staticmethod
309  def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None):
310  """
311  Fill voxels of the input volume inside/outside the masking model with the provided fill value
312  maskExtent: optional output to return computed mask extent (expected input is a 6-element list)
313  fillValues: list containing one or two fill values. If fill mode is inside or outside then only one value is specified in the list.
314  If fill mode is inside&outside then the list must contain two values: first is the inside fill, second is the outside fill value.
315  """
316 
317  segmentIDs = vtk.vtkStringArray()
318  segmentIDs.InsertNextValue(segmentID)
319  maskVolumeNode = slicer.modules.volumes.logic().CreateAndAddLabelVolume(inputVolumeNode, "TemporaryVolumeMask")
320  if not maskVolumeNode:
321  logging.error("maskVolumeWithSegment failed: invalid maskVolumeNode")
322  return False
323 
324  if not slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIDs, maskVolumeNode, inputVolumeNode):
325  logging.error("maskVolumeWithSegment failed: ExportSegmentsToLabelmapNode error")
326  slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
327  slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
328  slicer.mrmlScene.RemoveNode(maskVolumeNode)
329  return False
330 
331  if maskExtent:
332  img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
333  img.UnRegister(None)
334  import vtkSegmentationCorePython as vtkSegmentationCore
335  vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
336 
337  maskToStencil = vtk.vtkImageToImageStencil()
338  maskToStencil.ThresholdByLower(0)
339  maskToStencil.SetInputData(maskVolumeNode.GetImageData())
340 
341  stencil = vtk.vtkImageStencil()
342 
343  if operationMode == "FILL_INSIDE_AND_OUTSIDE":
344  # Set input to constant value
345  thresh = vtk.vtkImageThreshold()
346  thresh.SetInputData(inputVolumeNode.GetImageData())
347  thresh.ThresholdByLower(0)
348  thresh.SetInValue(fillValues[1])
349  thresh.SetOutValue(fillValues[1])
350  thresh.SetOutputScalarType(inputVolumeNode.GetImageData().GetScalarType())
351  thresh.Update()
352  stencil.SetInputData(thresh.GetOutput())
353  else:
354  stencil.SetInputData(inputVolumeNode.GetImageData())
355 
356  stencil.SetStencilConnection(maskToStencil.GetOutputPort())
357  stencil.SetReverseStencil(operationMode == "FILL_OUTSIDE")
358  stencil.SetBackgroundValue(fillValues[0])
359  stencil.Update()
360 
361  outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
362 
363  # Set the same geometry and parent transform as the input volume
364  ijkToRas = vtk.vtkMatrix4x4()
365  inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
366  outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
367  inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
368 
369  slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
370  slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
371  slicer.mrmlScene.RemoveNode(maskVolumeNode)
372  return True
def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None)