Slicer  4.8
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 os
2 import vtk, qt, ctk, slicer, logging
3 from AbstractScriptedSegmentEditorEffect import *
4 
5 __all__ = ['AbstractScriptedSegmentEditorAutoCompleteEffect']
6 
7 #
8 # Abstract class of python scripted segment editor auto-complete effects
9 #
10 # Auto-complete effects are a subtype of general effects that allow preview
11 # and refinement of segmentation results before accepting them.
12 #
13 
15  """ AutoCompleteEffect is an effect that can create a full segmentation
16  from a partial segmentation (not all slices are segmented or only
17  part of the target structures are painted).
18  """
19 
20  def __init__(self, scriptedEffect):
21  # Indicates that effect does not operate on one segment, but the whole segmentation.
22  # This means that while this effect is active, no segment can be selected
23  scriptedEffect.perSegment = False
24  AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
25 
28 
29  # Stores merged labelmap image geometry (voxel data is not allocated)
31  self.selectedSegmentIds = None
32  self.selectedSegmentModifiedTimes = {} # map from segment ID to ModifiedTime
34 
35  # Observation for auto-update
38 
39  # Wait this much after the last modified event before starting aut-update:
40  autoUpdateDelaySec = 1.0
41  self.delayedAutoUpdateTimer = qt.QTimer()
42  self.delayedAutoUpdateTimer.setSingleShot(True)
43  self.delayedAutoUpdateTimer.interval = autoUpdateDelaySec * 1000
44  self.delayedAutoUpdateTimer.connect('timeout()', self.onPreview)
45 
46 
47  def __del__(self, scriptedEffect):
48  super(SegmentEditorAutoCompleteEffect,self).__del__()
49  self.delayedAutoUpdateTimer.stop()
50  self.observeSegmentation(False)
51 
52  def setupOptionsFrame(self):
53  self.autoUpdateCheckBox = qt.QCheckBox("Auto-update")
54  self.autoUpdateCheckBox.setToolTip("Auto-update results preview when input segments change.")
55  self.autoUpdateCheckBox.setChecked(True)
56  self.autoUpdateCheckBox.setEnabled(False)
57 
58  self.previewButton = qt.QPushButton("Initialize")
59  self.previewButton.objectName = self.__class__.__name__ + 'Preview'
60  self.previewButton.setToolTip("Preview complete segmentation")
61  # qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
62  # fails on some systems, therefore set the policies using separate method calls
63  qSize = qt.QSizePolicy()
64  qSize.setHorizontalPolicy(qt.QSizePolicy.Expanding)
65  self.previewButton.setSizePolicy(qSize)
66 
67  previewFrame = qt.QHBoxLayout()
68  previewFrame.addWidget(self.autoUpdateCheckBox)
69  previewFrame.addWidget(self.previewButton)
70  self.scriptedEffect.addLabeledOptionsWidget("Preview:", previewFrame)
71 
72  self.previewOpacitySlider = ctk.ctkSliderWidget()
73  self.previewOpacitySlider.setToolTip("Adjust visibility of results preview.")
74  self.previewOpacitySlider.minimum = 0
75  self.previewOpacitySlider.maximum = 1.0
76  self.previewOpacitySlider.value = 0.0
77  self.previewOpacitySlider.singleStep = 0.05
78  self.previewOpacitySlider.pageStep = 0.1
79  self.previewOpacitySlider.spinBoxVisible = False
80 
81  displayFrame = qt.QHBoxLayout()
82  displayFrame.addWidget(qt.QLabel("inputs"))
83  displayFrame.addWidget(self.previewOpacitySlider)
84  displayFrame.addWidget(qt.QLabel("results"))
85  self.scriptedEffect.addLabeledOptionsWidget("Display:", displayFrame)
86 
87  self.cancelButton = qt.QPushButton("Cancel")
88  self.cancelButton.objectName = self.__class__.__name__ + 'Cancel'
89  self.cancelButton.setToolTip("Clear preview and cancel auto-complete")
90 
91  self.applyButton = qt.QPushButton("Apply")
92  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
93  self.applyButton.setToolTip("Replace segments by previewed result")
94 
95  finishFrame = qt.QHBoxLayout()
96  finishFrame.addWidget(self.cancelButton)
97  finishFrame.addWidget(self.applyButton)
98  self.scriptedEffect.addOptionsWidget(finishFrame)
99 
100  self.previewButton.connect('clicked()', self.onPreview)
101  self.cancelButton.connect('clicked()', self.onCancel)
102  self.applyButton.connect('clicked()', self.onApply)
103  self.previewOpacitySlider.connect("valueChanged(double)", self.updateMRMLFromGUI)
104  self.autoUpdateCheckBox.connect("stateChanged(int)", self.updateMRMLFromGUI)
105 
106  def createCursor(self, widget):
107  # Turn off effect-specific cursor for this effect
108  return slicer.util.mainWindow().cursor
109 
110  def setMRMLDefaults(self):
111  self.scriptedEffect.setParameterDefault("AutoUpdate", "1")
112 
113  def onSegmentationModified(self, caller, event):
114  if not self.autoUpdateCheckBox.isChecked():
115  # just in case a queued request comes through
116  return
117 
118  import vtkSegmentationCorePython as vtkSegmentationCore
119  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
120  segmentation = segmentationNode.GetSegmentation()
121 
122  updateNeeded = False
123  for segmentIndex in range(self.selectedSegmentIds.GetNumberOfValues()):
124  segmentID = self.selectedSegmentIds.GetValue(segmentIndex)
125  segment = segmentation.GetSegment(segmentID)
126  if not segment:
127  # selected segment was deleted, cancel segmentation
128  logging.debug("Segmentation cancelled because an input segment was deleted")
129  self.onCancel()
130  return
131  segmentLabelmap = segment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
132  if self.selectedSegmentModifiedTimes.has_key(segmentID) \
133  and segmentLabelmap.GetMTime() == self.selectedSegmentModifiedTimes[segmentID]:
134  # this segment has not changed since last update
135  continue
136  self.selectedSegmentModifiedTimes[segmentID] = segmentLabelmap.GetMTime()
137  updateNeeded = True
138  # continue so that all segment modified times are updated
139 
140  if not updateNeeded:
141  return
142 
143  logging.debug("Segmentation update requested")
144  # There could be multiple update events for a single paint operation (e.g., one segment overwrites the other)
145  # therefore don't update directly, just set up/reset a timer that will perform the update when it elapses.
146  self.delayedAutoUpdateTimer.start()
147 
148  def observeSegmentation(self, observationEnabled):
149  import vtkSegmentationCorePython as vtkSegmentationCore
150  segmentation = self.scriptedEffect.parameterSetNode().GetSegmentationNode().GetSegmentation()
151  if observationEnabled and self.observedSegmentation == segmentation:
152  return
153  if not observationEnabled and not self.observedSegmentation:
154  return
155  # Need to update the observer
156  # Remove old observer
157  if self.observedSegmentation:
158  for tag in self.segmentationNodeObserverTags:
159  self.observedSegmentation.RemoveObserver(tag)
161  self.observedSegmentation = None
162  # Add new observer
163  if observationEnabled and segmentation is not None:
164  self.observedSegmentation = segmentation
165  observedEvents = [
166  vtkSegmentationCore.vtkSegmentation.SegmentAdded,
167  vtkSegmentationCore.vtkSegmentation.SegmentRemoved,
168  vtkSegmentationCore.vtkSegmentation.SegmentModified,
169  vtkSegmentationCore.vtkSegmentation.MasterRepresentationModified ]
170  for eventId in observedEvents:
171  self.segmentationNodeObserverTags.append(self.observedSegmentation.AddObserver(eventId, self.onSegmentationModified))
172 
173  def getPreviewNode(self):
174  previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
175  if previewNode and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name:
176  # another effect owns this preview node
177  return None
178  return previewNode
179 
180  def updateGUIFromMRML(self):
181 
182  previewNode = self.getPreviewNode()
183 
184  self.cancelButton.setEnabled(previewNode is not None)
185  self.applyButton.setEnabled(previewNode is not None)
186 
187  self.previewOpacitySlider.setEnabled(previewNode is not None)
188  if previewNode:
189  wasBlocked = self.previewOpacitySlider.blockSignals(True)
190  self.previewOpacitySlider.value = self.getPreviewOpacity()
191  self.previewOpacitySlider.blockSignals(wasBlocked)
192  self.previewButton.text = "Update"
193  self.autoUpdateCheckBox.setEnabled(True)
194  self.observeSegmentation(self.autoUpdateCheckBox.isChecked())
195  else:
196  self.previewButton.text = "Initialize"
197  self.autoUpdateCheckBox.setEnabled(False)
198  self.delayedAutoUpdateTimer.stop()
199  self.observeSegmentation(False)
200 
201  autoUpdate = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("AutoUpdate") == 0 else qt.Qt.Checked
202  wasBlocked = self.autoUpdateCheckBox.blockSignals(True)
203  self.autoUpdateCheckBox.setCheckState(autoUpdate)
204  self.autoUpdateCheckBox.blockSignals(wasBlocked)
205 
206  def updateMRMLFromGUI(self):
207  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
208  previewNode = self.getPreviewNode()
209  if previewNode:
210  self.setPreviewOpacity(self.previewOpacitySlider.value)
211 
212  autoUpdate = 1 if self.autoUpdateCheckBox.isChecked() else 0
213  self.scriptedEffect.setParameter("AutoUpdate", autoUpdate)
214 
215  def onPreview(self):
216  slicer.util.showStatusMessage("Running {0} auto-complete...".format(self.scriptedEffect.name), 2000)
217  try:
218  # This can be a long operation - indicate it to the user
219  qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
220  self.preview()
221  finally:
222  qt.QApplication.restoreOverrideCursor()
223 
224  def reset(self):
225  self.delayedAutoUpdateTimer.stop()
226  self.observeSegmentation(False)
227  previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
228  if previewNode:
229  self.scriptedEffect.parameterSetNode().SetNodeReferenceID(ResultPreviewNodeReferenceRole, None)
230  slicer.mrmlScene.RemoveNode(previewNode)
231  self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", "")
232  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
233  segmentationNode.GetDisplayNode().SetOpacity(1.0)
234  self.mergedLabelmapGeometryImage = None
235  self.selectedSegmentIds = None
237  self.clippedMasterImageData = None
238  self.updateGUIFromMRML()
239 
240  def onCancel(self):
241  self.reset()
242 
243  def onApply(self):
244  self.delayedAutoUpdateTimer.stop()
245  self.observeSegmentation(False)
246 
247  import vtkSegmentationCorePython as vtkSegmentationCore
248  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
249  previewNode = self.getPreviewNode()
250 
251  self.scriptedEffect.saveStateForUndo()
252 
253  # Move segments from preview into current segmentation
254  segmentIDs = vtk.vtkStringArray()
255  previewNode.GetSegmentation().GetSegmentIDs(segmentIDs)
256  for index in xrange(segmentIDs.GetNumberOfValues()):
257  segmentID = segmentIDs.GetValue(index)
258  previewSegment = previewNode.GetSegmentation().GetSegment(segmentID)
259  previewSegmentLabelmap = previewSegment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
260  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(previewSegmentLabelmap, segmentationNode, segmentID)
261  previewNode.GetSegmentation().RemoveSegment(segmentID) # delete now to limit memory usage
262 
263  self.reset()
264 
265  def setPreviewOpacity(self, opacity):
266  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
267  segmentationNode.GetDisplayNode().SetOpacity(1.0-opacity)
268  previewNode = self.getPreviewNode()
269  if previewNode:
270  previewNode.GetDisplayNode().SetOpacity(opacity)
271 
272  # Make sure the GUI is up-to-date
273  wasBlocked = self.previewOpacitySlider.blockSignals(True)
274  self.previewOpacitySlider.value = opacity
275  self.previewOpacitySlider.blockSignals(wasBlocked)
276 
277  def getPreviewOpacity(self):
278  previewNode = self.getPreviewNode()
279  return previewNode.GetDisplayNode().GetOpacity() if previewNode else 0.6 # default opacity for preview
280 
281  def preview(self):
282  # Get master volume image data
283  import vtkSegmentationCorePython as vtkSegmentationCore
284  masterImageData = self.scriptedEffect.masterVolumeImageData()
285 
286  # Get segmentation
287  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
288 
289  previewNode = self.getPreviewNode()
290  if not previewNode or not self.mergedLabelmapGeometryImage \
292  self.reset()
293  # Compute merged labelmap extent (effective extent slightly expanded)
294  self.selectedSegmentIds = vtk.vtkStringArray()
295  segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(self.selectedSegmentIds)
296  if self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
297  logging.error("Auto-complete operation skipped: at least {0} visible segments are required".format(self.minimumNumberOfSegments))
298  return
299  if not self.mergedLabelmapGeometryImage:
300  self.mergedLabelmapGeometryImage = vtkSegmentationCore.vtkOrientedImageData()
301  commonGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
302  vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
303  if not commonGeometryString:
304  logging.info("Auto-complete operation skipped: all visible segments are empty")
305  return
306  vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(commonGeometryString, self.mergedLabelmapGeometryImage)
307 
308  masterImageExtent = masterImageData.GetExtent()
309  labelsEffectiveExtent = self.mergedLabelmapGeometryImage.GetExtent()
310  margin = [17, 17, 17]
311  labelsExpandedExtent = [
312  max(masterImageExtent[0], labelsEffectiveExtent[0]-margin[0]),
313  min(masterImageExtent[1], labelsEffectiveExtent[1]+margin[0]),
314  max(masterImageExtent[2], labelsEffectiveExtent[2]-margin[1]),
315  min(masterImageExtent[3], labelsEffectiveExtent[3]+margin[1]),
316  max(masterImageExtent[4], labelsEffectiveExtent[4]-margin[2]),
317  min(masterImageExtent[5], labelsEffectiveExtent[5]+margin[2]) ]
318  self.mergedLabelmapGeometryImage.SetExtent(labelsExpandedExtent)
319 
320  previewNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSegmentationNode')
321  previewNode.UnRegister(None)
322  previewNode = slicer.mrmlScene.AddNode(previewNode)
323  previewNode.CreateDefaultDisplayNodes()
324  previewNode.GetDisplayNode().SetVisibility2DOutline(False)
325  if segmentationNode.GetParentTransformNode():
326  previewNode.SetAndObserveTransformNodeID(segmentationNode.GetParentTransformNode().GetID())
327  self.scriptedEffect.parameterSetNode().SetNodeReferenceID(ResultPreviewNodeReferenceRole, previewNode.GetID())
328  self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", self.scriptedEffect.name)
329  self.setPreviewOpacity(0.6)
330 
332  self.clippedMasterImageData = vtkSegmentationCore.vtkOrientedImageData()
333  masterImageClipper = vtk.vtkImageConstantPad()
334  masterImageClipper.SetInputData(masterImageData)
335  masterImageClipper.SetOutputWholeExtent(self.mergedLabelmapGeometryImage.GetExtent())
336  masterImageClipper.Update()
337  self.clippedMasterImageData.ShallowCopy(masterImageClipper.GetOutput())
338  self.clippedMasterImageData.CopyDirections(self.mergedLabelmapGeometryImage)
339 
340  previewNode.SetName(segmentationNode.GetName()+" preview")
341 
342  mergedImage = vtkSegmentationCore.vtkOrientedImageData()
343  segmentationNode.GenerateMergedLabelmapForAllSegments(mergedImage,
344  vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.mergedLabelmapGeometryImage, self.selectedSegmentIds)
345 
346  outputLabelmap = vtkSegmentationCore.vtkOrientedImageData()
347 
348  self.computePreviewLabelmap(mergedImage, outputLabelmap)
349 
350  # Write output segmentation results in segments
351  for index in xrange(self.selectedSegmentIds.GetNumberOfValues()):
352  segmentID = self.selectedSegmentIds.GetValue(index)
353  segment = segmentationNode.GetSegmentation().GetSegment(segmentID)
354  # Disable save with scene?
355 
356  # Get only the label of the current segment from the output image
357  thresh = vtk.vtkImageThreshold()
358  thresh.ReplaceInOn()
359  thresh.ReplaceOutOn()
360  thresh.SetInValue(1)
361  thresh.SetOutValue(0)
362  labelValue = index + 1 # n-th segment label value = n + 1 (background label value is 0)
363  thresh.ThresholdBetween(labelValue, labelValue);
364  thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR)
365  thresh.SetInputData(outputLabelmap)
366  thresh.Update()
367 
368  # Write label to segment
369  newSegmentLabelmap = vtkSegmentationCore.vtkOrientedImageData()
370  newSegmentLabelmap.ShallowCopy(thresh.GetOutput())
371  newSegmentLabelmap.CopyDirections(mergedImage)
372  newSegment = previewNode.GetSegmentation().GetSegment(segmentID)
373  if not newSegment:
374  newSegment = vtkSegmentationCore.vtkSegment()
375  newSegment.SetName(segment.GetName())
376  color = segmentationNode.GetSegmentation().GetSegment(segmentID).GetColor()
377  newSegment.SetColor(color)
378  previewNode.GetSegmentation().AddSegment(newSegment, segmentID)
379  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(newSegmentLabelmap, previewNode, segmentID)
380 
381  self.updateGUIFromMRML()
382 
383 ResultPreviewNodeReferenceRole = "SegmentationResultPreview"