2 import vtk, qt, ctk, slicer
4 from SegmentEditorEffects
import *
7 """ SmoothingEffect is an Effect that smoothes a selected segment 11 scriptedEffect.name =
'Smoothing' 12 AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
15 import qSlicerSegmentationsEditorEffectsPythonQt
as effects
16 clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(
None)
17 clonedEffect.setPythonSource(__file__.replace(
'\\',
'/'))
21 iconPath = os.path.join(os.path.dirname(__file__),
'Resources/Icons/Smoothing.png')
22 if os.path.exists(iconPath):
23 return qt.QIcon(iconPath)
27 return """<html>Make segment boundaries smoother<br> by removing extrusions and filling small holes. Available methods:<p> 28 <ul style="margin: 0"> 29 <li><b>Median:</b> removes small details while keeps smooth contours mostly unchanged. Applied to selected segment only.</li> 30 <li><b>Opening:</b> removes extrusions smaller than the specified kernel size. Applied to selected segment only.</li> 31 <li><b>Closing:</b> fills sharp corners and holes smaller than the specified kernel size. Applied to selected segment only.</li> 32 <li><b>Gaussian:</b> smoothes all contours, tends to shrink the segment. Applied to selected segment only.</li> 33 <li><b>Joint smoothing:</b> smoothes multiple segments at once, preserving watertight interface between them. Masking settings are bypassed. 34 If segments overlap, segment higher in the segments table will have priority. <b>Applied to all visible segments.</b></li> 49 self.
kernelSizeMmSpinBox.setToolTip(
"Diameter of the neighborhood that will be considered around each voxel. Higher value makes smoothing stronger (more details are suppressed).")
57 self.
kernelSizePixel.setToolTip(
"Diameter of the neighborhood in pixels. Computed from the segment's spacing and the specified kernel size.")
59 kernelSizeFrame = qt.QHBoxLayout()
62 self.
kernelSizeMmLabel = self.scriptedEffect.addLabeledOptionsWidget(
"Kernel size:", kernelSizeFrame)
66 self.
gaussianStandardDeviationMmSpinBox.setToolTip(
"Standard deviation of the Gaussian smoothing filter coefficients. Higher value makes smoothing stronger (more details are suppressed).")
82 self.
applyButton.objectName = self.__class__.__name__ +
'Apply' 83 self.
applyButton.setToolTip(
"Apply smoothing to selected segment")
84 self.scriptedEffect.addOptionsWidget(self.
applyButton)
94 return slicer.util.mainWindow().cursor
97 self.scriptedEffect.setParameterDefault(
"SmoothingMethod", MEDIAN)
98 self.scriptedEffect.setParameterDefault(
"KernelSizeMm", 3)
99 self.scriptedEffect.setParameterDefault(
"GaussianStandardDeviationMm", 3)
100 self.scriptedEffect.setParameterDefault(
"JointTaubinSmoothingFactor", 0.5)
105 morphologicalMethod = (smoothingMethod==MEDIAN
or smoothingMethod==MORPHOLOGICAL_OPENING
or smoothingMethod==MORPHOLOGICAL_CLOSING)
115 selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
116 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
117 if selectedSegmentLabelmap:
118 selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
121 kernelSizeMm = self.scriptedEffect.doubleParameter(
"KernelSizeMm")
122 kernelSizePixel = [int(round((kernelSizeMm / selectedSegmentLabelmapSpacing[componentIndex]+1)/2)*2-1)
for componentIndex
in range(3)]
123 return kernelSizePixel
135 self.
kernelSizePixel.text =
"{0}x{1}x{2} pixels".format(kernelSizePixel[0], kernelSizePixel[1], kernelSizePixel[2])
147 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
148 if selectedSegmentLabelmap:
150 selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
151 singleStep = min(selectedSegmentLabelmapSpacing)
153 singleStep = pow(10,math.floor(math.log(singleStep)/math.log(10)))
160 self.scriptedEffect.setParameter(
"SmoothingMethod", smoothingMethod)
174 qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
176 self.scriptedEffect.saveStateForUndo()
178 smoothingMethod = self.scriptedEffect.parameter(
"SmoothingMethod")
179 if smoothingMethod == JOINT_TAUBIN:
184 qt.QApplication.restoreOverrideCursor()
190 import vtkSegmentationCorePython
193 modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
194 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
196 smoothingMethod = self.scriptedEffect.parameter(
"SmoothingMethod")
198 if smoothingMethod == GAUSSIAN:
201 thresh = vtk.vtkImageThreshold()
202 thresh.SetInputData(selectedSegmentLabelmap)
203 thresh.ThresholdByLower(0)
205 thresh.SetOutValue(maxValue)
206 thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR)
208 standardDeviationMm = self.scriptedEffect.doubleParameter(
"GaussianStandardDeviationMm")
209 gaussianFilter = vtk.vtkImageGaussianSmooth()
210 gaussianFilter.SetInputConnection(thresh.GetOutputPort())
211 gaussianFilter.SetStandardDeviation(standardDeviationMm)
212 gaussianFilter.SetRadiusFactor(4)
214 thresh2 = vtk.vtkImageThreshold()
215 thresh2.SetInputConnection(gaussianFilter.GetOutputPort())
216 thresh2.ThresholdByUpper(maxValue/2)
217 thresh2.SetInValue(1)
218 thresh2.SetOutValue(0)
219 thresh2.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
221 modifierLabelmap.DeepCopy(thresh2.GetOutput())
227 if smoothingMethod == MEDIAN:
229 smoothingFilter = vtk.vtkImageMedian3D()
230 smoothingFilter.SetInputData(selectedSegmentLabelmap)
236 thresh = vtk.vtkImageThreshold()
237 thresh.SetInputData(selectedSegmentLabelmap)
238 thresh.ThresholdByLower(0)
239 thresh.SetInValue(backgroundValue)
240 thresh.SetOutValue(labelValue)
241 thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
243 smoothingFilter = vtk.vtkImageOpenClose3D()
244 smoothingFilter.SetInputConnection(thresh.GetOutputPort())
245 if smoothingMethod == MORPHOLOGICAL_OPENING:
246 smoothingFilter.SetOpenValue(labelValue)
247 smoothingFilter.SetCloseValue(backgroundValue)
249 smoothingFilter.SetOpenValue(backgroundValue)
250 smoothingFilter.SetCloseValue(labelValue)
252 smoothingFilter.SetKernelSize(kernelSizePixel[0],kernelSizePixel[1],kernelSizePixel[2])
253 smoothingFilter.Update()
254 modifierLabelmap.DeepCopy(smoothingFilter.GetOutput())
257 logging.error(
'apply: Failed to apply smoothing')
260 self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
263 import vtkSegmentationCorePython
as vtkSegmentationCore
266 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
267 visibleSegmentIds = vtk.vtkStringArray()
268 segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(visibleSegmentIds)
269 if visibleSegmentIds.GetNumberOfValues() == 0:
270 logging.info(
"Smoothing operation skipped: there are no visible segments")
273 mergedImage = vtkSegmentationCore.vtkOrientedImageData()
274 if not segmentationNode.GenerateMergedLabelmapForAllSegments(mergedImage,
275 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_SEGMENTS_PADDED,
276 None, visibleSegmentIds):
277 logging.error(
'Failed to apply smoothing: cannot get list of visible segments')
280 segmentLabelValues = []
281 for i
in range(visibleSegmentIds.GetNumberOfValues()):
282 segmentId = visibleSegmentIds.GetValue(i)
283 segmentLabelValues.append([segmentId, i+1])
286 ici = vtk.vtkImageChangeInformation()
287 ici.SetInputData(mergedImage)
288 ici.SetOutputSpacing(1, 1, 1)
289 ici.SetOutputOrigin(0, 0, 0)
292 convertToPolyData = vtk.vtkDiscreteMarchingCubes()
293 convertToPolyData.SetInputConnection(ici.GetOutputPort())
294 convertToPolyData.SetNumberOfContours(len(segmentLabelValues))
296 for segmentId, labelValue
in segmentLabelValues:
297 convertToPolyData.SetValue(contourIndex, labelValue)
301 smoothingFactor = self.scriptedEffect.doubleParameter(
"JointTaubinSmoothingFactor")
302 smoothingIterations = 100
303 passBand = pow(10.0, -4.0*smoothingFactor)
304 smoother = vtk.vtkWindowedSincPolyDataFilter()
305 smoother.SetInputConnection(convertToPolyData.GetOutputPort())
306 smoother.SetNumberOfIterations(smoothingIterations)
307 smoother.BoundarySmoothingOff()
308 smoother.FeatureEdgeSmoothingOff()
309 smoother.SetFeatureAngle(90.0)
310 smoother.SetPassBand(passBand)
311 smoother.NonManifoldSmoothingOn()
312 smoother.NormalizeCoordinatesOn()
315 threshold = vtk.vtkThreshold()
316 threshold.SetInputConnection(smoother.GetOutputPort())
319 geometryFilter = vtk.vtkGeometryFilter()
320 geometryFilter.SetInputConnection(threshold.GetOutputPort())
323 polyDataToImageStencil = vtk.vtkPolyDataToImageStencil()
324 polyDataToImageStencil.SetInputConnection(geometryFilter.GetOutputPort())
325 polyDataToImageStencil.SetOutputSpacing(1,1,1)
326 polyDataToImageStencil.SetOutputOrigin(0,0,0)
327 polyDataToImageStencil.SetOutputWholeExtent(mergedImage.GetExtent())
330 stencil = vtk.vtkImageStencil()
331 emptyBinaryLabelMap = vtk.vtkImageData()
332 emptyBinaryLabelMap.SetExtent(mergedImage.GetExtent())
333 emptyBinaryLabelMap.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
334 vtkSegmentationCore.vtkOrientedImageDataResample.FillImage(emptyBinaryLabelMap, 0)
335 stencil.SetInputData(emptyBinaryLabelMap)
336 stencil.SetStencilConnection(polyDataToImageStencil.GetOutputPort())
337 stencil.ReverseStencilOn()
338 stencil.SetBackgroundValue(1)
340 imageToWorldMatrix = vtk.vtkMatrix4x4()
341 mergedImage.GetImageToWorldMatrix(imageToWorldMatrix)
343 for segmentId, labelValue
in segmentLabelValues:
344 threshold.ThresholdBetween(labelValue, labelValue)
346 smoothedBinaryLabelMap = vtkSegmentationCore.vtkOrientedImageData()
347 smoothedBinaryLabelMap.ShallowCopy(stencil.GetOutput())
348 smoothedBinaryLabelMap.SetImageToWorldMatrix(imageToWorldMatrix)
350 slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(smoothedBinaryLabelMap,
351 segmentationNode, segmentId, slicer.vtkSlicerSegmentationsModuleLogic.MODE_REPLACE, smoothedBinaryLabelMap.GetExtent())
354 GAUSSIAN =
'GAUSSIAN' 355 MORPHOLOGICAL_OPENING =
'MORPHOLOGICAL_OPENING' 356 MORPHOLOGICAL_CLOSING =
'MORPHOLOGICAL_CLOSING' 357 JOINT_TAUBIN =
'JOINT_TAUBIN' def updateGUIFromMRML(self)
def setupOptionsFrame(self)
jointTaubinSmoothingFactorLabel
def smoothSelectedSegment(self)
def updateParameterWidgetsVisibility(self)
def createCursor(self, widget)
gaussianStandardDeviationMmSpinBox
def updateMRMLFromGUI(self)
def setMRMLDefaults(self)
def smoothMultipleSegments(self)
def __init__(self, scriptedEffect)
gaussianStandardDeviationMmLabel
jointTaubinSmoothingFactorSlider
def getKernelSizePixel(self)