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