Slicer  4.11
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 
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)