Slicer 5.4
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
SegmentEditorDrawEffect.py
Go to the documentation of this file.
1import logging
2import os
3
4import qt
5import vtk
6
7import slicer
8
9from SegmentEditorEffects import *
10
11
12class SegmentEditorDrawEffect(AbstractScriptedSegmentEditorLabelEffect):
13 """ DrawEffect is a LabelEffect implementing the interactive draw
14 tool in the segment editor
15 """
16
17 def __init__(self, scriptedEffect):
18 scriptedEffect.name = 'Draw'
19 self.drawPipelines = {}
20 AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
21
22 def clone(self):
23 import qSlicerSegmentationsEditorEffectsPythonQt as effects
24 clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
25 clonedEffect.setPythonSource(__file__.replace('\\', '/'))
26 return clonedEffect
27
28 def icon(self):
29 iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Draw.png')
30 if os.path.exists(iconPath):
31 return qt.QIcon(iconPath)
32 return qt.QIcon()
33
34 def helpText(self):
35 return """<html>Draw segment outline in slice viewers<br>.
36<p><ul style="margin: 0">
37<li><b>Left-click:</b> add point.</li>
38<li><b>Left-button drag-and-drop:</b> add multiple points.</li>
39<li><b>x:</b> delete last point.</li>
40<li><b>Double-left-click</b> or <b>right-click</b> or <b>a</b> or <b>enter</b>: apply outline.</li>
41</ul><p></html>"""
42
43 def deactivate(self):
44 # Clear draw pipelines
45 for sliceWidget, pipeline in self.drawPipelines.items():
46 self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
47 self.drawPipelines = {}
48
50 pass
51
52 def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
53 abortEvent = False
54
55 # Only allow for slice views
56 if viewWidget.className() != "qMRMLSliceWidget":
57 return abortEvent
58 # Get draw pipeline for current slice
59 pipeline = self.pipelineForWidget(viewWidget)
60 if pipeline is None:
61 return abortEvent
62
63 anyModifierKeyPressed = callerInteractor.GetShiftKey() or callerInteractor.GetControlKey() or callerInteractor.GetAltKey()
64
65 if eventId == vtk.vtkCommand.LeftButtonPressEvent and not anyModifierKeyPressed:
66
67 # Make sure the user wants to do the operation, even if the segment is not visible
68 confirmedEditingAllowed = self.scriptedEffect.confirmCurrentSegmentVisible()
69 if confirmedEditingAllowed == self.scriptedEffect.NotConfirmed or confirmedEditingAllowed == self.scriptedEffect.ConfirmedWithDialog:
70 # ConfirmedWithDialog cancels the operation because the user had to move the mouse to click on the popup,
71 # which would interfere with the drawn shape. The dialog is not displayed again for the same segment.
72
73 # The event has to be aborted, because otherwise there would be a LeftButtonPressEvent without a matching
74 # LeftButtonReleaseEvent (as the popup window received the release button event).
75 abortEvent = True
76
77 return abortEvent
78
79 pipeline.actionState = "drawing"
80 self.scriptedEffect.cursorOff(viewWidget)
81 xy = callerInteractor.GetEventPosition()
82 ras = self.xyToRas(xy, viewWidget)
83 pipeline.addPoint(ras)
84 abortEvent = True
85 elif eventId == vtk.vtkCommand.LeftButtonReleaseEvent:
86 if pipeline.actionState == "drawing":
87 pipeline.actionState = "moving"
88 self.scriptedEffect.cursorOn(viewWidget)
89 abortEvent = True
90 elif eventId == vtk.vtkCommand.RightButtonPressEvent and not anyModifierKeyPressed:
91 pipeline.actionState = "finishing"
92 sliceNode = viewWidget.sliceLogic().GetSliceNode()
93 pipeline.lastInsertSliceNodeMTime = sliceNode.GetMTime()
94 abortEvent = True
95 elif (eventId == vtk.vtkCommand.RightButtonReleaseEvent and pipeline.actionState == "finishing") or (eventId == vtk.vtkCommand.LeftButtonDoubleClickEvent and not anyModifierKeyPressed):
96 abortEvent = (pipeline.rasPoints.GetNumberOfPoints() > 1)
97 sliceNode = viewWidget.sliceLogic().GetSliceNode()
98 if abs(pipeline.lastInsertSliceNodeMTime - sliceNode.GetMTime()) < 2:
99 pipeline.apply()
100 pipeline.actionState = ""
101 elif eventId == vtk.vtkCommand.MouseMoveEvent:
102 if pipeline.actionState == "drawing":
103 xy = callerInteractor.GetEventPosition()
104 ras = self.xyToRas(xy, viewWidget)
105 pipeline.addPoint(ras)
106 abortEvent = True
107 elif eventId == vtk.vtkCommand.KeyPressEvent:
108 key = callerInteractor.GetKeySym()
109 if key == 'a' or key == 'Return':
110 pipeline.apply()
111 abortEvent = True
112 if key == 'x':
113 pipeline.deleteLastPoint()
114 abortEvent = True
115 else:
116 pass
117
118 pipeline.positionActors()
119 return abortEvent
120
121 def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
122 if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
123 # Get draw pipeline for current slice
124 pipeline = self.pipelineForWidget(viewWidget)
125 if pipeline is None:
126 logging.error('processViewNodeEvents: Invalid pipeline')
127 return
128
129 # Make sure all points are on the current slice plane.
130 # If the SliceToRAS has been modified, then we're on a different plane
131 sliceLogic = viewWidget.sliceLogic()
132 lineMode = "solid"
133 currentSliceOffset = sliceLogic.GetSliceOffset()
134 if pipeline.activeSliceOffset:
135 offset = abs(currentSliceOffset - pipeline.activeSliceOffset)
136 if offset > 0.01:
137 lineMode = "dashed"
138 pipeline.setLineMode(lineMode)
139 pipeline.positionActors()
140
141 def pipelineForWidget(self, sliceWidget):
142 if sliceWidget in self.drawPipelines:
143 return self.drawPipelines[sliceWidget]
144
145 # Create pipeline if does not yet exist
146 pipeline = DrawPipeline(self.scriptedEffect, sliceWidget)
147
148 # Add actor
149 renderer = self.scriptedEffect.renderer(sliceWidget)
150 if renderer is None:
151 logging.error("pipelineForWidget: Failed to get renderer!")
152 return None
153 self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
154
155 self.drawPipelines[sliceWidget] = pipeline
156 return pipeline
157
158
159#
160# DrawPipeline
161#
163 """ Visualization objects and pipeline for each slice view for drawing
164 """
165
166 def __init__(self, scriptedEffect, sliceWidget):
167 self.scriptedEffect = scriptedEffect
168 self.sliceWidget = sliceWidget
171 self.actionState = None
172
173 self.xyPoints = vtk.vtkPoints()
174 self.rasPoints = vtk.vtkPoints()
176
177 self.mapper = vtk.vtkPolyDataMapper2D()
178 self.actor = vtk.vtkTexturedActor2D()
179 self.mapper.SetInputData(self.polyData)
180 self.actor.SetMapper(self.mapper)
181 actorProperty = self.actor.GetProperty()
182 actorProperty.SetColor(1, 1, 0)
183 actorProperty.SetLineWidth(1)
184
185 self.createStippleTexture(0xAAAA, 8)
186
187 def createStippleTexture(self, lineStipplePattern, lineStippleRepeat):
188 self.tcoords = vtk.vtkDoubleArray()
189 self.texture = vtk.vtkTexture()
190
191 # Create texture
192 dimension = 16 * lineStippleRepeat
193
194 image = vtk.vtkImageData()
195 image.SetDimensions(dimension, 1, 1)
196 image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 4)
197 image.SetExtent(0, dimension - 1, 0, 0, 0, 0)
198 on = 255
199 off = 0
200 i_dim = 0
201 while i_dim < dimension:
202 for i in range(0, 16):
203 mask = (1 << i)
204 bit = (lineStipplePattern & mask) >> i
205 value = bit
206 if value == 0:
207 for j in range(0, lineStippleRepeat):
208 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, on)
209 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, on)
210 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, on)
211 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, off)
212 i_dim += 1
213 else:
214 for j in range(0, lineStippleRepeat):
215 image.SetScalarComponentFromFloat(i_dim, 0, 0, 0, on)
216 image.SetScalarComponentFromFloat(i_dim, 0, 0, 1, on)
217 image.SetScalarComponentFromFloat(i_dim, 0, 0, 2, on)
218 image.SetScalarComponentFromFloat(i_dim, 0, 0, 3, on)
219 i_dim += 1
220 self.texture.SetInputData(image)
221 self.texture.InterpolateOff()
222 self.texture.RepeatOn()
223
224 def createPolyData(self):
225 # Make an empty single-polyline polydata
226 polyData = vtk.vtkPolyData()
227 polyData.SetPoints(self.xyPoints)
228 lines = vtk.vtkCellArray()
229 polyData.SetLines(lines)
230 return polyData
231
232 def addPoint(self, ras):
233 # Add a world space point to the current outline
234
235 # Store active slice when first point is added
236 sliceLogic = self.sliceWidget.sliceLogic()
237 currentSliceOffset = sliceLogic.GetSliceOffset()
238 if not self.activeSliceOffset:
239 self.activeSliceOffset = currentSliceOffset
240 self.setLineMode("solid")
241
242 # Don't allow adding points on except on the active slice
243 # (where first point was laid down)
244 if self.activeSliceOffset != currentSliceOffset:
245 return
246
247 # Keep track of node state (in case of pan/zoom)
248 sliceNode = sliceLogic.GetSliceNode()
249 self.lastInsertSliceNodeMTime = sliceNode.GetMTime()
250
251 p = self.rasPoints.InsertNextPoint(ras)
252 if p > 0:
253 idList = vtk.vtkIdList()
254 idList.InsertNextId(p - 1)
255 idList.InsertNextId(p)
256 self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
257
258 def setLineMode(self, mode="solid"):
259 actorProperty = self.actor.GetProperty()
260 if mode == "solid":
261 self.polyData.GetPointData().SetTCoords(None)
262 self.actor.SetTexture(None)
263 elif mode == "dashed":
264 # Create texture coordinates
265 self.tcoords.SetNumberOfComponents(1)
266 self.tcoords.SetNumberOfTuples(self.polyData.GetNumberOfPoints())
267 for i in range(0, self.polyData.GetNumberOfPoints()):
268 value = i * 0.5
269 self.tcoords.SetTypedTuple(i, [value])
270 self.polyData.GetPointData().SetTCoords(self.tcoords)
271 self.actor.SetTexture(self.texture)
272
273 def positionActors(self):
274 # Update draw feedback to follow slice node
275 sliceLogic = self.sliceWidget.sliceLogic()
276 sliceNode = sliceLogic.GetSliceNode()
277 rasToXY = vtk.vtkTransform()
278 rasToXY.SetMatrix(sliceNode.GetXYToRAS())
279 rasToXY.Inverse()
280 self.xyPoints.Reset()
281 rasToXY.TransformPoints(self.rasPoints, self.xyPoints)
282 self.polyData.Modified()
283 self.sliceWidget.sliceView().scheduleRender()
284
285 def apply(self):
286 lines = self.polyData.GetLines()
287 lineExists = lines.GetNumberOfCells() > 0
288 if lineExists:
289 # Close the polyline back to the first point
290 idList = vtk.vtkIdList()
291 idList.InsertNextId(self.polyData.GetNumberOfPoints() - 1)
292 idList.InsertNextId(0)
293 self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
294
295 # Get modifier labelmap
296 modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
297
298 # Apply poly data on modifier labelmap
299 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
300 self.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)
301
302 self.resetPolyData()
303 if lineExists:
304 self.scriptedEffect.saveStateForUndo()
305 self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
306
307 def resetPolyData(self):
308 # Return the polyline to initial state with no points
309 lines = self.polyData.GetLines()
310 lines.Initialize()
311 self.xyPoints.Reset()
312 self.rasPoints.Reset()
313 self.activeSliceOffset = None
314
316 # Unwind through addPoint list back to empty polydata
317 pcount = self.rasPoints.GetNumberOfPoints()
318 if pcount <= 0:
319 return
320
321 pcount = pcount - 1
322 self.rasPoints.SetNumberOfPoints(pcount)
323
324 cellCount = self.polyData.GetNumberOfCells()
325 if cellCount > 0:
326 self.polyData.DeleteCell(cellCount - 1)
327 self.polyData.RemoveDeletedCells()
328
329 self.positionActors()
__init__(self, scriptedEffect, sliceWidget)
createStippleTexture(self, lineStipplePattern, lineStippleRepeat)
processInteractionEvents(self, callerInteractor, eventId, viewWidget)
processViewNodeEvents(self, callerViewNode, eventId, viewWidget)