Slicer  5.0
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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)