Commit 1c123311cf31987f045159fac792421054050feb
Committed by
GitHub
1 parent
1a5b94be
Exists in
master
Create Styles 3D (#245)
* Added Zoom interactor styles * Added Zoom by region interactor style * Added WWWL interactor style 3D * Added Panning style * Added Spin style * Added Linear measure style * Added Angular measure style and cleanups in linear and angular styels * Added insert seed style * Remove old event methods from volume viewer * Showing WW&WL text in Viewer Volume * Remove old SetInteractorStyle from volume viewer * Removed VOLUME_STYLES from constants
Showing
5 changed files
with
496 additions
and
228 deletions
Show diff stats
invesalius/constants.py
... | ... | @@ -582,11 +582,6 @@ SLICE_STYLES.append(STATE_MEASURE_DENSITY) |
582 | 582 | SLICE_STYLES.append(STATE_MEASURE_DENSITY_ELLIPSE) |
583 | 583 | SLICE_STYLES.append(STATE_MEASURE_DENSITY_POLYGON) |
584 | 584 | |
585 | -VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, | |
586 | - STATE_MEASURE_ANGLE] | |
587 | -VOLUME_STYLES.append(STATE_DEFAULT) | |
588 | - | |
589 | - | |
590 | 585 | STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, |
591 | 586 | SLICE_STATE_WATERSHED: 1, |
592 | 587 | SLICE_STATE_MASK_FFILL: 2, | ... | ... |
invesalius/control.py
... | ... | @@ -174,7 +174,7 @@ class Controller(): |
174 | 174 | #Publisher.sendMessage("Enable state project", state=False) |
175 | 175 | Publisher.sendMessage('Set project name') |
176 | 176 | Publisher.sendMessage("Stop Config Recording") |
177 | - Publisher.sendMessage("Set slice interaction style", style=const.STATE_DEFAULT) | |
177 | + Publisher.sendMessage("Enable style", style=const.STATE_DEFAULT) | |
178 | 178 | |
179 | 179 | # Import TIFF, BMP, JPEG or PNG |
180 | 180 | dirpath = dialog.ShowImportBitmapDirDialog(self.frame) |
... | ... | @@ -198,7 +198,7 @@ class Controller(): |
198 | 198 | #Publisher.sendMessage("Enable state project", state=False) |
199 | 199 | Publisher.sendMessage('Set project name') |
200 | 200 | Publisher.sendMessage("Stop Config Recording") |
201 | - Publisher.sendMessage("Set slice interaction style", style=const.STATE_DEFAULT) | |
201 | + Publisher.sendMessage("Enable style", style=const.STATE_DEFAULT) | |
202 | 202 | # Import project |
203 | 203 | dirpath = dialog.ShowImportDirDialog(self.frame) |
204 | 204 | if dirpath and not os.listdir(dirpath): |
... | ... | @@ -219,7 +219,7 @@ class Controller(): |
219 | 219 | # Publisher.sendMessage("Enable state project", state=False) |
220 | 220 | Publisher.sendMessage('Set project name') |
221 | 221 | Publisher.sendMessage("Stop Config Recording") |
222 | - Publisher.sendMessage("Set slice interaction style", style=const.STATE_DEFAULT) | |
222 | + Publisher.sendMessage("Enable style", style=const.STATE_DEFAULT) | |
223 | 223 | |
224 | 224 | # Warning for limited support to Analyze format |
225 | 225 | if id_type == const.ID_ANALYZE_IMPORT: |
... | ... | @@ -373,7 +373,7 @@ class Controller(): |
373 | 373 | Publisher.sendMessage('End busy cursor') |
374 | 374 | |
375 | 375 | def CloseProject(self): |
376 | - Publisher.sendMessage('Set slice interaction style', style=const.STATE_DEFAULT) | |
376 | + Publisher.sendMessage('Enable style', style=const.STATE_DEFAULT) | |
377 | 377 | Publisher.sendMessage('Hide content panel') |
378 | 378 | Publisher.sendMessage('Close project data') |
379 | 379 | ... | ... |
... | ... | @@ -0,0 +1,461 @@ |
1 | +# -------------------------------------------------------------------------- | |
2 | +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | |
3 | +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | |
4 | +# Homepage: http://www.softwarepublico.gov.br | |
5 | +# Contact: invesalius@cti.gov.br | |
6 | +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | |
7 | +# -------------------------------------------------------------------------- | |
8 | +# Este programa e software livre; voce pode redistribui-lo e/ou | |
9 | +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | |
10 | +# publicada pela Free Software Foundation; de acordo com a versao 2 | |
11 | +# da Licenca. | |
12 | +# | |
13 | +# Este programa eh distribuido na expectativa de ser util, mas SEM | |
14 | +# QUALQUER GARANTIA; sem mesmo a garantia implicita de | |
15 | +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | |
16 | +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | |
17 | +# detalhes. | |
18 | +# -------------------------------------------------------------------------- | |
19 | + | |
20 | +import vtk | |
21 | +import wx | |
22 | + | |
23 | +import invesalius.constants as const | |
24 | +import invesalius.project as prj | |
25 | + | |
26 | +from pubsub import pub as Publisher | |
27 | + | |
28 | + | |
29 | +PROP_MEASURE = 0.8 | |
30 | + | |
31 | + | |
32 | +class Base3DInteractorStyle(vtk.vtkInteractorStyleTrackballCamera): | |
33 | + def __init__(self, viewer): | |
34 | + self.right_pressed = False | |
35 | + self.left_pressed = False | |
36 | + self.middle_pressed = False | |
37 | + | |
38 | + self.AddObserver("LeftButtonPressEvent", self.OnPressLeftButton) | |
39 | + self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | |
40 | + | |
41 | + self.AddObserver("RightButtonPressEvent", self.OnPressRightButton) | |
42 | + self.AddObserver("RightButtonReleaseEvent", self.OnReleaseRightButton) | |
43 | + | |
44 | + self.AddObserver("MiddleButtonPressEvent", self.OnMiddleButtonPressEvent) | |
45 | + self.AddObserver("MiddleButtonReleaseEvent", self.OnMiddleButtonReleaseEvent) | |
46 | + | |
47 | + def OnPressLeftButton(self, evt, obj): | |
48 | + self.left_pressed = True | |
49 | + | |
50 | + def OnReleaseLeftButton(self, evt, obj): | |
51 | + self.left_pressed = False | |
52 | + | |
53 | + def OnPressRightButton(self, evt, obj): | |
54 | + self.right_pressed = True | |
55 | + | |
56 | + def OnReleaseRightButton(self, evt, obj): | |
57 | + self.right_pressed = False | |
58 | + | |
59 | + def OnMiddleButtonPressEvent(self, evt, obj): | |
60 | + self.middle_pressed = True | |
61 | + | |
62 | + def OnMiddleButtonReleaseEvent(self, evt, obj): | |
63 | + self.middle_pressed = False | |
64 | + | |
65 | + | |
66 | +class DefaultInteractorStyle(Base3DInteractorStyle): | |
67 | + """ | |
68 | + Interactor style responsible for Default functionalities: | |
69 | + * Zoom moving mouse with right button pressed; | |
70 | + * Change the slices with the scroll. | |
71 | + """ | |
72 | + def __init__(self, viewer): | |
73 | + super().__init__(viewer) | |
74 | + self.state_code = const.STATE_DEFAULT | |
75 | + | |
76 | + self.viewer = viewer | |
77 | + | |
78 | + # Zoom using right button | |
79 | + self.AddObserver("LeftButtonPressEvent",self.OnRotateLeftClick) | |
80 | + self.AddObserver("LeftButtonReleaseEvent",self.OnRotateLeftRelease) | |
81 | + | |
82 | + self.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | |
83 | + self.AddObserver("RightButtonReleaseEvent",self.OnZoomRightRelease) | |
84 | + | |
85 | + self.AddObserver("MouseMoveEvent", self.OnMouseMove) | |
86 | + | |
87 | + self.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | |
88 | + self.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | |
89 | + self.AddObserver("EnterEvent", self.OnFocus) | |
90 | + | |
91 | + def OnFocus(self, evt, obj): | |
92 | + self.viewer.SetFocus() | |
93 | + | |
94 | + def OnMouseMove(self, evt, obj): | |
95 | + if self.left_pressed: | |
96 | + evt.Rotate() | |
97 | + evt.OnLeftButtonDown() | |
98 | + | |
99 | + elif self.right_pressed: | |
100 | + evt.Dolly() | |
101 | + evt.OnRightButtonDown() | |
102 | + | |
103 | + elif self.middle_pressed: | |
104 | + evt.Pan() | |
105 | + evt.OnMiddleButtonDown() | |
106 | + | |
107 | + def OnRotateLeftClick(self, evt, obj): | |
108 | + evt.StartRotate() | |
109 | + | |
110 | + def OnRotateLeftRelease(self, evt, obj): | |
111 | + evt.OnLeftButtonUp() | |
112 | + evt.EndRotate() | |
113 | + | |
114 | + def OnZoomRightClick(self, evt, obj): | |
115 | + evt.StartDolly() | |
116 | + | |
117 | + def OnZoomRightRelease(self, evt, obj): | |
118 | + evt.OnRightButtonUp() | |
119 | + evt.EndDolly() | |
120 | + | |
121 | + def OnScrollForward(self, evt, obj): | |
122 | + self.OnMouseWheelForward() | |
123 | + | |
124 | + def OnScrollBackward(self, evt, obj): | |
125 | + self.OnMouseWheelBackward() | |
126 | + | |
127 | + | |
128 | +class ZoomInteractorStyle(DefaultInteractorStyle): | |
129 | + """ | |
130 | + Interactor style responsible for zoom with movement of the mouse and the | |
131 | + left mouse button clicked. | |
132 | + """ | |
133 | + def __init__(self, viewer): | |
134 | + super().__init__(viewer) | |
135 | + | |
136 | + self.state_code = const.STATE_ZOOM | |
137 | + | |
138 | + self.viewer = viewer | |
139 | + | |
140 | + self.RemoveObservers("LeftButtonPressEvent") | |
141 | + self.RemoveObservers("LeftButtonReleaseEvent") | |
142 | + | |
143 | + self.AddObserver("LeftButtonPressEvent", self.OnPressLeftButton) | |
144 | + self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | |
145 | + | |
146 | + self.viewer.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnZoom) | |
147 | + | |
148 | + def SetUp(self): | |
149 | + Publisher.sendMessage('Toggle toolbar item', | |
150 | + _id=self.state_code, value=True) | |
151 | + | |
152 | + def CleanUp(self): | |
153 | + self.viewer.interactor.Unbind(wx.EVT_LEFT_DCLICK) | |
154 | + Publisher.sendMessage('Toggle toolbar item', | |
155 | + _id=self.state_code, value=False) | |
156 | + | |
157 | + def OnPressLeftButton(self, evt, obj): | |
158 | + self.right_pressed = True | |
159 | + | |
160 | + def OnReleaseLeftButton(self, obj, evt): | |
161 | + self.right_pressed = False | |
162 | + | |
163 | + def OnUnZoom(self, evt): | |
164 | + ren = self.viewer.ren | |
165 | + ren.ResetCamera() | |
166 | + ren.ResetCameraClippingRange() | |
167 | + self.viewer.interactor.Render() | |
168 | + | |
169 | + | |
170 | +class ZoomSLInteractorStyle(vtk.vtkInteractorStyleRubberBandZoom): | |
171 | + """ | |
172 | + Interactor style responsible for zoom by selecting a region. | |
173 | + """ | |
174 | + def __init__(self, viewer): | |
175 | + self.viewer = viewer | |
176 | + self.viewer.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnZoom) | |
177 | + | |
178 | + self.state_code = const.STATE_ZOOM_SL | |
179 | + | |
180 | + def SetUp(self): | |
181 | + Publisher.sendMessage('Toggle toolbar item', | |
182 | + _id=self.state_code, value=True) | |
183 | + | |
184 | + def CleanUp(self): | |
185 | + self.viewer.interactor.Unbind(wx.EVT_LEFT_DCLICK) | |
186 | + Publisher.sendMessage('Toggle toolbar item', | |
187 | + _id=self.state_code, value=False) | |
188 | + | |
189 | + def OnUnZoom(self, evt): | |
190 | + ren = self.viewer.ren | |
191 | + ren.ResetCamera() | |
192 | + ren.ResetCameraClippingRange() | |
193 | + self.viewer.interactor.Render() | |
194 | + | |
195 | + | |
196 | +class PanMoveInteractorStyle(DefaultInteractorStyle): | |
197 | + """ | |
198 | + Interactor style responsible for translate the camera. | |
199 | + """ | |
200 | + def __init__(self, viewer): | |
201 | + super().__init__(viewer) | |
202 | + | |
203 | + self.state_code = const.STATE_PAN | |
204 | + | |
205 | + self.viewer = viewer | |
206 | + | |
207 | + self.panning = False | |
208 | + | |
209 | + self.AddObserver("MouseMoveEvent", self.OnPanMove) | |
210 | + self.viewer.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnspan) | |
211 | + | |
212 | + def SetUp(self): | |
213 | + Publisher.sendMessage('Toggle toolbar item', | |
214 | + _id=self.state_code, value=True) | |
215 | + | |
216 | + def CleanUp(self): | |
217 | + self.viewer.interactor.Unbind(wx.EVT_LEFT_DCLICK) | |
218 | + Publisher.sendMessage('Toggle toolbar item', | |
219 | + _id=self.state_code, value=False) | |
220 | + | |
221 | + def OnPressLeftButton(self, evt, obj): | |
222 | + self.panning = True | |
223 | + | |
224 | + def OnReleaseLeftButton(self, evt, obj): | |
225 | + self.panning = False | |
226 | + | |
227 | + def OnPanMove(self, obj, evt): | |
228 | + if self.panning: | |
229 | + obj.Pan() | |
230 | + obj.OnRightButtonDown() | |
231 | + | |
232 | + def OnUnspan(self, evt): | |
233 | + self.viewer.ren.ResetCamera() | |
234 | + self.viewer.interactor.Render() | |
235 | + | |
236 | + | |
237 | +class SpinInteractorStyle(DefaultInteractorStyle): | |
238 | + """ | |
239 | + Interactor style responsible for spin the camera. | |
240 | + """ | |
241 | + def __init__(self, viewer): | |
242 | + DefaultInteractorStyle.__init__(self, viewer) | |
243 | + self.state_code = const.STATE_SPIN | |
244 | + self.viewer = viewer | |
245 | + self.spinning = False | |
246 | + self.AddObserver("MouseMoveEvent", self.OnSpinMove) | |
247 | + | |
248 | + def SetUp(self): | |
249 | + Publisher.sendMessage('Toggle toolbar item', _id=self.state_code, value=True) | |
250 | + | |
251 | + def CleanUp(self): | |
252 | + Publisher.sendMessage('Toggle toolbar item', _id=self.state_code, value=False) | |
253 | + | |
254 | + def OnPressLeftButton(self, evt, obj): | |
255 | + self.spinning = True | |
256 | + | |
257 | + def OnReleaseLeftButton(self, evt, obj): | |
258 | + self.spinning = False | |
259 | + | |
260 | + def OnSpinMove(self, evt, obj): | |
261 | + if self.spinning: | |
262 | + evt.Spin() | |
263 | + evt.OnRightButtonDown() | |
264 | + | |
265 | + | |
266 | +class WWWLInteractorStyle(DefaultInteractorStyle): | |
267 | + """ | |
268 | + Interactor style responsible for Window Level & Width functionality. | |
269 | + """ | |
270 | + def __init__(self, viewer): | |
271 | + super().__init__(viewer) | |
272 | + self.state_code = const.STATE_WL | |
273 | + | |
274 | + self.viewer = viewer | |
275 | + | |
276 | + self.last_x = 0 | |
277 | + self.last_y = 0 | |
278 | + | |
279 | + self.changing_wwwl = False | |
280 | + | |
281 | + self.RemoveObservers("LeftButtonPressEvent") | |
282 | + self.RemoveObservers("LeftButtonReleaseEvent") | |
283 | + | |
284 | + self.AddObserver("MouseMoveEvent", self.OnWindowLevelMove) | |
285 | + self.AddObserver("LeftButtonPressEvent", self.OnWindowLevelClick) | |
286 | + self.AddObserver("LeftButtonReleaseEvent", self.OnWindowLevelRelease) | |
287 | + | |
288 | + def SetUp(self): | |
289 | + Publisher.sendMessage('Toggle toolbar item', _id=self.state_code, value=True) | |
290 | + self.viewer.on_wl = True | |
291 | + if self.viewer.raycasting_volume: | |
292 | + self.viewer.text.Show() | |
293 | + self.viewer.interactor.Render() | |
294 | + | |
295 | + def CleanUp(self): | |
296 | + Publisher.sendMessage('Toggle toolbar item', _id=self.state_code, value=False) | |
297 | + self.viewer.on_wl = True | |
298 | + self.viewer.text.Hide() | |
299 | + self.viewer.interactor.Render() | |
300 | + | |
301 | + def OnWindowLevelMove(self, obj, evt): | |
302 | + if self.changing_wwwl: | |
303 | + iren = obj.GetInteractor() | |
304 | + mouse_x, mouse_y = iren.GetEventPosition() | |
305 | + diff_x = mouse_x - self.last_x | |
306 | + diff_y = mouse_y - self.last_y | |
307 | + self.last_x, self.last_y = mouse_x, mouse_y | |
308 | + Publisher.sendMessage('Set raycasting relative window and level', | |
309 | + diff_wl=diff_x, diff_ww=diff_y) | |
310 | + Publisher.sendMessage('Refresh raycasting widget points') | |
311 | + Publisher.sendMessage('Render volume viewer') | |
312 | + | |
313 | + def OnWindowLevelClick(self, obj, evt): | |
314 | + iren = obj.GetInteractor() | |
315 | + self.last_x, self.last_y = iren.GetLastEventPosition() | |
316 | + self.changing_wwwl = True | |
317 | + | |
318 | + def OnWindowLevelRelease(self, obj, evt): | |
319 | + self.changing_wwwl = False | |
320 | + | |
321 | + | |
322 | +class LinearMeasureInteractorStyle(DefaultInteractorStyle): | |
323 | + """ | |
324 | + Interactor style responsible for insert linear measurements. | |
325 | + """ | |
326 | + def __init__(self, viewer): | |
327 | + super().__init__(viewer) | |
328 | + self.viewer = viewer | |
329 | + self.state_code = const.STATE_MEASURE_DISTANCE | |
330 | + self.measure_picker = vtk.vtkPropPicker() | |
331 | + | |
332 | + proj = prj.Project() | |
333 | + self._radius = min(proj.spacing) * PROP_MEASURE | |
334 | + | |
335 | + self.RemoveObservers("LeftButtonPressEvent") | |
336 | + self.AddObserver("LeftButtonPressEvent", self.OnInsertLinearMeasurePoint) | |
337 | + | |
338 | + def SetUp(self): | |
339 | + Publisher.sendMessage('Toggle toolbar item', | |
340 | + _id=self.state_code, value=True) | |
341 | + | |
342 | + def CleanUp(self): | |
343 | + Publisher.sendMessage('Toggle toolbar item', | |
344 | + _id=self.state_code, value=False) | |
345 | + Publisher.sendMessage("Remove incomplete measurements") | |
346 | + | |
347 | + def OnInsertLinearMeasurePoint(self, obj, evt): | |
348 | + x,y = self.viewer.interactor.GetEventPosition() | |
349 | + self.measure_picker.Pick(x, y, 0, self.viewer.ren) | |
350 | + x, y, z = self.measure_picker.GetPickPosition() | |
351 | + if self.measure_picker.GetActor(): | |
352 | + self.left_pressed = False | |
353 | + Publisher.sendMessage("Add measurement point", | |
354 | + position=(x, y,z), | |
355 | + type=const.LINEAR, | |
356 | + location=const.SURFACE, | |
357 | + radius=self._radius) | |
358 | + self.viewer.interactor.Render() | |
359 | + else: | |
360 | + self.left_pressed = True | |
361 | + | |
362 | + | |
363 | +class AngularMeasureInteractorStyle(DefaultInteractorStyle): | |
364 | + """ | |
365 | + Interactor style responsible for insert linear measurements. | |
366 | + """ | |
367 | + def __init__(self, viewer): | |
368 | + super().__init__(viewer) | |
369 | + self.viewer = viewer | |
370 | + self.state_code = const.STATE_MEASURE_DISTANCE | |
371 | + self.measure_picker = vtk.vtkPropPicker() | |
372 | + | |
373 | + proj = prj.Project() | |
374 | + self._radius = min(proj.spacing) * PROP_MEASURE | |
375 | + | |
376 | + self.RemoveObservers("LeftButtonPressEvent") | |
377 | + self.AddObserver("LeftButtonPressEvent", self.OnInsertAngularMeasurePoint) | |
378 | + | |
379 | + def SetUp(self): | |
380 | + Publisher.sendMessage('Toggle toolbar item', _id=self.state_code, value=True) | |
381 | + | |
382 | + def CleanUp(self): | |
383 | + Publisher.sendMessage('Toggle toolbar item', _id=self.state_code, value=False) | |
384 | + Publisher.sendMessage("Remove incomplete measurements") | |
385 | + | |
386 | + def OnInsertAngularMeasurePoint(self, obj, evt): | |
387 | + x,y = self.viewer.interactor.GetEventPosition() | |
388 | + self.measure_picker.Pick(x, y, 0, self.viewer.ren) | |
389 | + x, y, z = self.measure_picker.GetPickPosition() | |
390 | + if self.measure_picker.GetActor(): | |
391 | + self.left_pressed = False | |
392 | + Publisher.sendMessage("Add measurement point", | |
393 | + position=(x, y,z), | |
394 | + type=const.ANGULAR, | |
395 | + location=const.SURFACE, | |
396 | + radius=self._radius) | |
397 | + self.viewer.interactor.Render() | |
398 | + else: | |
399 | + self.left_pressed = True | |
400 | + | |
401 | + | |
402 | +class SeedInteractorStyle(DefaultInteractorStyle): | |
403 | + """ | |
404 | + Interactor style responsible for select sub surfaces. | |
405 | + """ | |
406 | + def __init__(self, viewer): | |
407 | + super().__init__(viewer) | |
408 | + self.viewer = viewer | |
409 | + self.picker = vtk.vtkPointPicker() | |
410 | + | |
411 | + self.RemoveObservers("LeftButtonPressEvent") | |
412 | + self.AddObserver("LeftButtonPressEvent", self.OnInsertSeed) | |
413 | + | |
414 | + def OnInsertSeed(self, obj, evt): | |
415 | + x,y = self.viewer.interactor.GetEventPosition() | |
416 | + self.picker.Pick(x, y, 0, self.viewer.ren) | |
417 | + point_id = self.picker.GetPointId() | |
418 | + if point_id > -1: | |
419 | + self.viewer.seed_points.append(point_id) | |
420 | + self.viewer.interactor.Render() | |
421 | + else: | |
422 | + self.left_pressed = True | |
423 | + | |
424 | + | |
425 | +class Styles: | |
426 | + styles = { | |
427 | + const.STATE_DEFAULT: DefaultInteractorStyle, | |
428 | + const.STATE_ZOOM: ZoomInteractorStyle, | |
429 | + const.STATE_ZOOM_SL: ZoomSLInteractorStyle, | |
430 | + const.STATE_PAN: PanMoveInteractorStyle, | |
431 | + const.STATE_SPIN: SpinInteractorStyle, | |
432 | + const.STATE_WL: WWWLInteractorStyle, | |
433 | + const.STATE_MEASURE_DISTANCE: LinearMeasureInteractorStyle, | |
434 | + const.STATE_MEASURE_ANGLE: AngularMeasureInteractorStyle, | |
435 | + const.VOLUME_STATE_SEED: SeedInteractorStyle, | |
436 | + } | |
437 | + | |
438 | + @classmethod | |
439 | + def add_style(cls, style_cls, level=1): | |
440 | + if style_cls in cls.styles.values(): | |
441 | + for style_id in cls.styles: | |
442 | + if cls.styles[style_id] == style_cls: | |
443 | + const.STYLE_LEVEL[style_id] = level | |
444 | + return style_id | |
445 | + | |
446 | + new_style_id = max(cls.styles) + 1 | |
447 | + cls.styles[new_style_id] = style_cls | |
448 | + const.STYLE_LEVEL[new_style_id] = level | |
449 | + return new_style_id | |
450 | + | |
451 | + @classmethod | |
452 | + def remove_style(cls, style_id): | |
453 | + del cls.styles[style_id] | |
454 | + | |
455 | + @classmethod | |
456 | + def get_style(cls, style): | |
457 | + return cls.styles[style] | |
458 | + | |
459 | + @classmethod | |
460 | + def has_style(cls, style): | |
461 | + return style in cls.styles | ... | ... |
invesalius/data/viewer_volume.py
... | ... | @@ -38,6 +38,7 @@ from imageio import imsave |
38 | 38 | import invesalius.constants as const |
39 | 39 | import invesalius.data.bases as bases |
40 | 40 | import invesalius.data.slice_ as sl |
41 | +import invesalius.data.styles_3d as styles | |
41 | 42 | import invesalius.data.transformations as tr |
42 | 43 | import invesalius.data.vtk_utils as vtku |
43 | 44 | import invesalius.project as prj |
... | ... | @@ -46,6 +47,7 @@ import invesalius.utils as utils |
46 | 47 | |
47 | 48 | from invesalius import inv_paths |
48 | 49 | |
50 | + | |
49 | 51 | if sys.platform == 'win32': |
50 | 52 | try: |
51 | 53 | import win32api |
... | ... | @@ -70,14 +72,14 @@ class Viewer(wx.Panel): |
70 | 72 | |
71 | 73 | self.staticballs = [] |
72 | 74 | |
73 | - style = vtk.vtkInteractorStyleTrackballCamera() | |
74 | - self.style = style | |
75 | + self.style = None | |
75 | 76 | |
76 | 77 | interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize()) |
77 | - interactor.SetInteractorStyle(style) | |
78 | 78 | self.interactor = interactor |
79 | 79 | self.interactor.SetRenderWhenDisabled(True) |
80 | 80 | |
81 | + self.enable_style(const.STATE_DEFAULT) | |
82 | + | |
81 | 83 | sizer = wx.BoxSizer(wx.VERTICAL) |
82 | 84 | sizer.Add(interactor, 1, wx.EXPAND) |
83 | 85 | self.sizer = sizer |
... | ... | @@ -149,8 +151,6 @@ class Viewer(wx.Panel): |
149 | 151 | #self.measure_picker.SetTolerance(0.005) |
150 | 152 | self.measures = [] |
151 | 153 | |
152 | - self._last_state = 0 | |
153 | - | |
154 | 154 | self.repositioned_axial_plan = 0 |
155 | 155 | self.repositioned_sagital_plan = 0 |
156 | 156 | self.repositioned_coronal_plan = 0 |
... | ... | @@ -255,7 +255,7 @@ class Viewer(wx.Panel): |
255 | 255 | # Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') |
256 | 256 | Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') |
257 | 257 | |
258 | - Publisher.subscribe(self.OnEnableStyle, 'Enable style') | |
258 | + Publisher.subscribe(self.enable_style, 'Enable style') | |
259 | 259 | Publisher.subscribe(self.OnDisableStyle, 'Disable style') |
260 | 260 | |
261 | 261 | Publisher.subscribe(self.OnHideText, |
... | ... | @@ -506,7 +506,6 @@ class Viewer(wx.Panel): |
506 | 506 | |
507 | 507 | self.interaction_style.Reset() |
508 | 508 | self.SetInteractorStyle(const.STATE_DEFAULT) |
509 | - self._last_state = const.STATE_DEFAULT | |
510 | 509 | |
511 | 510 | def OnHideText(self): |
512 | 511 | self.text.Hide() |
... | ... | @@ -1441,147 +1440,26 @@ class Viewer(wx.Panel): |
1441 | 1440 | imsave('/tmp/polygon.png', arr) |
1442 | 1441 | |
1443 | 1442 | def SetInteractorStyle(self, state): |
1444 | - action = { | |
1445 | - const.STATE_PAN: | |
1446 | - { | |
1447 | - "MouseMoveEvent": self.OnPanMove, | |
1448 | - "LeftButtonPressEvent": self.OnPanClick, | |
1449 | - "LeftButtonReleaseEvent": self.OnReleasePanClick | |
1450 | - }, | |
1451 | - const.STATE_ZOOM: | |
1452 | - { | |
1453 | - "MouseMoveEvent": self.OnZoomMove, | |
1454 | - "LeftButtonPressEvent": self.OnZoomClick, | |
1455 | - "LeftButtonReleaseEvent": self.OnReleaseZoomClick, | |
1456 | - }, | |
1457 | - const.STATE_SPIN: | |
1458 | - { | |
1459 | - "MouseMoveEvent": self.OnSpinMove, | |
1460 | - "LeftButtonPressEvent": self.OnSpinClick, | |
1461 | - "LeftButtonReleaseEvent": self.OnReleaseSpinClick, | |
1462 | - }, | |
1463 | - const.STATE_WL: | |
1464 | - { | |
1465 | - "MouseMoveEvent": self.OnWindowLevelMove, | |
1466 | - "LeftButtonPressEvent": self.OnWindowLevelClick, | |
1467 | - "LeftButtonReleaseEvent":self.OnWindowLevelRelease | |
1468 | - }, | |
1469 | - const.STATE_DEFAULT: | |
1470 | - { | |
1471 | - }, | |
1472 | - const.VOLUME_STATE_SEED: | |
1473 | - { | |
1474 | - "LeftButtonPressEvent": self.OnInsertSeed | |
1475 | - }, | |
1476 | - const.STATE_MEASURE_DISTANCE: | |
1477 | - { | |
1478 | - "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint | |
1479 | - }, | |
1480 | - const.STATE_MEASURE_ANGLE: | |
1481 | - { | |
1482 | - "LeftButtonPressEvent": self.OnInsertAngularMeasurePoint | |
1483 | - } | |
1484 | - } | |
1485 | - | |
1486 | - if self._last_state in (const.STATE_MEASURE_DISTANCE, | |
1487 | - const.STATE_MEASURE_ANGLE): | |
1488 | - if self.measures and not self.measures[-1].text_actor: | |
1489 | - del self.measures[-1] | |
1490 | - | |
1491 | - if state == const.STATE_WL: | |
1492 | - self.on_wl = True | |
1493 | - if self.raycasting_volume: | |
1494 | - self.text.Show() | |
1495 | - self.interactor.Render() | |
1496 | - else: | |
1497 | - self.on_wl = False | |
1498 | - self.text.Hide() | |
1499 | - self.interactor.Render() | |
1443 | + cleanup = getattr(self.style, 'CleanUp', None) | |
1444 | + if cleanup: | |
1445 | + self.style.CleanUp() | |
1500 | 1446 | |
1501 | - if state in (const.STATE_MEASURE_DISTANCE, | |
1502 | - const.STATE_MEASURE_ANGLE): | |
1503 | - self.interactor.SetPicker(self.measure_picker) | |
1447 | + del self.style | |
1504 | 1448 | |
1505 | - if (state == const.STATE_ZOOM_SL): | |
1506 | - style = vtk.vtkInteractorStyleRubberBandZoom() | |
1507 | - self.interactor.SetInteractorStyle(style) | |
1508 | - self.style = style | |
1509 | - else: | |
1510 | - style = vtk.vtkInteractorStyleTrackballCamera() | |
1511 | - self.interactor.SetInteractorStyle(style) | |
1512 | - self.style = style | |
1513 | - | |
1514 | - # Check each event available for each mode | |
1515 | - for event in action.get(state, []): | |
1516 | - # Bind event | |
1517 | - style.AddObserver(event,action[state][event]) | |
1518 | - | |
1519 | - self._last_state = state | |
1520 | - | |
1521 | - def OnSpinMove(self, evt, obj): | |
1522 | - if (self.mouse_pressed): | |
1523 | - evt.Spin() | |
1524 | - evt.OnRightButtonDown() | |
1525 | - | |
1526 | - def OnSpinClick(self, evt, obj): | |
1527 | - self.mouse_pressed = 1 | |
1528 | - evt.StartSpin() | |
1449 | + style = styles.Styles.get_style(state)(self) | |
1529 | 1450 | |
1530 | - def OnReleaseSpinClick(self,evt,obj): | |
1531 | - self.mouse_pressed = 0 | |
1532 | - evt.EndSpin() | |
1533 | - | |
1534 | - def OnZoomMove(self, evt, obj): | |
1535 | - if (self.mouse_pressed): | |
1536 | - evt.Dolly() | |
1537 | - evt.OnRightButtonDown() | |
1538 | - | |
1539 | - def OnZoomClick(self, evt, obj): | |
1540 | - self.mouse_pressed = 1 | |
1541 | - evt.StartDolly() | |
1542 | - | |
1543 | - def OnReleaseZoomClick(self,evt,obj): | |
1544 | - self.mouse_pressed = 0 | |
1545 | - evt.EndDolly() | |
1451 | + setup = getattr(style, 'SetUp', None) | |
1452 | + if setup: | |
1453 | + style.SetUp() | |
1546 | 1454 | |
1547 | - def OnPanMove(self, evt, obj): | |
1548 | - if (self.mouse_pressed): | |
1549 | - evt.Pan() | |
1550 | - evt.OnRightButtonDown() | |
1551 | - | |
1552 | - def OnPanClick(self, evt, obj): | |
1553 | - self.mouse_pressed = 1 | |
1554 | - evt.StartPan() | |
1555 | - | |
1556 | - def OnReleasePanClick(self,evt,obj): | |
1557 | - self.mouse_pressed = 0 | |
1558 | - evt.EndPan() | |
1559 | - | |
1560 | - def OnWindowLevelMove(self, obj, evt): | |
1561 | - if self.onclick and self.raycasting_volume: | |
1562 | - mouse_x, mouse_y = self.interactor.GetEventPosition() | |
1563 | - diff_x = mouse_x - self.last_x | |
1564 | - diff_y = mouse_y - self.last_y | |
1565 | - self.last_x, self.last_y = mouse_x, mouse_y | |
1566 | - Publisher.sendMessage('Set raycasting relative window and level', | |
1567 | - diff_wl=diff_x, diff_ww=diff_y) | |
1568 | - Publisher.sendMessage('Refresh raycasting widget points') | |
1569 | - self.interactor.Render() | |
1570 | - | |
1571 | - def OnWindowLevelClick(self, obj, evt): | |
1572 | - if const.RAYCASTING_WWWL_BLUR: | |
1573 | - self.style.StartZoom() | |
1574 | - self.onclick = True | |
1575 | - mouse_x, mouse_y = self.interactor.GetEventPosition() | |
1576 | - self.last_x, self.last_y = mouse_x, mouse_y | |
1455 | + self.style = style | |
1456 | + self.interactor.SetInteractorStyle(style) | |
1457 | + self.interactor.Render() | |
1577 | 1458 | |
1578 | - def OnWindowLevelRelease(self, obj, evt): | |
1579 | - self.onclick = False | |
1580 | - if const.RAYCASTING_WWWL_BLUR: | |
1581 | - self.style.EndZoom() | |
1459 | + self.state = state | |
1582 | 1460 | |
1583 | - def OnEnableStyle(self, style): | |
1584 | - if (style in const.VOLUME_STYLES): | |
1461 | + def enable_style(self, style): | |
1462 | + if styles.Styles.has_style(style): | |
1585 | 1463 | new_state = self.interaction_style.AddState(style) |
1586 | 1464 | self.SetInteractorStyle(new_state) |
1587 | 1465 | else: |
... | ... | @@ -1849,73 +1727,6 @@ class Viewer(wx.Panel): |
1849 | 1727 | def AppendActor(self, actor): |
1850 | 1728 | self.ren.AddActor(actor) |
1851 | 1729 | |
1852 | - def OnInsertSeed(self, obj, evt): | |
1853 | - x,y = self.interactor.GetEventPosition() | |
1854 | - #x,y = obj.GetLastEventPosition() | |
1855 | - self.picker.Pick(x, y, 0, self.ren) | |
1856 | - point_id = self.picker.GetPointId() | |
1857 | - self.seed_points.append(point_id) | |
1858 | - self.interactor.Render() | |
1859 | - | |
1860 | - def OnInsertLinearMeasurePoint(self, obj, evt): | |
1861 | - x,y = self.interactor.GetEventPosition() | |
1862 | - self.measure_picker.Pick(x, y, 0, self.ren) | |
1863 | - x, y, z = self.measure_picker.GetPickPosition() | |
1864 | - | |
1865 | - proj = prj.Project() | |
1866 | - radius = min(proj.spacing) * PROP_MEASURE | |
1867 | - if self.measure_picker.GetActor(): | |
1868 | - # if not self.measures or self.measures[-1].IsComplete(): | |
1869 | - # m = measures.LinearMeasure(self.ren) | |
1870 | - # m.AddPoint(x, y, z) | |
1871 | - # self.measures.append(m) | |
1872 | - # else: | |
1873 | - # m = self.measures[-1] | |
1874 | - # m.AddPoint(x, y, z) | |
1875 | - # if m.IsComplete(): | |
1876 | - # Publisher.sendMessage("Add measure to list", | |
1877 | - # (u"3D", _(u"%.3f mm" % m.GetValue()))) | |
1878 | - Publisher.sendMessage("Add measurement point", | |
1879 | - position=(x, y,z), | |
1880 | - type=const.LINEAR, | |
1881 | - location=const.SURFACE, | |
1882 | - radius=radius) | |
1883 | - self.interactor.Render() | |
1884 | - | |
1885 | - def OnInsertAngularMeasurePoint(self, obj, evt): | |
1886 | - x,y = self.interactor.GetEventPosition() | |
1887 | - self.measure_picker.Pick(x, y, 0, self.ren) | |
1888 | - x, y, z = self.measure_picker.GetPickPosition() | |
1889 | - | |
1890 | - proj = prj.Project() | |
1891 | - radius = min(proj.spacing) * PROP_MEASURE | |
1892 | - if self.measure_picker.GetActor(): | |
1893 | - # if not self.measures or self.measures[-1].IsComplete(): | |
1894 | - # m = measures.AngularMeasure(self.ren) | |
1895 | - # m.AddPoint(x, y, z) | |
1896 | - # self.measures.append(m) | |
1897 | - # else: | |
1898 | - # m = self.measures[-1] | |
1899 | - # m.AddPoint(x, y, z) | |
1900 | - # if m.IsComplete(): | |
1901 | - # index = len(self.measures) - 1 | |
1902 | - # name = "M" | |
1903 | - # colour = m.colour | |
1904 | - # type_ = _("Angular") | |
1905 | - # location = u"3D" | |
1906 | - # value = u"%.2f˚"% m.GetValue() | |
1907 | - # msg = 'Update measurement info in GUI', | |
1908 | - # Publisher.sendMessage(msg, | |
1909 | - # (index, name, colour, | |
1910 | - # type_, location, | |
1911 | - # value)) | |
1912 | - Publisher.sendMessage("Add measurement point", | |
1913 | - position=(x, y,z), | |
1914 | - type=const.ANGULAR, | |
1915 | - location=const.SURFACE, | |
1916 | - radius=radius) | |
1917 | - self.interactor.Render() | |
1918 | - | |
1919 | 1730 | def Reposition3DPlane(self, plane_label): |
1920 | 1731 | if not(self.added_actor) and not(self.raycasting_volume): |
1921 | 1732 | if not(self.repositioned_axial_plan) and (plane_label == 'Axial'): | ... | ... |
invesalius/gui/data_notebook.py
... | ... | @@ -1030,16 +1030,17 @@ class MeasuresListCtrlPanel(InvListCtrl): |
1030 | 1030 | self.RemoveMeasurements() |
1031 | 1031 | |
1032 | 1032 | def OnRemoveGUIMeasure(self, measure_index): |
1033 | - self.DeleteItem(measure_index) | |
1034 | - | |
1035 | - old_dict = self._list_index | |
1036 | - new_dict = {} | |
1037 | - j = 0 | |
1038 | - for i in old_dict: | |
1039 | - if i != measure_index: | |
1040 | - new_dict[j] = old_dict[i] | |
1041 | - j+=1 | |
1042 | - self._list_index = new_dict | |
1033 | + if measure_index in self._list_index: | |
1034 | + self.DeleteItem(measure_index) | |
1035 | + | |
1036 | + old_dict = self._list_index | |
1037 | + new_dict = {} | |
1038 | + j = 0 | |
1039 | + for i in old_dict: | |
1040 | + if i != measure_index: | |
1041 | + new_dict[j] = old_dict[i] | |
1042 | + j+=1 | |
1043 | + self._list_index = new_dict | |
1043 | 1044 | |
1044 | 1045 | def RemoveMeasurements(self): |
1045 | 1046 | """ | ... | ... |