Slicer 5.6
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
AbstractScriptedSegmentEditorAutoCompleteEffect.py
Go to the documentation of this file.
1import logging
2
3import ctk
4import qt
5import vtk
6
7import slicer
8from slicer.i18n import tr as _
9
10from .AbstractScriptedSegmentEditorEffect import *
11
12__all__ = ["AbstractScriptedSegmentEditorAutoCompleteEffect"]
13
14
15#
16# Abstract class of python scripted segment editor auto-complete effects
17#
18# Auto-complete effects are a subtype of general effects that allow preview
19# and refinement of segmentation results before accepting them.
20#
21
22
24 """AutoCompleteEffect is an effect that can create a full segmentation
25 from a partial segmentation (not all slices are segmented or only
26 part of the target structures are painted).
27 """
28
29 def __init__(self, scriptedEffect):
30 # Indicates that effect does not operate on one segment, but the whole segmentation.
31 # This means that while this effect is active, no segment can be selected
32 scriptedEffect.perSegment = False
33 AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
34
35 # Number of segments required when editable area is not specified
37 # Number of segments required when editable area is specified
41
42 # Stores merged labelmap image geometry (voxel data is not allocated)
45 self.selectedSegmentModifiedTimes = {} # map from segment ID to ModifiedTime
48
49 # Observation for auto-update
52
53 # Wait this much after the last modified event before starting aut-update:
54 autoUpdateDelaySec = 1.0
55 self.delayedAutoUpdateTimer = qt.QTimer()
56 self.delayedAutoUpdateTimer.setSingleShot(True)
57 self.delayedAutoUpdateTimer.interval = autoUpdateDelaySec * 1000
58 self.delayedAutoUpdateTimer.connect("timeout()", self.onPreviewonPreview)
59
60 self.extentGrowthRatio = 0.1 # extent of seed region will be grown outside by this much
62
64
65 def __del__(self):
66 self.delayedAutoUpdateTimer.stop()
67 self.observeSegmentation(False)
68
69 @staticmethod
70 def isBackgroundLabelmap(labelmapOrientedImageData, label=None):
71 if labelmapOrientedImageData is None:
72 return False
73 # If five or more corner voxels of the image contain non-zero, then it is background
74 extent = labelmapOrientedImageData.GetExtent()
75 if extent[0] > extent[1] or extent[2] > extent[3] or extent[4] > extent[5]:
76 return False
77 numberOfFilledCorners = 0
78 for i in [0, 1]:
79 for j in [2, 3]:
80 for k in [4, 5]:
81 voxelValue = labelmapOrientedImageData.GetScalarComponentAsFloat(extent[i], extent[j], extent[k], 0)
82 if label is None:
83 if voxelValue > 0:
84 numberOfFilledCorners += 1
85 else:
86 if voxelValue == label:
87 numberOfFilledCorners += 1
88 if numberOfFilledCorners > 4:
89 return True
90 return False
91
93 self.autoUpdateCheckBox = qt.QCheckBox(_("Auto-update"))
94 self.autoUpdateCheckBox.setToolTip(_("Auto-update results preview when input segments change."))
95 self.autoUpdateCheckBox.setChecked(True)
96 self.autoUpdateCheckBox.setEnabled(False)
97
98 self.previewButton = qt.QPushButton(_("Initialize"))
99 self.previewButton.objectName = self.__class__.__name__ + "Preview"
100 self.previewButton.setToolTip(_("Preview complete segmentation"))
101 # qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
102 # fails on some systems, therefore set the policies using separate method calls
103 qSize = qt.QSizePolicy()
104 qSize.setHorizontalPolicy(qt.QSizePolicy.Expanding)
105 self.previewButton.setSizePolicy(qSize)
106
107 previewFrame = qt.QHBoxLayout()
108 previewFrame.addWidget(self.autoUpdateCheckBox)
109 previewFrame.addWidget(self.previewButton)
110 self.scriptedEffect.addLabeledOptionsWidget(_("Preview:"), previewFrame)
111
112 self.previewOpacitySlider = ctk.ctkSliderWidget()
113 self.previewOpacitySlider.setToolTip(_("Adjust visibility of results preview."))
114 self.previewOpacitySlider.minimum = 0
115 self.previewOpacitySlider.maximum = 1.0
116 self.previewOpacitySlider.value = 0.0
117 self.previewOpacitySlider.singleStep = 0.05
118 self.previewOpacitySlider.pageStep = 0.1
119 self.previewOpacitySlider.spinBoxVisible = False
120
121 self.previewShow3DButton = qt.QPushButton(_("Show 3D"))
122 self.previewShow3DButton.setToolTip(_("Preview results in 3D."))
123 self.previewShow3DButton.setCheckable(True)
124
125 displayFrame = qt.QHBoxLayout()
126 displayFrame.addWidget(qt.QLabel(_("inputs")))
127 displayFrame.addWidget(self.previewOpacitySlider)
128 displayFrame.addWidget(qt.QLabel(_("results")))
129 displayFrame.addWidget(self.previewShow3DButton)
130 self.scriptedEffect.addLabeledOptionsWidget(_("Display:"), displayFrame)
131
132 self.cancelButton = qt.QPushButton(_("Cancel"))
133 self.cancelButton.objectName = self.__class__.__name__ + "Cancel"
134 self.cancelButton.setToolTip(_("Clear preview and cancel auto-complete"))
135
136 self.applyButton = qt.QPushButton(_("Apply"))
137 self.applyButton.objectName = self.__class__.__name__ + "Apply"
138 self.applyButton.setToolTip(_("Replace segments by previewed result"))
139
140 finishFrame = qt.QHBoxLayout()
141 finishFrame.addWidget(self.cancelButton)
142 finishFrame.addWidget(self.applyButton)
143 self.scriptedEffect.addOptionsWidget(finishFrame)
144
145 self.previewButton.connect("clicked()", self.onPreviewonPreview)
146 self.cancelButton.connect("clicked()", self.onCancelonCancel)
147 self.applyButton.connect("clicked()", self.onApplyonApply)
148 self.previewOpacitySlider.connect("valueChanged(double)", self.updateMRMLFromGUIupdateMRMLFromGUI)
149 self.previewShow3DButton.connect("toggled(bool)", self.updateMRMLFromGUIupdateMRMLFromGUI)
150 self.autoUpdateCheckBox.connect("stateChanged(int)", self.updateMRMLFromGUIupdateMRMLFromGUI)
151
152 def createCursor(self, widget):
153 # Turn off effect-specific cursor for this effect
154 return slicer.util.mainWindow().cursor
155
157 self.scriptedEffect.setParameterDefault("AutoUpdate", "1")
158
159 def onSegmentationModified(self, caller, event):
160 if not self.autoUpdateCheckBox.isChecked():
161 # just in case a queued request comes through
162 return
163
164 import vtkSegmentationCorePython as vtkSegmentationCore
165
166 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
167 segmentation = segmentationNode.GetSegmentation()
168
169 updateNeeded = False
170 for segmentIndex in range(self.selectedSegmentIds.GetNumberOfValues()):
171 segmentID = self.selectedSegmentIds.GetValue(segmentIndex)
172 segment = segmentation.GetSegment(segmentID)
173 if not segment:
174 # selected segment was deleted, cancel segmentation
175 logging.debug("Segmentation operation is cancelled because an input segment was deleted")
176 self.onCancelonCancel()
177 slicer.util.showStatusMessage(_("Segmentation operation is cancelled because an input segment was deleted."), 3000)
178 return
179 segmentLabelmap = segment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
180 if segmentID in self.selectedSegmentModifiedTimes \
181 and segmentLabelmap and segmentLabelmap.GetMTime() == self.selectedSegmentModifiedTimes[segmentID]:
182 # this segment has not changed since last update
183 continue
184 if segmentLabelmap:
185 self.selectedSegmentModifiedTimes[segmentID] = segmentLabelmap.GetMTime()
186 elif segmentID in self.selectedSegmentModifiedTimes:
187 self.selectedSegmentModifiedTimes.pop(segmentID)
188 updateNeeded = True
189 # continue so that all segment modified times are updated
190
191 if not updateNeeded:
192 return
193
194 logging.debug("Segmentation update requested")
195 # There could be multiple update events for a single paint operation (e.g., one segment overwrites the other)
196 # therefore don't update directly, just set up/reset a timer that will perform the update when it elapses.
198 self.delayedAutoUpdateTimer.start()
199
200 def observeSegmentation(self, observationEnabled):
201 import vtkSegmentationCorePython as vtkSegmentationCore
202
203 parameterSetNode = self.scriptedEffect.parameterSetNode()
204 segmentationNode = None
205 if parameterSetNode:
206 segmentationNode = parameterSetNode.GetSegmentationNode()
207
208 segmentation = None
209 if segmentationNode:
210 segmentation = segmentationNode.GetSegmentation()
211
212 if observationEnabled and self.observedSegmentation == segmentation:
213 return
214 if not observationEnabled and not self.observedSegmentation:
215 return
216 # Need to update the observer
217 # Remove old observer
218 if self.observedSegmentation:
219 for tag in self.segmentationNodeObserverTags:
220 self.observedSegmentation.RemoveObserver(tag)
222 self.observedSegmentation = None
223 # Add new observer
224 if observationEnabled and segmentation is not None:
225 self.observedSegmentation = segmentation
226 observedEvents = [
227 vtkSegmentationCore.vtkSegmentation.SegmentAdded,
228 vtkSegmentationCore.vtkSegmentation.SegmentRemoved,
229 vtkSegmentationCore.vtkSegmentation.SegmentModified,
230 vtkSegmentationCore.vtkSegmentation.SourceRepresentationModified]
231 for eventId in observedEvents:
233
234 def getPreviewNode(self):
235 previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
236 if (previewNode
237 and self.scriptedEffect.parameterDefined("SegmentationResultPreviewOwnerEffect")
238 and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name):
239 # another effect owns this preview node
240 return None
241 return previewNode
242
244 previewNode = self.getPreviewNode()
245
246 self.cancelButton.setEnabled(previewNode is not None)
247 self.applyButton.setEnabled(previewNode is not None)
248
249 self.previewOpacitySlider.setEnabled(previewNode is not None)
250 if previewNode:
251 wasBlocked = self.previewOpacitySlider.blockSignals(True)
252 self.previewOpacitySlider.value = self.getPreviewOpacity()
253 self.previewOpacitySlider.blockSignals(wasBlocked)
254 self.previewButton.text = _("Update")
255 self.previewShow3DButton.setEnabled(True)
256 self.previewShow3DButton.setChecked(self.getPreviewShow3D())
257 self.autoUpdateCheckBox.setEnabled(True)
258 self.observeSegmentation(self.autoUpdateCheckBox.isChecked())
259 else:
260 self.previewButton.text = _("Initialize")
261 self.autoUpdateCheckBox.setEnabled(False)
262 self.previewShow3DButton.setEnabled(False)
263 self.delayedAutoUpdateTimer.stop()
264 self.observeSegmentation(False)
265
266 autoUpdate = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("AutoUpdate") == 0 else qt.Qt.Checked
267 wasBlocked = self.autoUpdateCheckBox.blockSignals(True)
268 self.autoUpdateCheckBox.setCheckState(autoUpdate)
269 self.autoUpdateCheckBox.blockSignals(wasBlocked)
270
272 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
273 previewNode = self.getPreviewNode()
274 if previewNode:
276 self.setPreviewShow3D(self.previewShow3DButton.checked)
277
278 autoUpdate = 1 if self.autoUpdateCheckBox.isChecked() else 0
279 self.scriptedEffect.setParameter("AutoUpdate", autoUpdate)
280
281 def onPreview(self):
283 return
285
286 slicer.util.showStatusMessage(_("Running {effectName} auto-complete...").format(effectName=self.scriptedEffect.name), 2000)
287 try:
288 with slicer.util.tryWithErrorDisplay(_("Segmentation operation failed:"), waitCursor=True):
289 self.preview()
290 finally:
292
293 def reset(self):
294 self.delayedAutoUpdateTimer.stop()
295 self.observeSegmentation(False)
296 previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
297 if previewNode:
298 self.scriptedEffect.parameterSetNode().SetNodeReferenceID(ResultPreviewNodeReferenceRole, None)
299 slicer.mrmlScene.RemoveNode(previewNode)
300 self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", "")
301 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
302 segmentationNode.GetDisplayNode().SetOpacity(1.0)
304 self.selectedSegmentIds = None
306 self.clippedMasterImageData = None
307 self.clippedMaskImageData = None
308 self.updateGUIFromMRML()
309
310 def onCancel(self):
311 self.reset()
312
313 def onApply(self):
314 self.delayedAutoUpdateTimer.stop()
315 self.observeSegmentation(False)
316
317 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
318 segmentationDisplayNode = segmentationNode.GetDisplayNode()
319 previewNode = self.getPreviewNode()
320
321 self.scriptedEffect.saveStateForUndo()
322
323 previewContainsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation(
324 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
325
326 # Move segments from preview into current segmentation
327 segmentIDs = vtk.vtkStringArray()
328 previewNode.GetSegmentation().GetSegmentIDs(segmentIDs)
329 for index in range(segmentIDs.GetNumberOfValues()):
330 segmentID = segmentIDs.GetValue(index)
331 previewSegmentLabelmap = slicer.vtkOrientedImageData()
332 previewNode.GetBinaryLabelmapRepresentation(segmentID, previewSegmentLabelmap)
333 self.scriptedEffect.modifySegmentByLabelmap(segmentationNode, segmentID, previewSegmentLabelmap,
334 slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
335 if segmentationDisplayNode is not None and self.isBackgroundLabelmap(previewSegmentLabelmap):
336 # Automatically hide result segments that are background (all eight corners are non-zero)
337 segmentationDisplayNode.SetSegmentVisibility(segmentID, False)
338 previewNode.GetSegmentation().RemoveSegment(segmentID) # delete now to limit memory usage
339
340 if previewContainsClosedSurfaceRepresentation:
341 segmentationNode.CreateClosedSurfaceRepresentation()
342
343 self.reset()
344
345 def setPreviewOpacity(self, opacity):
346 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
347 segmentationNode.GetDisplayNode().SetOpacity(1.0 - opacity)
348 previewNode = self.getPreviewNode()
349 if previewNode:
350 previewNode.GetDisplayNode().SetOpacity(opacity)
351 previewNode.GetDisplayNode().SetOpacity3D(opacity)
352
353 # Make sure the GUI is up-to-date
354 wasBlocked = self.previewOpacitySlider.blockSignals(True)
355 self.previewOpacitySlider.value = opacity
356 self.previewOpacitySlider.blockSignals(wasBlocked)
357
359 previewNode = self.getPreviewNode()
360 return previewNode.GetDisplayNode().GetOpacity() if previewNode else 0.6 # default opacity for preview
361
362 def setPreviewShow3D(self, show):
363 previewNode = self.getPreviewNode()
364 if previewNode:
365 if show:
366 previewNode.CreateClosedSurfaceRepresentation()
367 else:
368 previewNode.RemoveClosedSurfaceRepresentation()
369
370 # Make sure the GUI is up-to-date
371 wasBlocked = self.previewShow3DButton.blockSignals(True)
372 self.previewShow3DButton.checked = show
373 self.previewShow3DButton.blockSignals(wasBlocked)
374
376 previewNode = self.getPreviewNode()
377 if not previewNode:
378 return False
379 containsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation(
380 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
381 return containsClosedSurfaceRepresentation
382
384 if self.getPreviewNode() is None:
385 return True
386 if self.mergedLabelmapGeometryImage is None:
387 return True
388 if self.selectedSegmentIds is None:
389 return True
390
391 import vtkSegmentationCorePython as vtkSegmentationCore
392
393 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
394
395 # The effective extent for the current input segments
396 effectiveGeometryImage = slicer.vtkOrientedImageData()
397 effectiveGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
398 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
399 if effectiveGeometryString is None:
400 return True
401 vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(effectiveGeometryString, effectiveGeometryImage)
402
403 masterImageData = self.scriptedEffect.sourceVolumeImageData()
404 masterImageExtent = masterImageData.GetExtent()
405
406 # The effective extent of the selected segments
407 effectiveLabelExtent = effectiveGeometryImage.GetExtent()
408 # Current extent used for auto-complete preview
409 currentLabelExtent = self.mergedLabelmapGeometryImage.GetExtent()
410
411 # Determine if the current merged labelmap extent has less than a 3 voxel margin around the effective segment extent
412 # (limited by the source image extent)
413 return ((masterImageExtent[0] != currentLabelExtent[0] and currentLabelExtent[0] > effectiveLabelExtent[0] - self.minimumExtentMargin) or
414 (masterImageExtent[1] != currentLabelExtent[1] and currentLabelExtent[1] < effectiveLabelExtent[1] + self.minimumExtentMargin) or
415 (masterImageExtent[2] != currentLabelExtent[2] and currentLabelExtent[2] > effectiveLabelExtent[2] - self.minimumExtentMargin) or
416 (masterImageExtent[3] != currentLabelExtent[3] and currentLabelExtent[3] < effectiveLabelExtent[3] + self.minimumExtentMargin) or
417 (masterImageExtent[4] != currentLabelExtent[4] and currentLabelExtent[4] > effectiveLabelExtent[4] - self.minimumExtentMargin) or
418 (masterImageExtent[5] != currentLabelExtent[5] and currentLabelExtent[5] < effectiveLabelExtent[5] + self.minimumExtentMargin))
419
420 def preview(self):
421 # Get source volume image data
422 import vtkSegmentationCorePython as vtkSegmentationCore
423
424 # Get segmentation
425 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
426
427 previewNode = self.getPreviewNode()
428 previewOpacity = self.getPreviewOpacity()
429 previewShow3D = self.getPreviewShow3D()
430
431 # If the selectedSegmentIds have been specified, then they shouldn't be overwritten here
432 currentSelectedSegmentIds = self.selectedSegmentIds
433
434 if self.effectiveExtentChanged():
435 self.reset()
436
437 # Restore the selectedSegmentIds
438 self.selectedSegmentIds = currentSelectedSegmentIds
439 if self.selectedSegmentIds is None:
440 self.selectedSegmentIds = vtk.vtkStringArray()
441 segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(self.selectedSegmentIds)
442
444 editableAreaSpecified = (
445 self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
446 or self.scriptedEffect.parameterSetNode().GetMaskMode() != slicer.vtkMRMLSegmentationNode.EditAllowedEverywhere)
447 if editableAreaSpecified and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegmentsWithEditableArea:
448 logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments are required when editable area is defined")
449 raise RuntimeError(
450 _("Minimum {minimumNumberOfSegments} visible segments are required.").format(
451 minimumNumberOfSegments=self.minimumNumberOfSegmentsWithEditableArea))
452 elif (not editableAreaSpecified) and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
453 logging.error(f"Auto-complete operation skipped: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments or setting of editable area is required")
454 raise RuntimeError(
455 _("Minimum {minimumNumberOfSegments} visible segments (or specification of editable area or intensity range) is required.").format(
456 minimumNumberOfSegments=self.minimumNumberOfSegments))
457 elif self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
458 # Same number of input segments required regardless of editable area
459 logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegments} visible segments are required")
460 self.selectedSegmentIds = None
461 raise RuntimeError(
462 _("Minimum {minimumNumberOfSegments} visible segments are required.").format(
463 minimumNumberOfSegments=self.minimumNumberOfSegments))
464
465 # Compute merged labelmap extent (effective extent slightly expanded)
466 if not self.mergedLabelmapGeometryImage:
467 self.mergedLabelmapGeometryImage = slicer.vtkOrientedImageData()
468 commonGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
469 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
470 if not commonGeometryString:
471 logging.info("Auto-complete operation skipped: all visible segments are empty")
472 return
473 vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(commonGeometryString, self.mergedLabelmapGeometryImage)
474
475 masterImageData = self.scriptedEffect.sourceVolumeImageData()
476 masterImageExtent = masterImageData.GetExtent()
477 labelsEffectiveExtent = self.mergedLabelmapGeometryImage.GetExtent()
478 # Margin size is relative to combined seed region size, but minimum of 3 voxels
479 print(f"self.extentGrowthRatio = {self.extentGrowthRatio}")
480 margin = [
481 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[1] - labelsEffectiveExtent[0]))),
482 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[3] - labelsEffectiveExtent[2]))),
483 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[5] - labelsEffectiveExtent[4])))]
484 labelsExpandedExtent = [
485 max(masterImageExtent[0], labelsEffectiveExtent[0] - margin[0]),
486 min(masterImageExtent[1], labelsEffectiveExtent[1] + margin[0]),
487 max(masterImageExtent[2], labelsEffectiveExtent[2] - margin[1]),
488 min(masterImageExtent[3], labelsEffectiveExtent[3] + margin[1]),
489 max(masterImageExtent[4], labelsEffectiveExtent[4] - margin[2]),
490 min(masterImageExtent[5], labelsEffectiveExtent[5] + margin[2])]
491 print("masterImageExtent = " + repr(masterImageExtent))
492 print("labelsEffectiveExtent = " + repr(labelsEffectiveExtent))
493 print("labelsExpandedExtent = " + repr(labelsExpandedExtent))
494 self.mergedLabelmapGeometryImage.SetExtent(labelsExpandedExtent)
495
496 # Create and setup preview node
497 previewNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
498 previewNode.CreateDefaultDisplayNodes()
499 previewNode.GetDisplayNode().SetVisibility2DOutline(False)
500 if segmentationNode.GetParentTransformNode():
501 previewNode.SetAndObserveTransformNodeID(segmentationNode.GetParentTransformNode().GetID())
502 self.scriptedEffect.parameterSetNode().SetNodeReferenceID(ResultPreviewNodeReferenceRole, previewNode.GetID())
503 self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", self.scriptedEffect.name)
504 self.setPreviewOpacity(0.6)
505
506 # Disable smoothing for closed surface generation to make it fast
507 previewNode.GetSegmentation().SetConversionParameter(
508 slicer.vtkBinaryLabelmapToClosedSurfaceConversionRule.GetSmoothingFactorParameterName(),
509 "-0.5")
510
511 inputContainsClosedSurfaceRepresentation = segmentationNode.GetSegmentation().ContainsRepresentation(
512 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
513
514 self.setPreviewShow3D(inputContainsClosedSurfaceRepresentation)
515
517 self.clippedMasterImageData = slicer.vtkOrientedImageData()
518 masterImageClipper = vtk.vtkImageConstantPad()
519 masterImageClipper.SetInputData(masterImageData)
520 masterImageClipper.SetOutputWholeExtent(self.mergedLabelmapGeometryImage.GetExtent())
521 masterImageClipper.Update()
522 self.clippedMasterImageData.ShallowCopy(masterImageClipper.GetOutput())
524
525 self.clippedMaskImageData = None
527 self.clippedMaskImageData = slicer.vtkOrientedImageData()
528 intensityBasedMasking = self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
529 maskSegmentID = self.scriptedEffect.parameterSetNode().GetMaskSegmentID() if self.scriptedEffect.parameterSetNode().GetMaskSegmentID() else ""
530 intensityRange = self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMaskRange() if intensityBasedMasking else None
531 success = segmentationNode.GenerateEditMask(self.clippedMaskImageData,
532 self.scriptedEffect.parameterSetNode().GetMaskMode(),
533 self.clippedMasterImageData, # reference geometry
534 "", # edited segment ID
535 maskSegmentID,
536 self.clippedMasterImageData if intensityBasedMasking else None,
537 intensityRange)
538 if not success:
539 logging.error("Failed to create edit mask")
540 self.clippedMaskImageData = None
541
542 previewNode.SetName(segmentationNode.GetName() + " preview")
543 previewNode.RemoveClosedSurfaceRepresentation() # Force the closed surface representation to update
544 # TODO: This will no longer be required when we can use the segment editor to set multiple segments
545 # as the closed surfaces will be converted as necessary by the segmentation logic.
546
547 mergedImage = slicer.vtkOrientedImageData()
548 segmentationNode.GenerateMergedLabelmapForAllSegments(
549 mergedImage,
550 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.mergedLabelmapGeometryImage, self.selectedSegmentIds)
551
552 outputLabelmap = slicer.vtkOrientedImageData()
553 self.computePreviewLabelmap(mergedImage, outputLabelmap)
554
555 if previewNode.GetSegmentation().GetNumberOfSegments() != self.selectedSegmentIds.GetNumberOfValues():
556 # first update (or number of segments changed), need a full reinitialization
557 previewNode.GetSegmentation().RemoveAllSegments()
558
559 for index in range(self.selectedSegmentIds.GetNumberOfValues()):
560 segmentID = self.selectedSegmentIds.GetValue(index)
561
562 previewSegment = previewNode.GetSegmentation().GetSegment(segmentID)
563 if not previewSegment:
564 inputSegment = segmentationNode.GetSegmentation().GetSegment(segmentID)
565
566 previewSegment = vtkSegmentationCore.vtkSegment()
567 previewSegment.SetName(inputSegment.GetName())
568 previewSegment.SetColor(inputSegment.GetColor())
569 previewNode.GetSegmentation().AddSegment(previewSegment, segmentID)
570
571 labelValue = index + 1 # n-th segment label value = n + 1 (background label value is 0)
572 previewSegment.AddRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName(), outputLabelmap)
573 previewSegment.SetLabelValue(labelValue)
574
575 # Automatically hide result segments that are background (all eight corners are non-zero)
576 previewNode.GetDisplayNode().SetSegmentVisibility3D(segmentID, not self.isBackgroundLabelmap(outputLabelmap, labelValue))
577
578 # If the preview was reset, we need to restore the visibility options
579 self.setPreviewOpacity(previewOpacity)
580 self.setPreviewShow3D(previewShow3D)
581
582 self.updateGUIFromMRML()
583
584
585ResultPreviewNodeReferenceRole = "SegmentationResultPreview"