Slicer  5.2
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.
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)