Slicer 5.9
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.
1import os
2import vtk, qt, ctk, slicer
3import logging
4from SegmentEditorEffects import *
5
6from slicer.i18n import tr as _
7
8
10 """This effect fills a selected volume node inside and/or outside a segment with a chosen value."""
11
12 def __init__(self, scriptedEffect):
13 scriptedEffect.name = "Mask volume" # no tr (don't translate it because modules find effects by name)
14 scriptedEffect.title = _("Mask volume")
15 scriptedEffect.perSegment = True # this effect operates on a single selected segment
16 AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
17
18 # Effect-specific members
20
21 def clone(self):
22 # It should not be necessary to modify this method
23 import qSlicerSegmentationsEditorEffectsPythonQt as effects
24
25 clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
26 clonedEffect.setPythonSource(__file__.replace("\\", "/"))
27 return clonedEffect
28
29 def icon(self):
30 # It should not be necessary to modify this method
31 iconPath = os.path.join(os.path.dirname(__file__), "Resources/Icons/MaskVolume.png")
32 if os.path.exists(iconPath):
33 return qt.QIcon(iconPath)
34 return qt.QIcon()
35
36 def helpText(self):
37 return "<html>" + _("""Use the currently selected segment as a mask to blank out regions in a volume<br>.
38The mask is applied to the source volume by default.<p>
39Fill inside and outside operation creates a binary labelmap volume as output, with the inside and outside fill values modifiable.
40""")
41
42 def setupOptionsFrame(self):
45 self.visibleIcon = qt.QIcon(":/Icons/Small/SlicerVisible.png")
46 self.invisibleIcon = qt.QIcon(":/Icons/Small/SlicerInvisible.png")
47
48 # Fill operation buttons
49 self.fillInsideButton = qt.QRadioButton(_("Fill inside"))
51 self.buttonToOperationNameMap[self.fillInsideButton] = "FILL_INSIDE"
52
53 self.fillOutsideButton = qt.QRadioButton(_("Fill outside"))
55 self.buttonToOperationNameMap[self.fillOutsideButton] = "FILL_OUTSIDE"
56
57 self.binaryMaskFillButton = qt.QRadioButton(_("Fill inside and outside"))
58 self.binaryMaskFillButton.setToolTip(_("Create a labelmap volume with specified inside and outside fill values."))
60 self.buttonToOperationNameMap[self.binaryMaskFillButton] = "FILL_INSIDE_AND_OUTSIDE"
61
62 # Operation buttons layout
63 operationLayout = qt.QGridLayout()
64 operationLayout.addWidget(self.fillInsideButton, 0, 0)
65 operationLayout.addWidget(self.fillOutsideButton, 1, 0)
66 operationLayout.addWidget(self.binaryMaskFillButton, 0, 1)
67 self.scriptedEffect.addLabeledOptionsWidget(_("Operation:"), operationLayout)
68
69 # fill value
70 self.fillValueEdit = ctk.ctkDoubleSpinBox()
71 self.fillValueEdit.setToolTip(_("Choose the voxel intensity that will be used to fill the masked region."))
72 self.fillValueLabel = qt.QLabel(_("Fill value: "))
73
74 # Binary mask fill outside value
75 self.binaryMaskFillOutsideEdit = ctk.ctkDoubleSpinBox()
76 self.binaryMaskFillOutsideEdit.setToolTip(_("Choose the voxel intensity that will be used to fill outside the mask."))
77 self.fillOutsideLabel = qt.QLabel(_("Outside fill value: "))
78
79 # Binary mask fill outside value
80 self.binaryMaskFillInsideEdit = ctk.ctkDoubleSpinBox()
81 self.binaryMaskFillInsideEdit.setToolTip(_("Choose the voxel intensity that will be used to fill inside the mask."))
82 self.fillInsideLabel = qt.QLabel(_(" Inside fill value: "))
83
84 for fillValueEdit in [self.fillValueEdit, self.binaryMaskFillOutsideEdit, self.binaryMaskFillInsideEdit]:
85 fillValueEdit.decimalsOption = ctk.ctkDoubleSpinBox.DecimalsByValue + ctk.ctkDoubleSpinBox.DecimalsByKey + ctk.ctkDoubleSpinBox.InsertDecimals
86 fillValueEdit.minimum = vtk.vtkDoubleArray().GetDataTypeMin(vtk.VTK_DOUBLE)
87 fillValueEdit.maximum = vtk.vtkDoubleArray().GetDataTypeMax(vtk.VTK_DOUBLE)
88 fillValueEdit.connect("valueChanged(double)", self.fillValueChanged)
89
90 # Fill value layouts
91 fillValueLayout = qt.QFormLayout()
92 fillValueLayout.addRow(self.fillValueLabel, self.fillValueEdit)
93
94 fillOutsideLayout = qt.QFormLayout()
95 fillOutsideLayout.addRow(self.fillOutsideLabel, self.binaryMaskFillOutsideEdit)
96
97 fillInsideLayout = qt.QFormLayout()
98 fillInsideLayout.addRow(self.fillInsideLabel, self.binaryMaskFillInsideEdit)
99
100 binaryMaskFillLayout = qt.QHBoxLayout()
101 binaryMaskFillLayout.addLayout(fillOutsideLayout)
102 binaryMaskFillLayout.addLayout(fillInsideLayout)
103 fillValuesSpinBoxLayout = qt.QFormLayout()
104 fillValuesSpinBoxLayout.addRow(binaryMaskFillLayout)
105 fillValuesSpinBoxLayout.addRow(fillValueLayout)
106 self.scriptedEffect.addOptionsWidget(fillValuesSpinBoxLayout)
107
108 # Soft edge
109 self.softEdgeMmSpinBox = slicer.qMRMLSpinBox()
110 self.softEdgeMmSpinBox.setMRMLScene(slicer.mrmlScene)
111 self.softEdgeMmSpinBox.setToolTip(_("Standard deviation of the Gaussian function that blurs the edge of the mask."
112 " Higher value makes the edge softer."))
113 self.softEdgeMmSpinBox.quantity = "length"
114 self.softEdgeMmSpinBox.value = 0
115 self.softEdgeMmSpinBox.minimum = 0
116 self.softEdgeMmSpinBox.singleStep = 0.5
117 self.softEdgeMmLabel = self.scriptedEffect.addLabeledOptionsWidget(_("Soft edge:"), self.softEdgeMmSpinBox)
118 self.softEdgeMmSpinBox.connect("valueChanged(double)", self.softEdgeMmChanged)
119
120 # input volume selector
121 self.inputVolumeSelector = slicer.qMRMLNodeComboBox()
122 self.inputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
123 self.inputVolumeSelector.selectNodeUponCreation = True
124 self.inputVolumeSelector.addEnabled = True
125 self.inputVolumeSelector.removeEnabled = True
126 self.inputVolumeSelector.noneEnabled = True
127 self.inputVolumeSelector.noneDisplay = _("(Source volume)")
128 self.inputVolumeSelector.showHidden = False
129 self.inputVolumeSelector.setMRMLScene(slicer.mrmlScene)
130 self.inputVolumeSelector.setToolTip(_("Volume to mask. Default is current source volume node."))
131 self.inputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onInputVolumeChanged)
132
133 self.inputVisibilityButton = qt.QToolButton()
134 self.inputVisibilityButton.setIcon(self.invisibleIcon)
136 inputLayout = qt.QHBoxLayout()
137 inputLayout.addWidget(self.inputVisibilityButton)
138 inputLayout.addWidget(self.inputVolumeSelector)
139 self.scriptedEffect.addLabeledOptionsWidget(_("Input Volume: "), inputLayout)
140
141 # output volume selector
142 self.outputVolumeSelector = slicer.qMRMLNodeComboBox()
143 self.outputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", "vtkMRMLLabelMapVolumeNode"]
144 self.outputVolumeSelector.selectNodeUponCreation = True
145 self.outputVolumeSelector.addEnabled = True
146 self.outputVolumeSelector.removeEnabled = True
147 self.outputVolumeSelector.renameEnabled = True
148 self.outputVolumeSelector.noneEnabled = True
149 self.outputVolumeSelector.noneDisplay = _("(Create new Volume)")
150 self.outputVolumeSelector.showHidden = False
151 self.outputVolumeSelector.setMRMLScene(slicer.mrmlScene)
152 self.outputVolumeSelector.setToolTip(_("Masked output volume. It may be the same as the input volume for cumulative masking."))
153 self.outputVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onOutputVolumeChanged)
154
155 self.outputVisibilityButton = qt.QToolButton()
156 self.outputVisibilityButton.setIcon(self.invisibleIcon)
158 outputLayout = qt.QHBoxLayout()
159 outputLayout.addWidget(self.outputVisibilityButton)
160 outputLayout.addWidget(self.outputVolumeSelector)
161 self.scriptedEffect.addLabeledOptionsWidget(_("Output Volume: "), outputLayout)
162
163 # Apply button
164 self.applyButton = qt.QPushButton(_("Apply"))
165 self.applyButton.objectName = self.__class__.__name__ + "Apply"
166 self.applyButton.setToolTip(_("Apply segment as volume mask. No undo operation available once applied."))
167 self.scriptedEffect.addOptionsWidget(self.applyButton)
168 self.applyButton.connect("clicked()", self.onApply)
169
170 for button in self.operationRadioButtons:
171 button.connect("toggled(bool)",
172 lambda toggle, widget=self.buttonToOperationNameMap[button]: self.onOperationSelectionChanged(widget, toggle))
173
174 def createCursor(self, widget):
175 # Turn off effect-specific cursor for this effect
176 return slicer.util.mainWindow().cursor
177
178 def setMRMLDefaults(self):
179 self.scriptedEffect.setParameterDefault("FillValue", "0")
180 self.scriptedEffect.setParameterDefault("BinaryMaskFillValueInside", "1")
181 self.scriptedEffect.setParameterDefault("BinaryMaskFillValueOutside", "0")
182 self.scriptedEffect.setParameterDefault("SoftEdgeMm", "0")
183 self.scriptedEffect.setParameterDefault("Operation", "FILL_OUTSIDE")
184
185 def isVolumeVisible(self, volumeNode):
186 if not volumeNode:
187 return False
188 volumeNodeID = volumeNode.GetID()
189 lm = slicer.app.layoutManager()
190 sliceViewNames = lm.sliceViewNames()
191 for sliceViewName in sliceViewNames:
192 sliceWidget = lm.sliceWidget(sliceViewName)
193 if volumeNodeID == sliceWidget.mrmlSliceCompositeNode().GetBackgroundVolumeID():
194 return True
195 return False
196
197 def updateGUIFromMRML(self):
198 self.updatingGUIFromMRML = True
199
200 self.fillValueEdit.setValue(float(self.scriptedEffect.parameter("FillValue")) if self.scriptedEffect.parameter("FillValue") else 0)
201 self.binaryMaskFillOutsideEdit.setValue(float(self.scriptedEffect.parameter("BinaryMaskFillValueOutside"))
202 if self.scriptedEffect.parameter("BinaryMaskFillValueOutside") else 0)
203 self.binaryMaskFillInsideEdit.setValue(float(self.scriptedEffect.parameter("BinaryMaskFillValueInside"))
204 if self.scriptedEffect.parameter("BinaryMaskFillValueInside") else 1)
205 operationName = self.scriptedEffect.parameter("Operation")
206 if operationName:
207 operationButton = list(self.buttonToOperationNameMap.keys())[list(self.buttonToOperationNameMap.values()).index(operationName)]
208 operationButton.setChecked(True)
209
210 self.softEdgeMmSpinBox.setValue(float(self.scriptedEffect.parameter("SoftEdgeMm"))
211 if self.scriptedEffect.parameter("SoftEdgeMm") else 0)
212
213 inputVolume = self.scriptedEffect.nodeReference("InputVolume")
214 self.inputVolumeSelector.setCurrentNode(inputVolume)
215 outputVolume = self.scriptedEffect.nodeReference("OutputVolume")
216 self.outputVolumeSelector.setCurrentNode(outputVolume)
217
218 sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
219 if inputVolume is None:
220 inputVolume = sourceVolume
221
222 self.fillValueEdit.setVisible(operationName in ["FILL_INSIDE", "FILL_OUTSIDE"])
223 self.fillValueLabel.setVisible(operationName in ["FILL_INSIDE", "FILL_OUTSIDE"])
224 self.binaryMaskFillInsideEdit.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
225 self.fillInsideLabel.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
226 self.binaryMaskFillOutsideEdit.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
227 self.fillOutsideLabel.setVisible(operationName == "FILL_INSIDE_AND_OUTSIDE")
228
229 # operationName is temporarily set to empty during scene close. If we change node types during this time then
230 # the node selector breaks, therefore it is important to only update the node selector if operationName is valid.
231 if operationName in ["FILL_INSIDE", "FILL_OUTSIDE"]:
232 if self.outputVolumeSelector.noneDisplay != _("(Create new Volume)"):
233 self.outputVolumeSelector.noneDisplay = _("(Create new Volume)")
234 self.outputVolumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", "vtkMRMLLabelMapVolumeNode"]
235 elif operationName == "FILL_INSIDE_AND_OUTSIDE":
236 if self.outputVolumeSelector.noneDisplay != _("(Create new Labelmap Volume)"):
237 self.outputVolumeSelector.noneDisplay = _("(Create new Labelmap Volume)")
238 self.outputVolumeSelector.nodeTypes = ["vtkMRMLLabelMapVolumeNode", "vtkMRMLScalarVolumeNode"]
239
240 self.inputVisibilityButton.setIcon(self.visibleIcon if self.isVolumeVisible(inputVolume) else self.invisibleIcon)
241
242 self.outputVisibilityButton.setIcon(self.visibleIcon if self.isVolumeVisible(outputVolume) else self.invisibleIcon)
243
244 self.updatingGUIFromMRML = False
245
246 def updateMRMLFromGUI(self):
247 if self.updatingGUIFromMRML:
248 return
249 self.scriptedEffect.setParameter("FillValue", self.fillValueEdit.value)
250 self.scriptedEffect.setParameter("BinaryMaskFillValueInside", self.binaryMaskFillInsideEdit.value)
251 self.scriptedEffect.setParameter("BinaryMaskFillValueOutside", self.binaryMaskFillOutsideEdit.value)
252 self.scriptedEffect.setNodeReference("InputVolume", self.inputVolumeSelector.currentNode())
253 self.scriptedEffect.setNodeReference("OutputVolume", self.outputVolumeSelector.currentNode())
254 self.scriptedEffect.setParameter("SoftEdgeMm", self.softEdgeMmSpinBox.value)
255
256 def activate(self):
257 self.scriptedEffect.setParameter("InputVisibility", "True")
258
259 def deactivate(self):
260 if self.outputVolumeSelector.currentNode() is not self.scriptedEffect.parameterSetNode().GetSourceVolumeNode():
261 self.scriptedEffect.setParameter("OutputVisibility", "False")
262 slicer.util.setSliceViewerLayers(background=self.scriptedEffect.parameterSetNode().GetSourceVolumeNode())
263
264 def onOperationSelectionChanged(self, operationName, toggle):
265 if not toggle:
266 return
267 self.scriptedEffect.setParameter("Operation", operationName)
268
269 def softEdgeMmChanged(self, edgeMm):
270 self.scriptedEffect.setParameter("SoftEdgeMm", edgeMm)
271
272 def getInputVolume(self):
273 inputVolume = self.inputVolumeSelector.currentNode()
274 if inputVolume is None:
275 inputVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
276 return inputVolume
277
278 def onInputVisibilityButtonClicked(self):
279 inputVolume = self.scriptedEffect.nodeReference("InputVolume")
280 sourceVolume = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
281 if inputVolume is None:
282 inputVolume = sourceVolume
283 if inputVolume:
284 slicer.util.setSliceViewerLayers(background=inputVolume)
285 self.updateGUIFromMRML()
286
287 def onOutputVisibilityButtonClicked(self):
288 outputVolume = self.scriptedEffect.nodeReference("OutputVolume")
289 if outputVolume:
290 slicer.util.setSliceViewerLayers(background=outputVolume)
291 self.updateGUIFromMRML()
292
293 def onInputVolumeChanged(self):
294 self.scriptedEffect.setNodeReference("InputVolume", self.inputVolumeSelector.currentNode())
295 self.updateGUIFromMRML() # node reference changes are not observed, update GUI manually
296
297 def onOutputVolumeChanged(self):
298 self.scriptedEffect.setNodeReference("OutputVolume", self.outputVolumeSelector.currentNode())
299 self.updateGUIFromMRML() # node reference changes are not observed, update GUI manually
300
301 def fillValueChanged(self):
302 self.updateMRMLFromGUI()
303
304 def onApply(self):
305 with slicer.util.tryWithErrorDisplay(_("Failed to apply mask to volume."), waitCursor=True):
306 inputVolume = self.getInputVolume()
307 outputVolume = self.outputVolumeSelector.currentNode()
308 operationMode = self.scriptedEffect.parameter("Operation")
309 if not outputVolume:
310 # Create new node for output
311 volumesLogic = slicer.modules.volumes.logic()
312 scene = inputVolume.GetScene()
313 if operationMode == "FILL_INSIDE_AND_OUTSIDE":
314 outputVolumeName = inputVolume.GetName() + " label"
315 outputVolume = volumesLogic.CreateAndAddLabelVolume(inputVolume, outputVolumeName)
316 else:
317 outputVolumeName = inputVolume.GetName() + " masked"
318 outputVolume = volumesLogic.CloneVolumeGeneric(scene, inputVolume, outputVolumeName, False)
319 self.outputVolumeSelector.setCurrentNode(outputVolume)
320
321 if operationMode in ["FILL_INSIDE", "FILL_OUTSIDE"]:
322 fillValues = [self.fillValueEdit.value]
323 else:
324 fillValues = [self.binaryMaskFillInsideEdit.value, self.binaryMaskFillOutsideEdit.value]
325
326 segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
327 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
328
329 softEdgeMm = self.scriptedEffect.doubleParameter("SoftEdgeMm")
330
331 SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolume, outputVolume,
332 softEdgeMm=softEdgeMm)
333
334 slicer.util.setSliceViewerLayers(background=outputVolume)
335
336 self.updateGUIFromMRML()
337
338 @staticmethod
339 def maskVolumeWithSegment(segmentationNode, segmentID, operationMode, fillValues, inputVolumeNode, outputVolumeNode, maskExtent=None, softEdgeMm=0.0):
340 """
341 Fill voxels of the input volume inside/outside the masking model with the provided fill value
342 maskExtent: optional output to return computed mask extent (expected input is a 6-element list)
343 fillValues: list containing one or two fill values. If fill mode is inside or outside then only one value is specified in the list.
344 If fill mode is inside&outside then the list must contain two values: first is the inside fill, second is the outside fill value.
345 """
346
347 import vtk # without this we get the error: UnboundLocalError: local variable 'vtk' referenced before assignment
348
349 segmentIDs = vtk.vtkStringArray()
350 segmentIDs.InsertNextValue(segmentID)
351 maskVolumeNode = slicer.modules.volumes.logic().CreateAndAddLabelVolume(inputVolumeNode, "TemporaryVolumeMask")
352 if not maskVolumeNode:
353 logging.error("maskVolumeWithSegment failed: invalid maskVolumeNode")
354 return False
355
356 if not slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIDs, maskVolumeNode, inputVolumeNode):
357 logging.error("maskVolumeWithSegment failed: ExportSegmentsToLabelmapNode error")
358 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
359 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
360 slicer.mrmlScene.RemoveNode(maskVolumeNode)
361 return False
362
363 if maskExtent:
364 img = slicer.modules.segmentations.logic().CreateOrientedImageDataFromVolumeNode(maskVolumeNode)
365 img.UnRegister(None)
366
367 import vtkSegmentationCorePython as vtkSegmentationCore
368
369 vtkSegmentationCore.vtkOrientedImageDataResample.CalculateEffectiveExtent(img, maskExtent, 0)
370
371 if softEdgeMm == 0:
372 # Hard edge
373 maskToStencil = vtk.vtkImageToImageStencil()
374 maskToStencil.ThresholdByLower(0)
375 maskToStencil.SetInputData(maskVolumeNode.GetImageData())
376
377 stencil = vtk.vtkImageStencil()
378
379 if operationMode == "FILL_INSIDE_AND_OUTSIDE":
380 # Set input to constant value
381 thresh = vtk.vtkImageThreshold()
382 thresh.SetInputData(inputVolumeNode.GetImageData())
383 thresh.ThresholdByLower(0)
384 thresh.SetInValue(fillValues[1])
385 thresh.SetOutValue(fillValues[1])
386 thresh.SetOutputScalarType(inputVolumeNode.GetImageData().GetScalarType())
387 thresh.Update()
388 stencil.SetInputData(thresh.GetOutput())
389 else:
390 stencil.SetInputData(inputVolumeNode.GetImageData())
391
392 stencil.SetStencilConnection(maskToStencil.GetOutputPort())
393 stencil.SetReverseStencil(operationMode == "FILL_OUTSIDE")
394 stencil.SetBackgroundValue(fillValues[0])
395 stencil.Update()
396 outputVolumeNode.SetAndObserveImageData(stencil.GetOutput())
397 else:
398 # Soft edge
399
400 thresh = vtk.vtkImageThreshold()
401 maskMin = 0
402 maskMax = 255
403 thresh.SetOutputScalarTypeToUnsignedChar()
404 thresh.SetInputData(maskVolumeNode.GetImageData())
405 thresh.ThresholdByLower(0)
406 thresh.SetInValue(maskMin)
407 thresh.SetOutValue(maskMax)
408 thresh.Update()
409
410 gaussianFilter = vtk.vtkImageGaussianSmooth()
411 spacing = maskVolumeNode.GetSpacing()
412 standardDeviationPixel = [1.0, 1.0, 1.0]
413 for idx in range(3):
414 standardDeviationPixel[idx] = softEdgeMm / spacing[idx]
415 gaussianFilter.SetInputConnection(thresh.GetOutputPort())
416 gaussianFilter.SetStandardDeviations(*standardDeviationPixel)
417 # Do not truncate the Gaussian kernel at the default 1.5 sigma,
418 # because it would result in edge artifacts.
419 # Larger value results in less edge artifact but increased computation time,
420 # so 3.0 is a good tradeoff.
421 gaussianFilter.SetRadiusFactor(3.0)
422 gaussianFilter.Update()
423
424 import vtk.util.numpy_support
425
426 maskImage = gaussianFilter.GetOutput()
427 nshape = tuple(reversed(maskImage.GetDimensions()))
428 maskArray = vtk.util.numpy_support.vtk_to_numpy(maskImage.GetPointData().GetScalars()).reshape(nshape)
429 # Normalize mask with the actual min/max values.
430 # Gaussian output is not always exactly the original minimum and maximum, so we get the actual min/max values.
431 maskMin = maskArray.min()
432 maskMax = maskArray.max()
433 mask = (maskArray.astype(float) - maskMin) / float(maskMax - maskMin)
434
435 inputArray = slicer.util.arrayFromVolume(inputVolumeNode)
436
437 if operationMode == "FILL_INSIDE_AND_OUTSIDE":
438 # Rescale the smoothed mask
439 resultArray = fillValues[0] + (fillValues[1] - fillValues[0]) * mask[:]
440 else:
441 # Compute weighted average between blanked out and input volume
442 if operationMode == "FILL_INSIDE":
443 mask = 1.0 - mask
444 resultArray = inputArray[:] * mask[:] + float(fillValues[0]) * (1.0 - mask[:])
445
446 slicer.util.updateVolumeFromArray(outputVolumeNode, resultArray.astype(inputArray.dtype))
447
448 # Set the same geometry and parent transform as the input volume
449 ijkToRas = vtk.vtkMatrix4x4()
450 inputVolumeNode.GetIJKToRASMatrix(ijkToRas)
451 outputVolumeNode.SetIJKToRASMatrix(ijkToRas)
452 inputVolumeNode.SetAndObserveTransformNodeID(inputVolumeNode.GetTransformNodeID())
453
454 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode().GetColorNode())
455 slicer.mrmlScene.RemoveNode(maskVolumeNode.GetDisplayNode())
456 slicer.mrmlScene.RemoveNode(maskVolumeNode)
457 return True