Slicer  4.8
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
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)