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