Slicer  4.11
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.items():
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  # Make sure the user wants to do the operation, even if the segment is not visible
59  confirmedEditingAllowed = self.scriptedEffect.confirmCurrentSegmentVisible()
60  if confirmedEditingAllowed == self.scriptedEffect.NotConfirmed or confirmedEditingAllowed == self.scriptedEffect.ConfirmedWithDialog:
61  # If user had to move the mouse to click on the popup, so we cannot continue with painting
62  # from the current mouse position. User will need to click again.
63  # The dialog is not displayed again for the same segment.
64  return abortEvent
65  pipeline.actionState = "drawing"
66  self.scriptedEffect.cursorOff(viewWidget)
67  xy = callerInteractor.GetEventPosition()
68  ras = self.xyToRas(xy, viewWidget)
69  pipeline.addPoint(ras)
70  abortEvent = True
71  elif eventId == vtk.vtkCommand.LeftButtonReleaseEvent:
72  pipeline.actionState = ""
73  self.scriptedEffect.cursorOn(viewWidget)
74  elif eventId == vtk.vtkCommand.RightButtonPressEvent:
75  sliceNode = viewWidget.sliceLogic().GetSliceNode()
76  pipeline.lastInsertSliceNodeMTime = sliceNode.GetMTime()
77  elif eventId == vtk.vtkCommand.RightButtonReleaseEvent:
78  sliceNode = viewWidget.sliceLogic().GetSliceNode()
79  if abs(pipeline.lastInsertSliceNodeMTime - sliceNode.GetMTime()) < 2:
80  pipeline.apply()
81  pipeline.actionState = None
82  elif eventId == vtk.vtkCommand.MouseMoveEvent:
83  if pipeline.actionState == "drawing":
84  xy = callerInteractor.GetEventPosition()
85  ras = self.xyToRas(xy, viewWidget)
86  pipeline.addPoint(ras)
87  abortEvent = True
88  elif eventId == vtk.vtkCommand.KeyPressEvent:
89  key = callerInteractor.GetKeySym()
90  if key == 'a' or key == 'Return':
91  pipeline.apply()
92  abortEvent = True
93  if key == 'x':
94  pipeline.deleteLastPoint()
95  abortEvent = True
96  else:
97  pass
98 
99  pipeline.positionActors()
100  return abortEvent
101 
102  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
103  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
104  # Get draw pipeline for current slice
105  pipeline = self.pipelineForWidget(viewWidget)
106  if pipeline is None:
107  logging.error('processViewNodeEvents: Invalid pipeline')
108  return
109 
110  # Make sure all points are on the current slice plane.
111  # If the SliceToRAS has been modified, then we're on a different plane
112  sliceLogic = viewWidget.sliceLogic()
113  lineMode = "solid"
114  currentSlice = sliceLogic.GetSliceOffset()
115  if pipeline.activeSlice:
116  offset = abs(currentSlice - pipeline.activeSlice)
117  if offset > 0.01:
118  lineMode = "dashed"
119  pipeline.setLineMode(lineMode)
120  pipeline.positionActors()
121 
122  def pipelineForWidget(self, sliceWidget):
123  if sliceWidget in self.drawPipelines:
124  return self.drawPipelines[sliceWidget]
125 
126  # Create pipeline if does not yet exist
127  pipeline = DrawPipeline(self.scriptedEffect, sliceWidget)
128 
129  # Add actor
130  renderer = self.scriptedEffect.renderer(sliceWidget)
131  if renderer is None:
132  logging.error("pipelineForWidget: Failed to get renderer!")
133  return None
134  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
135 
136  self.drawPipelines[sliceWidget] = pipeline
137  return pipeline
138 
139 #
140 # DrawPipeline
141 #
142 class DrawPipeline(object):
143  """ Visualization objects and pipeline for each slice view for drawing
144  """
145  def __init__(self, scriptedEffect, sliceWidget):
146  self.scriptedEffect = scriptedEffect
147  self.sliceWidget = sliceWidget
148  self.activeSlice = None
150  self.actionState = None
151 
152  self.xyPoints = vtk.vtkPoints()
153  self.rasPoints = vtk.vtkPoints()
154  self.polyData = self.createPolyData()
155 
156  self.mapper = vtk.vtkPolyDataMapper2D()
157  self.actor = vtk.vtkActor2D()
158  self.mapper.SetInputData(self.polyData)
159  self.actor.SetMapper(self.mapper)
160  actorProperty = self.actor.GetProperty()
161  actorProperty.SetColor(1,1,0)
162  actorProperty.SetLineWidth(1)
163 
164  def createPolyData(self):
165  # Make an empty single-polyline polydata
166  polyData = vtk.vtkPolyData()
167  polyData.SetPoints(self.xyPoints)
168  lines = vtk.vtkCellArray()
169  polyData.SetLines(lines)
170  return polyData
171 
172  def addPoint(self,ras):
173  # Add a world space point to the current outline
174 
175  # Store active slice when first point is added
176  sliceLogic = self.sliceWidget.sliceLogic()
177  currentSlice = sliceLogic.GetSliceOffset()
178  if not self.activeSlice:
179  self.activeSlice = currentSlice
180  self.setLineMode("solid")
181 
182  # Don't allow adding points on except on the active slice
183  # (where first point was laid down)
184  if self.activeSlice != currentSlice: return
185 
186  # Keep track of node state (in case of pan/zoom)
187  sliceNode = sliceLogic.GetSliceNode()
188  self.lastInsertSliceNodeMTime = sliceNode.GetMTime()
189 
190  p = self.rasPoints.InsertNextPoint(ras)
191  if p > 0:
192  idList = vtk.vtkIdList()
193  idList.InsertNextId(p-1)
194  idList.InsertNextId(p)
195  self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
196 
197  def setLineMode(self,mode="solid"):
198  actorProperty = self.actor.GetProperty()
199  if mode == "solid":
200  actorProperty.SetLineStipplePattern(0xffff)
201  elif mode == "dashed":
202  actorProperty.SetLineStipplePattern(0xff00)
203 
204  def positionActors(self):
205  # Update draw feedback to follow slice node
206  sliceLogic = self.sliceWidget.sliceLogic()
207  sliceNode = sliceLogic.GetSliceNode()
208  rasToXY = vtk.vtkTransform()
209  rasToXY.SetMatrix(sliceNode.GetXYToRAS())
210  rasToXY.Inverse()
211  self.xyPoints.Reset()
212  rasToXY.TransformPoints(self.rasPoints, self.xyPoints)
213  self.polyData.Modified()
214  self.sliceWidget.sliceView().scheduleRender()
215 
216  def apply(self):
217  lines = self.polyData.GetLines()
218  lineExists = lines.GetNumberOfCells() > 0
219  if lineExists:
220  # Close the polyline back to the first point
221  idList = vtk.vtkIdList()
222  idList.InsertNextId(self.polyData.GetNumberOfPoints()-1)
223  idList.InsertNextId(0)
224  self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
225 
226  # Get modifier labelmap
227  import vtkSegmentationCorePython as vtkSegmentationCore
228  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
229 
230  # Apply poly data on modifier labelmap
231  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
232  self.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)
233 
234  self.resetPolyData()
235  if lineExists:
236  self.scriptedEffect.saveStateForUndo()
237  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
238 
239  def resetPolyData(self):
240  # Return the polyline to initial state with no points
241  lines = self.polyData.GetLines()
242  lines.Initialize()
243  self.xyPoints.Reset()
244  self.rasPoints.Reset()
245  self.activeSlice = None
246 
247  def deleteLastPoint(self):
248  # Unwind through addPoint list back to empty polydata
249  pcount = self.rasPoints.GetNumberOfPoints()
250  if pcount <= 0:
251  return
252 
253  pcount = pcount - 1
254  self.rasPoints.SetNumberOfPoints(pcount)
255 
256  cellCount = self.polyData.GetNumberOfCells()
257  if cellCount > 0:
258  self.polyData.DeleteCell(cellCount - 1)
259  self.polyData.RemoveDeletedCells()
260 
261  self.positionActors()
def __init__(self, scriptedEffect, sliceWidget)
def processViewNodeEvents(self, callerViewNode, eventId, viewWidget)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)