Slicer  4.8
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorDrawEffect.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 
7  """ DrawEffect is a LabelEffect implementing the interactive draw
8  tool in the segment editor
9  """
10 
11  def __init__(self, scriptedEffect):
12  scriptedEffect.name = 'Draw'
13  self.drawPipelines = {}
14  AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
15 
16  def clone(self):
17  import qSlicerSegmentationsEditorEffectsPythonQt as effects
18  clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
19  clonedEffect.setPythonSource(__file__.replace('\\','/'))
20  return clonedEffect
21 
22  def icon(self):
23  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Draw.png')
24  if os.path.exists(iconPath):
25  return qt.QIcon(iconPath)
26  return qt.QIcon()
27 
28  def helpText(self):
29  return """<html>Draw segment outline in slice viewers<br>.
30 <p><ul style="margin: 0">
31 <li><b>Left-click:</b> add point.</li>
32 <li><b>Left-button drag-and-drop:</b> add multiple points.</li>
33 <li><b>x:</b> delete last point.</li>
34 <li><b>Right-click</b> or <b>a</b> or <b>enter:</b> apply outline.</li>
35 </ul><p></html>"""
36 
37  def deactivate(self):
38  # Clear draw pipelines
39  for sliceWidget, pipeline in self.drawPipelines.iteritems():
40  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
41  self.drawPipelines = {}
42 
43  def setupOptionsFrame(self):
44  pass
45 
46  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
47  abortEvent = False
48 
49  # Only allow for slice views
50  if viewWidget.className() != "qMRMLSliceWidget":
51  return abortEvent
52  # Get draw pipeline for current slice
53  pipeline = self.pipelineForWidget(viewWidget)
54  if pipeline is None:
55  return abortEvent
56 
57  if eventId == vtk.vtkCommand.LeftButtonPressEvent:
58  pipeline.actionState = "drawing"
59  self.scriptedEffect.cursorOff(viewWidget)
60  xy = callerInteractor.GetEventPosition()
61  ras = self.xyToRas(xy, viewWidget)
62  pipeline.addPoint(ras)
63  abortEvent = True
64  elif eventId == vtk.vtkCommand.LeftButtonReleaseEvent:
65  pipeline.actionState = ""
66  self.scriptedEffect.cursorOn(viewWidget)
67  elif eventId == vtk.vtkCommand.RightButtonPressEvent:
68  sliceNode = viewWidget.sliceLogic().GetSliceNode()
69  pipeline.lastInsertSliceNodeMTime = sliceNode.GetMTime()
70  elif eventId == vtk.vtkCommand.RightButtonReleaseEvent:
71  sliceNode = viewWidget.sliceLogic().GetSliceNode()
72  if abs(pipeline.lastInsertSliceNodeMTime - sliceNode.GetMTime()) < 2:
73  pipeline.apply()
74  pipeline.actionState = None
75  elif eventId == vtk.vtkCommand.MouseMoveEvent:
76  if pipeline.actionState == "drawing":
77  xy = callerInteractor.GetEventPosition()
78  ras = self.xyToRas(xy, viewWidget)
79  pipeline.addPoint(ras)
80  abortEvent = True
81  elif eventId == vtk.vtkCommand.KeyPressEvent:
82  key = callerInteractor.GetKeySym()
83  if key == 'a' or key == 'Return':
84  pipeline.apply()
85  abortEvent = True
86  if key == 'x':
87  pipeline.deleteLastPoint()
88  abortEvent = True
89  else:
90  pass
91 
92  pipeline.positionActors()
93  return abortEvent
94 
95  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
96  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
97  # Get draw pipeline for current slice
98  pipeline = self.pipelineForWidget(viewWidget)
99  if pipeline is None:
100  logging.error('processViewNodeEvents: Invalid pipeline')
101  return
102 
103  # Make sure all points are on the current slice plane.
104  # If the SliceToRAS has been modified, then we're on a different plane
105  sliceLogic = viewWidget.sliceLogic()
106  lineMode = "solid"
107  currentSlice = sliceLogic.GetSliceOffset()
108  if pipeline.activeSlice:
109  offset = abs(currentSlice - pipeline.activeSlice)
110  if offset > 0.01:
111  lineMode = "dashed"
112  pipeline.setLineMode(lineMode)
113  pipeline.positionActors()
114 
115  def pipelineForWidget(self, sliceWidget):
116  if sliceWidget in self.drawPipelines:
117  return self.drawPipelines[sliceWidget]
118 
119  # Create pipeline if does not yet exist
120  pipeline = DrawPipeline(self.scriptedEffect, sliceWidget)
121 
122  # Add actor
123  renderer = self.scriptedEffect.renderer(sliceWidget)
124  if renderer is None:
125  logging.error("pipelineForWidget: Failed to get renderer!")
126  return None
127  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
128 
129  self.drawPipelines[sliceWidget] = pipeline
130  return pipeline
131 
132 #
133 # DrawPipeline
134 #
136  """ Visualization objects and pipeline for each slice view for drawing
137  """
138  def __init__(self, scriptedEffect, sliceWidget):
139  self.scriptedEffect = scriptedEffect
140  self.sliceWidget = sliceWidget
141  self.activeSlice = None
143  self.actionState = None
144 
145  self.xyPoints = vtk.vtkPoints()
146  self.rasPoints = vtk.vtkPoints()
147  self.polyData = self.createPolyData()
148 
149  self.mapper = vtk.vtkPolyDataMapper2D()
150  self.actor = vtk.vtkActor2D()
151  self.mapper.SetInputData(self.polyData)
152  self.actor.SetMapper(self.mapper)
153  actorProperty = self.actor.GetProperty()
154  actorProperty.SetColor(1,1,0)
155  actorProperty.SetLineWidth(1)
156 
157  def createPolyData(self):
158  # Make an empty single-polyline polydata
159  polyData = vtk.vtkPolyData()
160  polyData.SetPoints(self.xyPoints)
161 
162  lines = vtk.vtkCellArray()
163  polyData.SetLines(lines)
164  idArray = lines.GetData()
165  idArray.Reset()
166  idArray.InsertNextTuple1(0)
167 
168  polygons = vtk.vtkCellArray()
169  polyData.SetPolys(polygons)
170  idArray = polygons.GetData()
171  idArray.Reset()
172  idArray.InsertNextTuple1(0)
173 
174  return polyData
175 
176  def addPoint(self,ras):
177  # Add a world space point to the current outline
178 
179  # Store active slice when first point is added
180  sliceLogic = self.sliceWidget.sliceLogic()
181  currentSlice = sliceLogic.GetSliceOffset()
182  if not self.activeSlice:
183  self.activeSlice = currentSlice
184  self.setLineMode("solid")
185 
186  # Don't allow adding points on except on the active slice
187  # (where first point was laid down)
188  if self.activeSlice != currentSlice: return
189 
190  # Keep track of node state (in case of pan/zoom)
191  sliceNode = sliceLogic.GetSliceNode()
192  self.lastInsertSliceNodeMTime = sliceNode.GetMTime()
193 
194  p = self.rasPoints.InsertNextPoint(ras)
195  lines = self.polyData.GetLines()
196  idArray = lines.GetData()
197  idArray.InsertNextTuple1(p)
198  idArray.SetTuple1(0, idArray.GetNumberOfTuples()-1)
199  lines.SetNumberOfCells(1)
200 
201  def setLineMode(self,mode="solid"):
202  actorProperty = self.actor.GetProperty()
203  if mode == "solid":
204  actorProperty.SetLineStipplePattern(0xffff)
205  elif mode == "dashed":
206  actorProperty.SetLineStipplePattern(0xff00)
207 
208  def positionActors(self):
209  # Update draw feedback to follow slice node
210  sliceLogic = self.sliceWidget.sliceLogic()
211  sliceNode = sliceLogic.GetSliceNode()
212  rasToXY = vtk.vtkTransform()
213  rasToXY.SetMatrix(sliceNode.GetXYToRAS())
214  rasToXY.Inverse()
215  self.xyPoints.Reset()
216  rasToXY.TransformPoints(self.rasPoints, self.xyPoints)
217  self.polyData.Modified()
218  self.sliceWidget.sliceView().scheduleRender()
219 
220  def apply(self):
221  lines = self.polyData.GetLines()
222  if lines.GetNumberOfCells() == 0: return
223 
224  # Close the polyline back to the first point
225  idArray = lines.GetData()
226  p = idArray.GetTuple1(1)
227  idArray.InsertNextTuple1(p)
228  idArray.SetTuple1(0, idArray.GetNumberOfTuples() - 1)
229 
230  self.scriptedEffect.saveStateForUndo()
231 
232  # Get modifier labelmap
233  import vtkSegmentationCorePython as vtkSegmentationCore
234  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
235 
236  # Apply poly data on modifier labelmap
237  self.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget)
238  self.resetPolyData()
239  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
240 
241  def resetPolyData(self):
242  # Return the polyline to initial state with no points
243  lines = self.polyData.GetLines()
244  idArray = lines.GetData()
245  idArray.Reset()
246  idArray.InsertNextTuple1(0)
247  self.xyPoints.Reset()
248  self.rasPoints.Reset()
249  lines.SetNumberOfCells(0)
250  self.activeSlice = None
251 
252  def deleteLastPoint(self):
253  # Unwind through addPoint list back to empty polydata
254  pcount = self.rasPoints.GetNumberOfPoints()
255  if pcount <= 0: return
256 
257  pcount = pcount - 1
258  self.rasPoints.SetNumberOfPoints(pcount)
259 
260  lines = self.polyData.GetLines()
261  idArray = lines.GetData()
262  idArray.SetTuple1(0, pcount)
263  idArray.SetNumberOfTuples(pcount+1)
264 
265  self.positionActors()
def __init__(self, scriptedEffect, sliceWidget)
def processViewNodeEvents(self, callerViewNode, eventId, viewWidget)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)