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