Slicer  4.13
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorSmoothingEffect.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  """ SmoothingEffect is an Effect that smoothes a selected segment
8  """
9 
10  def __init__(self, scriptedEffect):
11  scriptedEffect.name = 'Smoothing'
12  AbstractScriptedSegmentEditorPaintEffect.__init__(self, scriptedEffect)
13 
14  def clone(self):
15  import qSlicerSegmentationsEditorEffectsPythonQt as effects
16  clonedEffect = effects.qSlicerSegmentEditorScriptedPaintEffect(None)
17  clonedEffect.setPythonSource(__file__.replace('\\','/'))
18  return clonedEffect
19 
20  def icon(self):
21  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Smoothing.png')
22  if os.path.exists(iconPath):
23  return qt.QIcon(iconPath)
24  return qt.QIcon()
25 
26  def helpText(self):
27  return """<html>Make segment boundaries smoother<br> by removing extrusions and filling small holes. The effect can be either applied locally
28 (by painting in viewers) or to the whole segment (by clicking Apply button). Available methods:<p>
29 <ul style="margin: 0">
30 <li><b>Median:</b> removes small details while keeps smooth contours mostly unchanged. Applied to selected segment only.</li>
31 <li><b>Opening:</b> removes extrusions smaller than the specified kernel size. Applied to selected segment only.</li>
32 <li><b>Closing:</b> fills sharp corners and holes smaller than the specified kernel size. Applied to selected segment only.</li>
33 <li><b>Gaussian:</b> smoothes all contours, tends to shrink the segment. Applied to selected segment only.</li>
34 <li><b>Joint smoothing:</b> smoothes multiple segments at once, preserving watertight interface between them. Masking settings are bypassed.
35 If segments overlap, segment higher in the segments table will have priority. <b>Applied to all visible segments.</b></li>
36 </ul><p></html>"""
37 
38  def setupOptionsFrame(self):
39 
40  self.methodSelectorComboBox = qt.QComboBox()
41  self.methodSelectorComboBox.addItem("Median", MEDIAN)
42  self.methodSelectorComboBox.addItem("Opening (remove extrusions)", MORPHOLOGICAL_OPENING)
43  self.methodSelectorComboBox.addItem("Closing (fill holes)", MORPHOLOGICAL_CLOSING)
44  self.methodSelectorComboBox.addItem("Gaussian", GAUSSIAN)
45  self.methodSelectorComboBox.addItem("Joint smoothing", JOINT_TAUBIN)
46  self.scriptedEffect.addLabeledOptionsWidget("Smoothing method:", self.methodSelectorComboBox)
47 
48  self.kernelSizeMMSpinBox = slicer.qMRMLSpinBox()
49  self.kernelSizeMMSpinBox.setMRMLScene(slicer.mrmlScene)
50  self.kernelSizeMMSpinBox.setToolTip("Diameter of the neighborhood that will be considered around each voxel. Higher value makes smoothing stronger (more details are suppressed).")
51  self.kernelSizeMMSpinBox.quantity = "length"
52  self.kernelSizeMMSpinBox.minimum = 0.0
53  self.kernelSizeMMSpinBox.value = 3.0
54  self.kernelSizeMMSpinBox.singleStep = 1.0
55 
56  self.kernelSizePixel = qt.QLabel()
57  self.kernelSizePixel.setToolTip("Diameter of the neighborhood in pixel. Computed from the segment's spacing and the specified kernel size.")
58 
59  kernelSizeFrame = qt.QHBoxLayout()
60  kernelSizeFrame.addWidget(self.kernelSizeMMSpinBox)
61  kernelSizeFrame.addWidget(self.kernelSizePixel)
62  self.kernelSizeMMLabel = self.scriptedEffect.addLabeledOptionsWidget("Kernel size:", kernelSizeFrame)
63 
64  self.gaussianStandardDeviationMMSpinBox = slicer.qMRMLSpinBox()
65  self.gaussianStandardDeviationMMSpinBox.setMRMLScene(slicer.mrmlScene)
66  self.gaussianStandardDeviationMMSpinBox.setToolTip("Standard deviation of the Gaussian smoothing filter coefficients. Higher value makes smoothing stronger (more details are suppressed).")
67  self.gaussianStandardDeviationMMSpinBox.quantity = "length"
68  self.gaussianStandardDeviationMMSpinBox.value = 3.0
69  self.gaussianStandardDeviationMMSpinBox.singleStep = 1.0
70  self.gaussianStandardDeviationMMLabel = self.scriptedEffect.addLabeledOptionsWidget("Standard deviation:", self.gaussianStandardDeviationMMSpinBox)
71 
72  self.jointTaubinSmoothingFactorSlider = ctk.ctkSliderWidget()
73  self.jointTaubinSmoothingFactorSlider.setToolTip("Higher value means stronger smoothing.")
74  self.jointTaubinSmoothingFactorSlider.minimum = 0.01
75  self.jointTaubinSmoothingFactorSlider.maximum = 1.0
76  self.jointTaubinSmoothingFactorSlider.value = 0.5
77  self.jointTaubinSmoothingFactorSlider.singleStep = 0.01
78  self.jointTaubinSmoothingFactorSlider.pageStep = 0.1
79  self.jointTaubinSmoothingFactorLabel = self.scriptedEffect.addLabeledOptionsWidget("Smoothing factor:", self.jointTaubinSmoothingFactorSlider)
80 
81  self.applyButton = qt.QPushButton("Apply")
82  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
83  self.applyButton.setToolTip("Apply smoothing to selected segment")
84  self.scriptedEffect.addOptionsWidget(self.applyButton)
85 
86  self.methodSelectorComboBox.connect("currentIndexChanged(int)", self.updateMRMLFromGUI)
87  self.kernelSizeMMSpinBox.connect("valueChanged(double)", self.updateMRMLFromGUI)
88  self.gaussianStandardDeviationMMSpinBox.connect("valueChanged(double)", self.updateMRMLFromGUI)
89  self.jointTaubinSmoothingFactorSlider.connect("valueChanged(double)", self.updateMRMLFromGUI)
90  self.applyButton.connect('clicked()', self.onApply)
91 
92  # Customize smoothing brush
93  self.scriptedEffect.setColorSmudgeCheckboxVisible(False)
94  self.paintOptionsGroupBox = ctk.ctkCollapsibleGroupBox()
95  self.paintOptionsGroupBox.setTitle("Smoothing brush options")
96  self.paintOptionsGroupBox.setLayout(qt.QVBoxLayout())
97  self.paintOptionsGroupBox.layout().addWidget(self.scriptedEffect.paintOptionsFrame())
98  self.paintOptionsGroupBox.collapsed = True
99  self.scriptedEffect.addOptionsWidget(self.paintOptionsGroupBox)
100 
101  def setMRMLDefaults(self):
102  self.scriptedEffect.setParameterDefault("SmoothingMethod", MEDIAN)
103  self.scriptedEffect.setParameterDefault("KernelSizeMm", 3)
104  self.scriptedEffect.setParameterDefault("GaussianStandardDeviationMm", 3)
105  self.scriptedEffect.setParameterDefault("JointTaubinSmoothingFactor", 0.5)
106 
108  methodIndex = self.methodSelectorComboBox.currentIndex
109  smoothingMethod = self.methodSelectorComboBox.itemData(methodIndex)
110  morphologicalMethod = (smoothingMethod==MEDIAN or smoothingMethod==MORPHOLOGICAL_OPENING or smoothingMethod==MORPHOLOGICAL_CLOSING)
111  self.kernelSizeMMLabel.setVisible(morphologicalMethod)
112  self.kernelSizeMMSpinBox.setVisible(morphologicalMethod)
113  self.kernelSizePixel.setVisible(morphologicalMethod)
114  self.gaussianStandardDeviationMMLabel.setVisible(smoothingMethod==GAUSSIAN)
115  self.gaussianStandardDeviationMMSpinBox.setVisible(smoothingMethod==GAUSSIAN)
116  self.jointTaubinSmoothingFactorLabel.setVisible(smoothingMethod==JOINT_TAUBIN)
117  self.jointTaubinSmoothingFactorSlider.setVisible(smoothingMethod==JOINT_TAUBIN)
118 
120  selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
121  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
122  if selectedSegmentLabelmap:
123  selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
124 
125  # size rounded to nearest odd number. If kernel size is even then image gets shifted.
126  kernelSizeMM = self.scriptedEffect.doubleParameter("KernelSizeMm")
127  kernelSizePixel = [int(round((kernelSizeMM / selectedSegmentLabelmapSpacing[componentIndex]+1)/2)*2-1) for componentIndex in range(3)]
128  return kernelSizePixel
129 
130  def updateGUIFromMRML(self):
131  methodIndex = self.methodSelectorComboBox.findData(self.scriptedEffect.parameter("SmoothingMethod"))
132  wasBlocked = self.methodSelectorComboBox.blockSignals(True)
133  self.methodSelectorComboBox.setCurrentIndex(methodIndex)
134  self.methodSelectorComboBox.blockSignals(wasBlocked)
135 
136  wasBlocked = self.kernelSizeMMSpinBox.blockSignals(True)
137  self.setWidgetMinMaxStepFromImageSpacing(self.kernelSizeMMSpinBox, self.scriptedEffect.selectedSegmentLabelmap())
138  self.kernelSizeMMSpinBox.value = self.scriptedEffect.doubleParameter("KernelSizeMm")
139  self.kernelSizeMMSpinBox.blockSignals(wasBlocked)
140  kernelSizePixel = self.getKernelSizePixel()
141  self.kernelSizePixel.text = "{0}x{1}x{2} pixel".format(kernelSizePixel[0], kernelSizePixel[1], kernelSizePixel[2])
142 
143  wasBlocked = self.gaussianStandardDeviationMMSpinBox.blockSignals(True)
144  self.setWidgetMinMaxStepFromImageSpacing(self.gaussianStandardDeviationMMSpinBox, self.scriptedEffect.selectedSegmentLabelmap())
145  self.gaussianStandardDeviationMMSpinBox.value = self.scriptedEffect.doubleParameter("GaussianStandardDeviationMm")
146  self.gaussianStandardDeviationMMSpinBox.blockSignals(wasBlocked)
147 
148  wasBlocked = self.jointTaubinSmoothingFactorSlider.blockSignals(True)
149  self.jointTaubinSmoothingFactorSlider.value = self.scriptedEffect.doubleParameter("JointTaubinSmoothingFactor")
150  self.jointTaubinSmoothingFactorSlider.blockSignals(wasBlocked)
151 
153 
154  def updateMRMLFromGUI(self):
155  methodIndex = self.methodSelectorComboBox.currentIndex
156  smoothingMethod = self.methodSelectorComboBox.itemData(methodIndex)
157  self.scriptedEffect.setParameter("SmoothingMethod", smoothingMethod)
158  self.scriptedEffect.setParameter("KernelSizeMm", self.kernelSizeMMSpinBox.value)
159  self.scriptedEffect.setParameter("GaussianStandardDeviationMm", self.gaussianStandardDeviationMMSpinBox.value)
160  self.scriptedEffect.setParameter("JointTaubinSmoothingFactor", self.jointTaubinSmoothingFactorSlider.value)
161 
163 
164  #
165  # Effect specific methods (the above ones are the API methods to override)
166  #
167 
168  def onApply(self, maskImage=None, maskExtent=None):
169  """maskImage: contains nonzero where smoothing will be applied
170  """
171  smoothingMethod = self.scriptedEffect.parameter("SmoothingMethod")
172  if smoothingMethod != JOINT_TAUBIN:
173  # Make sure the user wants to do the operation, even if the segment is not visible
174  if not self.scriptedEffect.confirmCurrentSegmentVisible():
175  return
176 
177  try:
178  # This can be a long operation - indicate it to the user
179  qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
180  self.scriptedEffect.saveStateForUndo()
181  if smoothingMethod == JOINT_TAUBIN:
182  self.smoothMultipleSegments(maskImage, maskExtent)
183  else:
184  self.smoothSelectedSegment(maskImage, maskExtent)
185  finally:
186  qt.QApplication.restoreOverrideCursor()
187 
188  def clipImage(self, inputImage, maskExtent, margin):
189  clipper = vtk.vtkImageClip()
190  clipper.SetOutputWholeExtent(maskExtent[0] - margin[0], maskExtent[1] + margin[0],
191  maskExtent[2] - margin[1], maskExtent[3] + margin[1],
192  maskExtent[4] - margin[2], maskExtent[5] + margin[2])
193  clipper.SetInputData(inputImage)
194  clipper.SetClipData(True)
195  clipper.Update()
196  clippedImage = slicer.vtkOrientedImageData()
197  clippedImage.ShallowCopy(clipper.GetOutput())
198  clippedImage.CopyDirections(inputImage)
199  return clippedImage
200 
201  def modifySelectedSegmentByLabelmap(self, smoothedImage, selectedSegmentLabelmap, modifierLabelmap, maskImage, maskExtent):
202  if maskImage:
203  smoothedClippedSelectedSegmentLabelmap = slicer.vtkOrientedImageData()
204  smoothedClippedSelectedSegmentLabelmap.ShallowCopy(smoothedImage)
205  smoothedClippedSelectedSegmentLabelmap.CopyDirections(modifierLabelmap)
206 
207  # fill smoothed selected segment outside the painted region to 1 so that in the end the image is not modified by OPERATION_MINIMUM
208  fillValue = 1.0
209  slicer.vtkOrientedImageDataResample.ApplyImageMask(smoothedClippedSelectedSegmentLabelmap, maskImage, fillValue, False)
210  # set original segment labelmap outside painted region, solid 1 inside painted region
211  slicer.vtkOrientedImageDataResample.ModifyImage(maskImage, selectedSegmentLabelmap,
212  slicer.vtkOrientedImageDataResample.OPERATION_MAXIMUM)
213  slicer.vtkOrientedImageDataResample.ModifyImage(maskImage, smoothedClippedSelectedSegmentLabelmap,
214  slicer.vtkOrientedImageDataResample.OPERATION_MINIMUM)
215 
216  updateExtent = [0, -1, 0, -1, 0, -1]
217  modifierExtent = modifierLabelmap.GetExtent()
218  for i in range(3):
219  updateExtent[2 * i] = min(maskExtent[2 * i], modifierExtent[2 * i])
220  updateExtent[2 * i + 1] = max(maskExtent[2 * i + 1], modifierExtent[2 * i + 1])
221 
222  self.scriptedEffect.modifySelectedSegmentByLabelmap(maskImage,
223  slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet,
224  updateExtent)
225  else:
226  modifierLabelmap.DeepCopy(smoothedImage)
227  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
228 
229  def smoothSelectedSegment(self, maskImage=None, maskExtent=None):
230  try:
231 
232  # Get master volume image data
233  import vtkSegmentationCorePython
234 
235  # Get modifier labelmap
236  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
237  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
238 
239  smoothingMethod = self.scriptedEffect.parameter("SmoothingMethod")
240 
241  if smoothingMethod == GAUSSIAN:
242  maxValue = 255
243  radiusFactor = 4.0
244  standardDeviationMM = self.scriptedEffect.doubleParameter("GaussianStandardDeviationMm")
245  spacing = modifierLabelmap.GetSpacing()
246  standardDeviationPixel = [1.0, 1.0, 1.0]
247  radiusPixel = [3, 3, 3]
248  for idx in range(3):
249  standardDeviationPixel[idx] = standardDeviationMM / spacing[idx]
250  radiusPixel[idx] = int(standardDeviationPixel[idx] * radiusFactor) + 1
251  if maskExtent:
252  clippedSelectedSegmentLabelmap = self.clipImage(selectedSegmentLabelmap, maskExtent, radiusPixel)
253  else:
254  clippedSelectedSegmentLabelmap = selectedSegmentLabelmap
255 
256  thresh = vtk.vtkImageThreshold()
257  thresh.SetInputData(clippedSelectedSegmentLabelmap)
258  thresh.ThresholdByLower(0)
259  thresh.SetInValue(0)
260  thresh.SetOutValue(maxValue)
261  thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR)
262 
263  gaussianFilter = vtk.vtkImageGaussianSmooth()
264  gaussianFilter.SetInputConnection(thresh.GetOutputPort())
265  gaussianFilter.SetStandardDeviation(*standardDeviationPixel)
266  gaussianFilter.SetRadiusFactor(radiusFactor)
267 
268  thresh2 = vtk.vtkImageThreshold()
269  thresh2.SetInputConnection(gaussianFilter.GetOutputPort())
270  thresh2.ThresholdByUpper(int(maxValue / 2))
271  thresh2.SetInValue(1)
272  thresh2.SetOutValue(0)
273  thresh2.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
274  thresh2.Update()
275 
276  self.modifySelectedSegmentByLabelmap(thresh2.GetOutput(), selectedSegmentLabelmap, modifierLabelmap, maskImage, maskExtent)
277 
278  else:
279  # size rounded to nearest odd number. If kernel size is even then image gets shifted.
280  kernelSizePixel = self.getKernelSizePixel()
281 
282  if maskExtent:
283  clippedSelectedSegmentLabelmap = self.clipImage(selectedSegmentLabelmap, maskExtent, kernelSizePixel)
284  else:
285  clippedSelectedSegmentLabelmap = selectedSegmentLabelmap
286 
287  if smoothingMethod == MEDIAN:
288  # Median filter does not require a particular label value
289  smoothingFilter = vtk.vtkImageMedian3D()
290  smoothingFilter.SetInputData(clippedSelectedSegmentLabelmap)
291 
292  else:
293  # We need to know exactly the value of the segment voxels, apply threshold to make force the selected label value
294  labelValue = 1
295  backgroundValue = 0
296  thresh = vtk.vtkImageThreshold()
297  thresh.SetInputData(clippedSelectedSegmentLabelmap)
298  thresh.ThresholdByLower(0)
299  thresh.SetInValue(backgroundValue)
300  thresh.SetOutValue(labelValue)
301  thresh.SetOutputScalarType(clippedSelectedSegmentLabelmap.GetScalarType())
302 
303  smoothingFilter = vtk.vtkImageOpenClose3D()
304  smoothingFilter.SetInputConnection(thresh.GetOutputPort())
305  if smoothingMethod == MORPHOLOGICAL_OPENING:
306  smoothingFilter.SetOpenValue(labelValue)
307  smoothingFilter.SetCloseValue(backgroundValue)
308  else: # must be smoothingMethod == MORPHOLOGICAL_CLOSING:
309  smoothingFilter.SetOpenValue(backgroundValue)
310  smoothingFilter.SetCloseValue(labelValue)
311 
312  smoothingFilter.SetKernelSize(kernelSizePixel[0],kernelSizePixel[1],kernelSizePixel[2])
313  smoothingFilter.Update()
314 
315  self.modifySelectedSegmentByLabelmap(smoothingFilter.GetOutput(), selectedSegmentLabelmap, modifierLabelmap, maskImage, maskExtent)
316 
317  except IndexError:
318  logging.error('apply: Failed to apply smoothing')
319 
320  def smoothMultipleSegments(self, maskImage=None, maskExtent=None):
321  import vtkSegmentationCorePython as vtkSegmentationCore
322 
323  # Generate merged labelmap of all visible segments
324  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
325  visibleSegmentIds = vtk.vtkStringArray()
326  segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(visibleSegmentIds)
327  if visibleSegmentIds.GetNumberOfValues() == 0:
328  logging.info("Smoothing operation skipped: there are no visible segments")
329  return
330 
331  mergedImage = slicer.vtkOrientedImageData()
332  if not segmentationNode.GenerateMergedLabelmapForAllSegments(mergedImage,
333  vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_SEGMENTS_PADDED,
334  None, visibleSegmentIds):
335  logging.error('Failed to apply smoothing: cannot get list of visible segments')
336  return
337 
338  segmentLabelValues = [] # list of [segmentId, labelValue]
339  for i in range(visibleSegmentIds.GetNumberOfValues()):
340  segmentId = visibleSegmentIds.GetValue(i)
341  segmentLabelValues.append([segmentId, i+1])
342 
343  # Perform smoothing in voxel space
344  ici = vtk.vtkImageChangeInformation()
345  ici.SetInputData(mergedImage)
346  ici.SetOutputSpacing(1, 1, 1)
347  ici.SetOutputOrigin(0, 0, 0)
348 
349  # Convert labelmap to combined polydata
350  # vtkDiscreteFlyingEdges3D cannot be used here, as in the output of that filter,
351  # each labeled region is completely disconnected from neighboring regions, and
352  # for joint smoothing it is essential for the points to move together.
353  convertToPolyData = vtk.vtkDiscreteMarchingCubes()
354  convertToPolyData.SetInputConnection(ici.GetOutputPort())
355  convertToPolyData.SetNumberOfContours(len(segmentLabelValues))
356 
357  contourIndex = 0
358  for segmentId, labelValue in segmentLabelValues:
359  convertToPolyData.SetValue(contourIndex, labelValue)
360  contourIndex += 1
361 
362  # Low-pass filtering using Taubin's method
363  smoothingFactor = self.scriptedEffect.doubleParameter("JointTaubinSmoothingFactor")
364  smoothingIterations = 100 # according to VTK documentation 10-20 iterations could be enough but we use a higher value to reduce chance of shrinking
365  passBand = pow(10.0, -4.0*smoothingFactor) # gives a nice range of 1-0.0001 from a user input of 0-1
366  smoother = vtk.vtkWindowedSincPolyDataFilter()
367  smoother.SetInputConnection(convertToPolyData.GetOutputPort())
368  smoother.SetNumberOfIterations(smoothingIterations)
369  smoother.BoundarySmoothingOff()
370  smoother.FeatureEdgeSmoothingOff()
371  smoother.SetFeatureAngle(90.0)
372  smoother.SetPassBand(passBand)
373  smoother.NonManifoldSmoothingOn()
374  smoother.NormalizeCoordinatesOn()
375 
376  # Extract a label
377  threshold = vtk.vtkThreshold()
378  threshold.SetInputConnection(smoother.GetOutputPort())
379 
380  # Convert to polydata
381  geometryFilter = vtk.vtkGeometryFilter()
382  geometryFilter.SetInputConnection(threshold.GetOutputPort())
383 
384  # Convert polydata to stencil
385  polyDataToImageStencil = vtk.vtkPolyDataToImageStencil()
386  polyDataToImageStencil.SetInputConnection(geometryFilter.GetOutputPort())
387  polyDataToImageStencil.SetOutputSpacing(1,1,1)
388  polyDataToImageStencil.SetOutputOrigin(0,0,0)
389  polyDataToImageStencil.SetOutputWholeExtent(mergedImage.GetExtent())
390 
391  # Convert stencil to image
392  stencil = vtk.vtkImageStencil()
393  emptyBinaryLabelMap = vtk.vtkImageData()
394  emptyBinaryLabelMap.SetExtent(mergedImage.GetExtent())
395  emptyBinaryLabelMap.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
396  vtkSegmentationCore.vtkOrientedImageDataResample.FillImage(emptyBinaryLabelMap, 0)
397  stencil.SetInputData(emptyBinaryLabelMap)
398  stencil.SetStencilConnection(polyDataToImageStencil.GetOutputPort())
399  stencil.ReverseStencilOn()
400  stencil.SetBackgroundValue(1) # General foreground value is 1 (background value because of reverse stencil)
401 
402  imageToWorldMatrix = vtk.vtkMatrix4x4()
403  mergedImage.GetImageToWorldMatrix(imageToWorldMatrix)
404 
405  # TODO: Temporarily setting the overwite mode to OverwriteVisibleSegments is an approach that should be change once additional
406  # layer control options have been implemented. Users may wish to keep segments on separate layers, and not allow them to be separated/merged automatically.
407  # This effect could leverage those options once they have been implemented.
408  oldOverwriteMode = self.scriptedEffect.parameterSetNode().GetOverwriteMode()
409  self.scriptedEffect.parameterSetNode().SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteVisibleSegments)
410  for segmentId, labelValue in segmentLabelValues:
411  threshold.ThresholdBetween(labelValue, labelValue)
412  stencil.Update()
413  smoothedBinaryLabelMap = slicer.vtkOrientedImageData()
414  smoothedBinaryLabelMap.ShallowCopy(stencil.GetOutput())
415  smoothedBinaryLabelMap.SetImageToWorldMatrix(imageToWorldMatrix)
416  self.scriptedEffect.modifySegmentByLabelmap(segmentationNode, segmentId, smoothedBinaryLabelMap,
417  slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, False)
418  self.scriptedEffect.parameterSetNode().SetOverwriteMode(oldOverwriteMode)
419 
420  def paintApply(self, viewWidget):
421 
422  # Current limitation: smoothing brush is not implemented for joint smoothing
423  smoothingMethod = self.scriptedEffect.parameter("SmoothingMethod")
424  if smoothingMethod == JOINT_TAUBIN:
425  self.scriptedEffect.clearBrushes()
426  self.scriptedEffect.forceRender(viewWidget)
427  slicer.util.messageBox("Smoothing brush is not available for 'joint smoothing' method.")
428  return
429 
430  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
431  maskImage = slicer.vtkOrientedImageData()
432  maskImage.DeepCopy(modifierLabelmap)
433  maskExtent = self.scriptedEffect.paintBrushesIntoLabelmap(maskImage, viewWidget)
434  self.scriptedEffect.clearBrushes()
435  self.scriptedEffect.forceRender(viewWidget)
436  if maskExtent[0]>maskExtent[1] or maskExtent[2]>maskExtent[3] or maskExtent[4]>maskExtent[5]:
437  return
438 
439  self.scriptedEffect.saveStateForUndo()
440  self.onApply(maskImage, maskExtent)
441 
442 MEDIAN = 'MEDIAN'
443 GAUSSIAN = 'GAUSSIAN'
444 MORPHOLOGICAL_OPENING = 'MORPHOLOGICAL_OPENING'
445 MORPHOLOGICAL_CLOSING = 'MORPHOLOGICAL_CLOSING'
446 JOINT_TAUBIN = 'JOINT_TAUBIN'
def modifySelectedSegmentByLabelmap(self, smoothedImage, selectedSegmentLabelmap, modifierLabelmap, maskImage, maskExtent)
def smoothSelectedSegment(self, maskImage=None, maskExtent=None)
def smoothMultipleSegments(self, maskImage=None, maskExtent=None)