Slicer  5.2
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
SegmentEditorLevelTracingEffect.py
Go to the documentation of this file.
1 import logging
2 import os
3 
4 import qt
5 import vtk
6 import vtkITK
7 
8 import slicer
9 
10 from SegmentEditorEffects import *
11 
12 
14  """ LevelTracingEffect is a LabelEffect implementing level tracing fill
15  using intensity-based isolines
16  """
17 
18  def __init__(self, scriptedEffect):
19  scriptedEffect.name = 'Level tracing'
20  AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
21 
22  # Effect-specific members
24  self.lastXY = None
25 
26  def clone(self):
27  import qSlicerSegmentationsEditorEffectsPythonQt as effects
28  clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
29  clonedEffect.setPythonSource(__file__.replace('\\', '/'))
30  return clonedEffect
31 
32  def icon(self):
33  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/LevelTracing.png')
34  if os.path.exists(iconPath):
35  return qt.QIcon(iconPath)
36  return qt.QIcon()
37 
38  def helpText(self):
39  return """<html>Add uniform intensity region to selected segment<br>.
40 <p><ul style="margin: 0">
41 <li><b>Mouse move:</b> current background voxel is used to find a closed path that
42 follows the same intensity value back to the starting point within the current slice.</li>
43 <li><b>Left-click:</b> add the previewed region to the current segment.</li>
44 </ul><p></html>"""
45 
46  def setupOptionsFrame(self):
47  self.sliceRotatedErrorLabel = qt.QLabel()
48  # This widget displays an error message if the slice view is not aligned
49  # with the segmentation's axes.
50  self.scriptedEffect.addOptionsWidget(self.sliceRotatedErrorLabel)
51 
52  def activate(self):
53  self.sliceRotatedErrorLabel.text = ""
54 
55  def deactivate(self):
56  # Clear draw pipelines
57  for sliceWidget, pipeline in self.levelTracingPipelines.items():
58  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
59  self.levelTracingPipelines = {}
60  self.lastXY = None
61 
62  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
63  abortEvent = False
64 
65  # Only allow for slice views
66  if viewWidget.className() != "qMRMLSliceWidget":
67  return abortEvent
68  # Get draw pipeline for current slice
69  pipeline = self.pipelineForWidget(viewWidget)
70  if pipeline is None:
71  return abortEvent
72 
73  anyModifierKeyPressed = callerInteractor.GetShiftKey() or callerInteractor.GetControlKey() or callerInteractor.GetAltKey()
74 
75  if eventId == vtk.vtkCommand.LeftButtonPressEvent and not anyModifierKeyPressed:
76  # Make sure the user wants to do the operation, even if the segment is not visible
77  if not self.scriptedEffect.confirmCurrentSegmentVisible():
78  return abortEvent
79 
80  self.scriptedEffect.saveStateForUndo()
81 
82  # Get modifier labelmap
83  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
84 
85  # Apply poly data on modifier labelmap
86  pipeline.appendPolyMask(modifierLabelmap)
87  # TODO: it would be nice to reduce extent
88  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
89  abortEvent = True
90  elif eventId == vtk.vtkCommand.MouseMoveEvent:
91  if pipeline.actionState == '':
92  xy = callerInteractor.GetEventPosition()
93  if pipeline.preview(xy):
94  self.sliceRotatedErrorLabel.text = ""
95  else:
96  self.sliceRotatedErrorLabel.text = ("<b><font color=\"red\">"
97  + "Slice view is not aligned with segmentation axis.<br>To use this effect, click the 'Slice views orientation' warning button."
98  + "</font></b>")
99  abortEvent = True
100  self.lastXY = xy
101  elif eventId == vtk.vtkCommand.EnterEvent:
102  self.sliceRotatedErrorLabel.text = ""
103  pipeline.actor.VisibilityOn()
104  elif eventId == vtk.vtkCommand.LeaveEvent:
105  self.sliceRotatedErrorLabel.text = ""
106  pipeline.actor.VisibilityOff()
107  self.lastXY = None
108 
109  return abortEvent
110 
111  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
112  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
113  # Get draw pipeline for current slice
114  pipeline = self.pipelineForWidget(viewWidget)
115  if pipeline is None:
116  logging.error('processViewNodeEvents: Invalid pipeline')
117  return
118 
119  # Update the preview to the new slice
120  if pipeline.actionState == '' and self.lastXY:
121  pipeline.preview(self.lastXY)
122 
123  def pipelineForWidget(self, sliceWidget):
124  if sliceWidget in self.levelTracingPipelines:
125  return self.levelTracingPipelines[sliceWidget]
126 
127  # Create pipeline if does not yet exist
128  pipeline = LevelTracingPipeline(self, sliceWidget)
129 
130  # Add actor
131  renderer = self.scriptedEffect.renderer(sliceWidget)
132  if renderer is None:
133  logging.error("setupPreviewDisplay: Failed to get renderer!")
134  return None
135  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
136 
137  self.levelTracingPipelines[sliceWidget] = pipeline
138  return pipeline
139 
140 
141 #
142 # LevelTracingPipeline
143 #
145  """ Visualization objects and pipeline for each slice view for level tracing
146  """
147 
148  def __init__(self, effect, sliceWidget):
149  self.effect = effect
150  self.sliceWidget = sliceWidget
151  self.actionState = ''
152 
153  self.xyPoints = vtk.vtkPoints()
154  self.rasPoints = vtk.vtkPoints()
155  self.polyData = vtk.vtkPolyData()
156 
157  self.tracingFilter = vtkITK.vtkITKLevelTracingImageFilter()
158  self.ijkToXY = vtk.vtkGeneralTransform()
159 
160  self.mapper = vtk.vtkPolyDataMapper2D()
161  self.actor = vtk.vtkActor2D()
162  actorProperty = self.actor.GetProperty()
163  actorProperty.SetColor(107 / 255., 190 / 255., 99 / 255.)
164  actorProperty.SetLineWidth(1)
165  self.mapper.SetInputData(self.polyData)
166  self.actor.SetMapper(self.mapper)
167  actorProperty = self.actor.GetProperty()
168  actorProperty.SetColor(1, 1, 0)
169  actorProperty.SetLineWidth(1)
170 
171  def preview(self, xy):
172  """Calculate the current level trace view if the mouse is inside the volume extent
173  Returns False if slice views are rotated.
174  """
175 
176  # Get source volume image data
177  sourceImageData = self.effect.scriptedEffect.sourceVolumeImageData()
178 
179  segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
180  parentTransformNode = None
181  if segmentationNode:
182  parentTransformNode = segmentationNode.GetParentTransformNode()
183 
184  self.xyPoints.Reset()
185  ijk = self.effect.xyToIjk(xy, self.sliceWidget, sourceImageData, parentTransformNode)
186  dimensions = sourceImageData.GetDimensions()
187 
188  self.tracingFilter.SetInputData(sourceImageData)
189  self.tracingFilter.SetSeed(ijk)
190 
191  # Select the plane corresponding to current slice orientation
192  # for the input volume
193  sliceNode = self.effect.scriptedEffect.viewNode(self.sliceWidget)
194  offset = max(sliceNode.GetDimensions())
195 
196  i0, j0, k0 = self.effect.xyToIjk((0, 0), self.sliceWidget, sourceImageData, parentTransformNode)
197  i1, j1, k1 = self.effect.xyToIjk((offset, offset), self.sliceWidget, sourceImageData, parentTransformNode)
198  if i0 == i1:
199  self.tracingFilter.SetPlaneToJK()
200  elif j0 == j1:
201  self.tracingFilter.SetPlaneToIK()
202  elif k0 == k1:
203  self.tracingFilter.SetPlaneToIJ()
204  else:
205  self.polyData.Reset()
206  self.sliceWidget.sliceView().scheduleRender()
207  return False
208 
209  self.tracingFilter.Update()
210  polyData = self.tracingFilter.GetOutput()
211 
212  # Get source volume IJK to slice XY transform
213  xyToRas = sliceNode.GetXYToRAS()
214  rasToIjk = vtk.vtkMatrix4x4()
215  sourceImageData.GetImageToWorldMatrix(rasToIjk)
216  rasToIjk.Invert()
217  xyToIjk = vtk.vtkGeneralTransform()
218  xyToIjk.PostMultiply()
219  xyToIjk.Concatenate(xyToRas)
220  if parentTransformNode:
221  worldToSegmentation = vtk.vtkMatrix4x4()
222  parentTransformNode.GetMatrixTransformFromWorld(worldToSegmentation)
223  xyToIjk.Concatenate(worldToSegmentation)
224  xyToIjk.Concatenate(rasToIjk)
225  ijkToXy = xyToIjk.GetInverse()
226  if polyData.GetPoints():
227  ijkToXy.TransformPoints(polyData.GetPoints(), self.xyPoints)
228  self.polyData.DeepCopy(polyData)
229  self.polyData.GetPoints().DeepCopy(self.xyPoints)
230  else:
231  self.polyData.Reset()
232  self.sliceWidget.sliceView().scheduleRender()
233  return True
234 
235  def appendPolyMask(self, modifierLabelmap):
236  lines = self.polyData.GetLines()
237  if lines.GetNumberOfCells() == 0:
238  return
239 
240  # Apply poly data on modifier labelmap
241  segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
242  self.effect.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)