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).")
56 self.
kernelSizePixel.setToolTip(
"Diameter of the neighborhood in pixels. Computed from the segment's spacing and the specified kernel size.")
58 kernelSizeFrame = qt.QHBoxLayout()
61 self.
kernelSizeMmLabel = self.scriptedEffect.addLabeledOptionsWidget(
"Kernel size:", kernelSizeFrame)
65 self.
gaussianStandardDeviationMmSpinBox.setToolTip(
"Standard deviation of the Gaussian smoothing filter coefficients. Higher value makes smoothing stronger (more details are suppressed).")
81 self.
applyButton.objectName = self.__class__.__name__ +
'Apply' 82 self.
applyButton.setToolTip(
"Apply smoothing to selected segment")
83 self.scriptedEffect.addOptionsWidget(self.
applyButton)
93 return slicer.util.mainWindow().cursor
96 self.scriptedEffect.setParameterDefault(
"SmoothingMethod", MEDIAN)
97 self.scriptedEffect.setParameterDefault(
"KernelSizeMm", 3)
98 self.scriptedEffect.setParameterDefault(
"GaussianStandardDeviationMm", 3)
99 self.scriptedEffect.setParameterDefault(
"JointTaubinSmoothingFactor", 0.5)
104 morphologicalMethod = (smoothingMethod==MEDIAN
or smoothingMethod==MORPHOLOGICAL_OPENING
or smoothingMethod==MORPHOLOGICAL_CLOSING)
114 selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
115 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
116 if selectedSegmentLabelmap:
117 selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
120 kernelSizeMm = self.scriptedEffect.doubleParameter(
"KernelSizeMm")
121 kernelSizePixel = [int(round((kernelSizeMm / selectedSegmentLabelmapSpacing[componentIndex]+1)/2)*2-1)
for componentIndex
in range(3)]
122 return kernelSizePixel
131 self.setWidgetMinMaxStepFromImageSpacing(self.
kernelSizeMmSpinBox, self.scriptedEffect.selectedSegmentLabelmap())
135 self.
kernelSizePixel.text =
"{0}x{1}x{2} pixels".format(kernelSizePixel[0], kernelSizePixel[1], kernelSizePixel[2])
151 self.scriptedEffect.setParameter(
"SmoothingMethod", smoothingMethod)
165 qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
167 self.scriptedEffect.saveStateForUndo()
169 smoothingMethod = self.scriptedEffect.parameter(
"SmoothingMethod")
170 if smoothingMethod == JOINT_TAUBIN:
175 qt.QApplication.restoreOverrideCursor()
181 import vtkSegmentationCorePython
184 modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
185 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
187 smoothingMethod = self.scriptedEffect.parameter(
"SmoothingMethod")
189 if smoothingMethod == GAUSSIAN:
192 thresh = vtk.vtkImageThreshold()
193 thresh.SetInputData(selectedSegmentLabelmap)
194 thresh.ThresholdByLower(0)
196 thresh.SetOutValue(maxValue)
197 thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR)
199 standardDeviationMm = self.scriptedEffect.doubleParameter(
"GaussianStandardDeviationMm")
200 gaussianFilter = vtk.vtkImageGaussianSmooth()
201 gaussianFilter.SetInputConnection(thresh.GetOutputPort())
202 gaussianFilter.SetStandardDeviation(standardDeviationMm)
203 gaussianFilter.SetRadiusFactor(4)
205 thresh2 = vtk.vtkImageThreshold()
206 thresh2.SetInputConnection(gaussianFilter.GetOutputPort())
207 thresh2.ThresholdByUpper(maxValue/2)
208 thresh2.SetInValue(1)
209 thresh2.SetOutValue(0)
210 thresh2.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
212 modifierLabelmap.DeepCopy(thresh2.GetOutput())
218 if smoothingMethod == MEDIAN:
220 smoothingFilter = vtk.vtkImageMedian3D()
221 smoothingFilter.SetInputData(selectedSegmentLabelmap)
227 thresh = vtk.vtkImageThreshold()
228 thresh.SetInputData(selectedSegmentLabelmap)
229 thresh.ThresholdByLower(0)
230 thresh.SetInValue(backgroundValue)
231 thresh.SetOutValue(labelValue)
232 thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
234 smoothingFilter = vtk.vtkImageOpenClose3D()
235 smoothingFilter.SetInputConnection(thresh.GetOutputPort())
236 if smoothingMethod == MORPHOLOGICAL_OPENING:
237 smoothingFilter.SetOpenValue(labelValue)
238 smoothingFilter.SetCloseValue(backgroundValue)
240 smoothingFilter.SetOpenValue(backgroundValue)
241 smoothingFilter.SetCloseValue(labelValue)
243 smoothingFilter.SetKernelSize(kernelSizePixel[0],kernelSizePixel[1],kernelSizePixel[2])
244 smoothingFilter.Update()
245 modifierLabelmap.DeepCopy(smoothingFilter.GetOutput())
248 logging.error(
'apply: Failed to apply smoothing')
251 self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
254 import vtkSegmentationCorePython
as vtkSegmentationCore
257 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
258 visibleSegmentIds = vtk.vtkStringArray()
259 segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(visibleSegmentIds)
260 if visibleSegmentIds.GetNumberOfValues() == 0:
261 logging.info(
"Smoothing operation skipped: there are no visible segments")
264 mergedImage = vtkSegmentationCore.vtkOrientedImageData()
265 if not segmentationNode.GenerateMergedLabelmapForAllSegments(mergedImage,
266 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_SEGMENTS_PADDED,
267 None, visibleSegmentIds):
268 logging.error(
'Failed to apply smoothing: cannot get list of visible segments')
271 segmentLabelValues = []
272 for i
in range(visibleSegmentIds.GetNumberOfValues()):
273 segmentId = visibleSegmentIds.GetValue(i)
274 segmentLabelValues.append([segmentId, i+1])
277 ici = vtk.vtkImageChangeInformation()
278 ici.SetInputData(mergedImage)
279 ici.SetOutputSpacing(1, 1, 1)
280 ici.SetOutputOrigin(0, 0, 0)
286 convertToPolyData = vtk.vtkDiscreteMarchingCubes()
287 convertToPolyData.SetInputConnection(ici.GetOutputPort())
288 convertToPolyData.SetNumberOfContours(len(segmentLabelValues))
291 for segmentId, labelValue
in segmentLabelValues:
292 convertToPolyData.SetValue(contourIndex, labelValue)
296 smoothingFactor = self.scriptedEffect.doubleParameter(
"JointTaubinSmoothingFactor")
297 smoothingIterations = 100
298 passBand = pow(10.0, -4.0*smoothingFactor)
299 smoother = vtk.vtkWindowedSincPolyDataFilter()
300 smoother.SetInputConnection(convertToPolyData.GetOutputPort())
301 smoother.SetNumberOfIterations(smoothingIterations)
302 smoother.BoundarySmoothingOff()
303 smoother.FeatureEdgeSmoothingOff()
304 smoother.SetFeatureAngle(90.0)
305 smoother.SetPassBand(passBand)
306 smoother.NonManifoldSmoothingOn()
307 smoother.NormalizeCoordinatesOn()
310 threshold = vtk.vtkThreshold()
311 threshold.SetInputConnection(smoother.GetOutputPort())
314 geometryFilter = vtk.vtkGeometryFilter()
315 geometryFilter.SetInputConnection(threshold.GetOutputPort())
318 polyDataToImageStencil = vtk.vtkPolyDataToImageStencil()
319 polyDataToImageStencil.SetInputConnection(geometryFilter.GetOutputPort())
320 polyDataToImageStencil.SetOutputSpacing(1,1,1)
321 polyDataToImageStencil.SetOutputOrigin(0,0,0)
322 polyDataToImageStencil.SetOutputWholeExtent(mergedImage.GetExtent())
325 stencil = vtk.vtkImageStencil()
326 emptyBinaryLabelMap = vtk.vtkImageData()
327 emptyBinaryLabelMap.SetExtent(mergedImage.GetExtent())
328 emptyBinaryLabelMap.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
329 vtkSegmentationCore.vtkOrientedImageDataResample.FillImage(emptyBinaryLabelMap, 0)
330 stencil.SetInputData(emptyBinaryLabelMap)
331 stencil.SetStencilConnection(polyDataToImageStencil.GetOutputPort())
332 stencil.ReverseStencilOn()
333 stencil.SetBackgroundValue(1)
335 imageToWorldMatrix = vtk.vtkMatrix4x4()
336 mergedImage.GetImageToWorldMatrix(imageToWorldMatrix)
338 for segmentId, labelValue
in segmentLabelValues:
339 threshold.ThresholdBetween(labelValue, labelValue)
341 smoothedBinaryLabelMap = vtkSegmentationCore.vtkOrientedImageData()
342 smoothedBinaryLabelMap.ShallowCopy(stencil.GetOutput())
343 smoothedBinaryLabelMap.SetImageToWorldMatrix(imageToWorldMatrix)
345 slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(smoothedBinaryLabelMap,
346 segmentationNode, segmentId, slicer.vtkSlicerSegmentationsModuleLogic.MODE_REPLACE, smoothedBinaryLabelMap.GetExtent())
349 GAUSSIAN =
'GAUSSIAN' 350 MORPHOLOGICAL_OPENING =
'MORPHOLOGICAL_OPENING' 351 MORPHOLOGICAL_CLOSING =
'MORPHOLOGICAL_CLOSING' 352 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)