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