Commit f7b5fc4d6a0a17b0359510217568644ca344dc5a
1 parent
071dff96
Exists in
interactor_style
Created a new interactor style to handle the cross tool
Showing
2 changed files
with
264 additions
and
141 deletions
Show diff stats
invesalius/data/styles.py
@@ -19,8 +19,145 @@ | @@ -19,8 +19,145 @@ | ||
19 | 19 | ||
20 | import vtk | 20 | import vtk |
21 | 21 | ||
22 | +from wx.lib.pubsub import pub as Publisher | ||
23 | + | ||
22 | import constants as const | 24 | import constants as const |
23 | 25 | ||
26 | +class ZoomInteractorStyle(vtk.vtkInteractorStyleImage): | ||
27 | + """ | ||
28 | + Interactor style responsible for zoom the camera. | ||
29 | + """ | ||
30 | + def __init__(self): | ||
31 | + self.right_pressed = False | ||
32 | + | ||
33 | + # Zoom using right button | ||
34 | + self.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | ||
35 | + self.AddObserver("MouseMoveEvent", self.OnZoomRightMove) | ||
36 | + self.AddObserver("RightButtonReleaseEvent", self.OnZoomRightRelease) | ||
37 | + | ||
38 | + def OnZoomRightMove(self, evt, obj): | ||
39 | + if (self.right_pressed): | ||
40 | + evt.Dolly() | ||
41 | + evt.OnRightButtonDown() | ||
42 | + | ||
43 | + def OnZoomRightClick(self, evt, obj): | ||
44 | + self.right_pressed = 1 | ||
45 | + evt.StartDolly() | ||
46 | + | ||
47 | + def OnZoomRightRelease(self, evt, obj): | ||
48 | + self.right_pressed = False | ||
49 | + | ||
50 | +class CrossInteractorStyle(ZoomInteractorStyle): | ||
51 | + """ | ||
52 | + Interactor style responsible for the Cross. | ||
53 | + """ | ||
54 | + def __init__(self, orientation, slice_data): | ||
55 | + ZoomInteractorStyle.__init__(self) | ||
56 | + | ||
57 | + self.orientation = orientation | ||
58 | + self.slice_actor = slice_data.actor | ||
59 | + self.slice_data = slice_data | ||
60 | + | ||
61 | + self.left_pressed = False | ||
62 | + self.picker = vtk.vtkWorldPointPicker() | ||
63 | + | ||
64 | + self.AddObserver("MouseMoveEvent", self.OnCrossMove) | ||
65 | + self.AddObserver("LeftButtonPressEvent", self.OnCrossMouseClick) | ||
66 | + self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | ||
67 | + | ||
68 | + def OnCrossMouseClick(self, obj, evt): | ||
69 | + self.left_pressed = True | ||
70 | + iren = obj.GetInteractor() | ||
71 | + self.ChangeCrossPosition(iren) | ||
72 | + | ||
73 | + def OnCrossMove(self, obj, evt): | ||
74 | + # The user moved the mouse with left button pressed | ||
75 | + if self.left_pressed: | ||
76 | + print "OnCrossMove interactor style" | ||
77 | + iren = obj.GetInteractor() | ||
78 | + self.ChangeCrossPosition(iren) | ||
79 | + | ||
80 | + def OnReleaseLeftButton(self, obj, evt): | ||
81 | + self.left_pressed = False | ||
82 | + | ||
83 | + def ChangeCrossPosition(self, iren): | ||
84 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
85 | + ren = iren.GetRenderWindow().GetRenderers().GetFirstRenderer() | ||
86 | + self.picker.Pick(mouse_x, mouse_y, 0, ren) | ||
87 | + | ||
88 | + # Get in what slice data the click occurred | ||
89 | + # pick to get click position in the 3d world | ||
90 | + coord_cross = self.get_coordinate_cursor() | ||
91 | + position = self.slice_actor.GetInput().FindPoint(coord_cross) | ||
92 | + # Forcing focal point to be setted in the center of the pixel. | ||
93 | + coord_cross = self.slice_actor.GetInput().GetPoint(position) | ||
94 | + | ||
95 | + coord = self.calcultate_scroll_position(position) | ||
96 | + self.ScrollSlice(coord) | ||
97 | + | ||
98 | + Publisher.sendMessage('Update cross position', coord_cross) | ||
99 | + Publisher.sendMessage('Set ball reference position based on bound', | ||
100 | + coord_cross) | ||
101 | + Publisher.sendMessage('Set camera in volume', coord_cross) | ||
102 | + Publisher.sendMessage('Render volume viewer') | ||
103 | + | ||
104 | + iren.Render() | ||
105 | + | ||
106 | + def get_coordinate_cursor(self): | ||
107 | + # Find position | ||
108 | + x, y, z = self.picker.GetPickPosition() | ||
109 | + bounds = self.slice_actor.GetBounds() | ||
110 | + if bounds[0] == bounds[1]: | ||
111 | + x = bounds[0] | ||
112 | + elif bounds[2] == bounds[3]: | ||
113 | + y = bounds[2] | ||
114 | + elif bounds[4] == bounds[5]: | ||
115 | + z = bounds[4] | ||
116 | + return x, y, z | ||
117 | + | ||
118 | + def calcultate_scroll_position(self, position): | ||
119 | + # Based in the given coord (x, y, z), returns a list with the scroll positions for each | ||
120 | + # orientation, being the first position the sagital, second the coronal | ||
121 | + # and the last, axial. | ||
122 | + | ||
123 | + if self.orientation == 'AXIAL': | ||
124 | + image_width = self.slice_actor.GetInput().GetDimensions()[0] | ||
125 | + axial = self.slice_data.number | ||
126 | + coronal = position / image_width | ||
127 | + sagital = position % image_width | ||
128 | + | ||
129 | + elif self.orientation == 'CORONAL': | ||
130 | + image_width = self.slice_actor.GetInput().GetDimensions()[0] | ||
131 | + axial = position / image_width | ||
132 | + coronal = self.slice_data.number | ||
133 | + sagital = position % image_width | ||
134 | + | ||
135 | + elif self.orientation == 'SAGITAL': | ||
136 | + image_width = self.slice_actor.GetInput().GetDimensions()[1] | ||
137 | + axial = position / image_width | ||
138 | + coronal = position % image_width | ||
139 | + sagital = self.slice_data.number | ||
140 | + | ||
141 | + return sagital, coronal, axial | ||
142 | + | ||
143 | + def ScrollSlice(self, coord): | ||
144 | + if self.orientation == "AXIAL": | ||
145 | + Publisher.sendMessage(('Set scroll position', 'SAGITAL'), | ||
146 | + coord[0]) | ||
147 | + Publisher.sendMessage(('Set scroll position', 'CORONAL'), | ||
148 | + coord[1]) | ||
149 | + elif self.orientation == "SAGITAL": | ||
150 | + Publisher.sendMessage(('Set scroll position', 'AXIAL'), | ||
151 | + coord[2]) | ||
152 | + Publisher.sendMessage(('Set scroll position', 'CORONAL'), | ||
153 | + coord[1]) | ||
154 | + elif self.orientation == "CORONAL": | ||
155 | + Publisher.sendMessage(('Set scroll position', 'AXIAL'), | ||
156 | + coord[2]) | ||
157 | + Publisher.sendMessage(('Set scroll position', 'SAGITAL'), | ||
158 | + coord[0]) | ||
159 | + | ||
160 | + | ||
24 | class ViewerStyle: | 161 | class ViewerStyle: |
25 | def __init__(self): | 162 | def __init__(self): |
26 | self.interactor = None | 163 | self.interactor = None |
invesalius/data/viewer_slice.py
@@ -27,6 +27,8 @@ import numpy | @@ -27,6 +27,8 @@ import numpy | ||
27 | import vtk | 27 | import vtk |
28 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor | 28 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
29 | 29 | ||
30 | +import styles | ||
31 | + | ||
30 | import wx | 32 | import wx |
31 | from wx.lib.pubsub import pub as Publisher | 33 | from wx.lib.pubsub import pub as Publisher |
32 | 34 | ||
@@ -173,127 +175,142 @@ class Viewer(wx.Panel): | @@ -173,127 +175,142 @@ class Viewer(wx.Panel): | ||
173 | interactor.SetInteractorStyle(style) | 175 | interactor.SetInteractorStyle(style) |
174 | 176 | ||
175 | def SetInteractorStyle(self, state): | 177 | def SetInteractorStyle(self, state): |
176 | - self.state = state | ||
177 | - action = {const.SLICE_STATE_CROSS: | ||
178 | - { | ||
179 | - "MouseMoveEvent": self.OnCrossMove, | ||
180 | - "LeftButtonPressEvent": self.OnCrossMouseClick, | ||
181 | - }, | ||
182 | - const.SLICE_STATE_EDITOR: | ||
183 | - { | ||
184 | - "MouseMoveEvent": self.OnBrushMove, | ||
185 | - "LeftButtonPressEvent": self.OnBrushClick, | ||
186 | - "LeftButtonReleaseEvent": self.OnBrushRelease, | ||
187 | - "EnterEvent": self.OnEnterInteractor, | ||
188 | - "LeaveEvent": self.OnLeaveInteractor | ||
189 | - }, | ||
190 | - const.STATE_PAN: | ||
191 | - { | ||
192 | - "MouseMoveEvent": self.OnPanMove, | ||
193 | - "LeftButtonPressEvent": self.OnPanClick, | ||
194 | - "LeftButtonReleaseEvent": self.OnVtkRightRelease | ||
195 | - }, | ||
196 | - const.STATE_SPIN: | ||
197 | - { | ||
198 | - "MouseMoveEvent": self.OnSpinMove, | ||
199 | - "LeftButtonPressEvent": self.OnSpinClick, | ||
200 | - "LeftButtonReleaseEvent": self.OnVtkRightRelease | ||
201 | - }, | ||
202 | - const.STATE_ZOOM: | ||
203 | - { | ||
204 | - "MouseMoveEvent": self.OnZoomMoveLeft, | ||
205 | - "LeftButtonPressEvent": self.OnZoomLeftClick, | ||
206 | - "LeftButtonReleaseEvent": self.OnVtkRightRelease | ||
207 | - }, | ||
208 | - const.SLICE_STATE_SCROLL: | ||
209 | - { | ||
210 | - "MouseMoveEvent": self.OnChangeSliceMove, | ||
211 | - "LeftButtonPressEvent": self.OnChangeSliceClick, | ||
212 | - }, | ||
213 | - const.STATE_WL: | ||
214 | - { | ||
215 | - "MouseMoveEvent": self.OnWindowLevelMove, | ||
216 | - "LeftButtonPressEvent": self.OnWindowLevelClick, | ||
217 | - }, | ||
218 | - const.STATE_DEFAULT: | ||
219 | - { | ||
220 | - }, | ||
221 | - const.STATE_MEASURE_DISTANCE: | ||
222 | - { | ||
223 | - "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint | ||
224 | - }, | ||
225 | - const.STATE_MEASURE_ANGLE: | ||
226 | - { | ||
227 | - "LeftButtonPressEvent": self.OnInsertAngularMeasurePoint | ||
228 | - }, | ||
229 | - } | 178 | + if state == const.SLICE_STATE_CROSS: |
179 | + style = styles.CrossInteractorStyle(self.orientation, | ||
180 | + self.slice_data) | ||
181 | + self.style = style | ||
182 | + self.interactor.SetInteractorStyle(style) | ||
183 | + self.interactor.Render() | ||
230 | 184 | ||
185 | + ## Zoom using right button | ||
186 | + #style.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | ||
187 | + #style.AddObserver("MouseMoveEvent", self.OnZoomMoveRight) | ||
188 | + #style.AddObserver("RightButtonReleaseEvent", self.OnVtkRightRelease) | ||
189 | + | ||
190 | + #Scroll change slice | ||
191 | + style.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | ||
192 | + style.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | ||
231 | 193 | ||
232 | - if state == const.SLICE_STATE_CROSS: | ||
233 | self.__set_cross_visibility(1) | 194 | self.__set_cross_visibility(1) |
234 | Publisher.sendMessage('Activate ball reference') | 195 | Publisher.sendMessage('Activate ball reference') |
235 | else: | 196 | else: |
236 | - self.__set_cross_visibility(0) | ||
237 | - Publisher.sendMessage('Deactivate ball reference') | 197 | + self.state = state |
198 | + action = { | ||
199 | + const.SLICE_STATE_EDITOR: | ||
200 | + { | ||
201 | + "MouseMoveEvent": self.OnBrushMove, | ||
202 | + "LeftButtonPressEvent": self.OnBrushClick, | ||
203 | + "LeftButtonReleaseEvent": self.OnBrushRelease, | ||
204 | + "EnterEvent": self.OnEnterInteractor, | ||
205 | + "LeaveEvent": self.OnLeaveInteractor | ||
206 | + }, | ||
207 | + const.STATE_PAN: | ||
208 | + { | ||
209 | + "MouseMoveEvent": self.OnPanMove, | ||
210 | + "LeftButtonPressEvent": self.OnPanClick, | ||
211 | + "LeftButtonReleaseEvent": self.OnVtkRightRelease | ||
212 | + }, | ||
213 | + const.STATE_SPIN: | ||
214 | + { | ||
215 | + "MouseMoveEvent": self.OnSpinMove, | ||
216 | + "LeftButtonPressEvent": self.OnSpinClick, | ||
217 | + "LeftButtonReleaseEvent": self.OnVtkRightRelease | ||
218 | + }, | ||
219 | + const.STATE_ZOOM: | ||
220 | + { | ||
221 | + "MouseMoveEvent": self.OnZoomMoveLeft, | ||
222 | + "LeftButtonPressEvent": self.OnZoomLeftClick, | ||
223 | + "LeftButtonReleaseEvent": self.OnVtkRightRelease | ||
224 | + }, | ||
225 | + const.SLICE_STATE_SCROLL: | ||
226 | + { | ||
227 | + "MouseMoveEvent": self.OnChangeSliceMove, | ||
228 | + "LeftButtonPressEvent": self.OnChangeSliceClick, | ||
229 | + }, | ||
230 | + const.STATE_WL: | ||
231 | + { | ||
232 | + "MouseMoveEvent": self.OnWindowLevelMove, | ||
233 | + "LeftButtonPressEvent": self.OnWindowLevelClick, | ||
234 | + }, | ||
235 | + const.STATE_DEFAULT: | ||
236 | + { | ||
237 | + }, | ||
238 | + const.STATE_MEASURE_DISTANCE: | ||
239 | + { | ||
240 | + "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint | ||
241 | + }, | ||
242 | + const.STATE_MEASURE_ANGLE: | ||
243 | + { | ||
244 | + "LeftButtonPressEvent": self.OnInsertAngularMeasurePoint | ||
245 | + }, | ||
246 | + } | ||
247 | + | ||
248 | + | ||
249 | + if state == const.SLICE_STATE_CROSS: | ||
250 | + self.__set_cross_visibility(1) | ||
251 | + Publisher.sendMessage('Activate ball reference') | ||
252 | + else: | ||
253 | + self.__set_cross_visibility(0) | ||
254 | + Publisher.sendMessage('Deactivate ball reference') | ||
238 | 255 | ||
239 | - if state == const.STATE_WL: | ||
240 | - self.on_wl = True | ||
241 | - self.wl_text.Show() | ||
242 | - else: | ||
243 | - self.on_wl = False | ||
244 | - self.wl_text.Hide() | 256 | + if state == const.STATE_WL: |
257 | + self.on_wl = True | ||
258 | + self.wl_text.Show() | ||
259 | + else: | ||
260 | + self.on_wl = False | ||
261 | + self.wl_text.Hide() | ||
245 | 262 | ||
246 | - self.__set_editor_cursor_visibility(0) | ||
247 | - | ||
248 | - # Bind method according to current mode | ||
249 | - if(state == const.STATE_ZOOM_SL): | ||
250 | - style = vtk.vtkInteractorStyleRubberBandZoom() | 263 | + self.__set_editor_cursor_visibility(0) |
264 | + | ||
265 | + # Bind method according to current mode | ||
266 | + if(state == const.STATE_ZOOM_SL): | ||
267 | + style = vtk.vtkInteractorStyleRubberBandZoom() | ||
251 | 268 | ||
252 | - style.AddObserver("RightButtonPressEvent", self.QuitRubberBandZoom) | ||
253 | - #style.AddObserver("RightButtonPressEvent", self.EnterRubberBandZoom) | 269 | + style.AddObserver("RightButtonPressEvent", self.QuitRubberBandZoom) |
270 | + #style.AddObserver("RightButtonPressEvent", self.EnterRubberBandZoom) | ||
254 | 271 | ||
255 | - else: | ||
256 | - style = vtk.vtkInteractorStyleImage() | ||
257 | - | ||
258 | - # Check each event available for each state | ||
259 | - for event in action[state]: | ||
260 | - # Bind event | ||
261 | - style.AddObserver(event, | ||
262 | - action[state][event]) | ||
263 | - | ||
264 | - # Common to all styles | ||
265 | - # Mouse Buttons' presses / releases | ||
266 | - style.AddObserver("LeftButtonPressEvent", self.OnLeftClick) | ||
267 | - style.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | ||
268 | - style.AddObserver("RightButtonPressEvent", self.OnRightClick) | ||
269 | - style.AddObserver("RightButtonReleaseEvent", self.OnReleaseRightButton) | ||
270 | - | ||
271 | - # Zoom using right button | ||
272 | - style.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | ||
273 | - style.AddObserver("MouseMoveEvent", self.OnZoomMoveRight) | ||
274 | - style.AddObserver("RightButtonReleaseEvent", self.OnVtkRightRelease) | ||
275 | - | ||
276 | - #Scroll change slice | ||
277 | - style.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | ||
278 | - style.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | ||
279 | - | ||
280 | - if ((state == const.STATE_ZOOM) or (state == const.STATE_ZOOM_SL)): | ||
281 | - self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnZoom) | ||
282 | - else: | ||
283 | - self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnSpinPan) | 272 | + else: |
273 | + style = vtk.vtkInteractorStyleImage() | ||
274 | + | ||
275 | + # Check each event available for each state | ||
276 | + for event in action[state]: | ||
277 | + # Bind event | ||
278 | + style.AddObserver(event, | ||
279 | + action[state][event]) | ||
280 | + | ||
281 | + # Common to all styles | ||
282 | + # Mouse Buttons' presses / releases | ||
283 | + style.AddObserver("LeftButtonPressEvent", self.OnLeftClick) | ||
284 | + style.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | ||
285 | + style.AddObserver("RightButtonPressEvent", self.OnRightClick) | ||
286 | + style.AddObserver("RightButtonReleaseEvent", self.OnReleaseRightButton) | ||
287 | + | ||
288 | + # Zoom using right button | ||
289 | + style.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | ||
290 | + style.AddObserver("MouseMoveEvent", self.OnZoomMoveRight) | ||
291 | + style.AddObserver("RightButtonReleaseEvent", self.OnVtkRightRelease) | ||
292 | + | ||
293 | + #Scroll change slice | ||
294 | + style.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | ||
295 | + style.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | ||
296 | + | ||
297 | + if ((state == const.STATE_ZOOM) or (state == const.STATE_ZOOM_SL)): | ||
298 | + self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnZoom) | ||
299 | + else: | ||
300 | + self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnSpinPan) | ||
284 | 301 | ||
285 | - # Measures are using vtkPropPicker because they need to get which actor | ||
286 | - # was picked. | ||
287 | - if state in (const.STATE_MEASURE_DISTANCE, const.STATE_MEASURE_ANGLE): | ||
288 | - self.pick = vtk.vtkPropPicker() | ||
289 | - self.interactor.SetPicker(self.pick) | ||
290 | - else: | ||
291 | - self.pick = vtk.vtkWorldPointPicker() | ||
292 | - self.interactor.SetPicker(self.pick) | 302 | + # Measures are using vtkPropPicker because they need to get which actor |
303 | + # was picked. | ||
304 | + if state in (const.STATE_MEASURE_DISTANCE, const.STATE_MEASURE_ANGLE): | ||
305 | + self.pick = vtk.vtkPropPicker() | ||
306 | + self.interactor.SetPicker(self.pick) | ||
307 | + else: | ||
308 | + self.pick = vtk.vtkWorldPointPicker() | ||
309 | + self.interactor.SetPicker(self.pick) | ||
293 | 310 | ||
294 | - self.style = style | ||
295 | - self.interactor.SetInteractorStyle(style) | ||
296 | - self.interactor.Render() | 311 | + self.style = style |
312 | + self.interactor.SetInteractorStyle(style) | ||
313 | + self.interactor.Render() | ||
297 | 314 | ||
298 | def QuitRubberBandZoom(self, evt, obj): | 315 | def QuitRubberBandZoom(self, evt, obj): |
299 | style = vtk.vtkInteractorStyleImage() | 316 | style = vtk.vtkInteractorStyleImage() |
@@ -808,37 +825,6 @@ class Viewer(wx.Panel): | @@ -808,37 +825,6 @@ class Viewer(wx.Panel): | ||
808 | self.slice_.apply_slice_buffer_to_mask(self.orientation) | 825 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
809 | self._flush_buffer = False | 826 | self._flush_buffer = False |
810 | 827 | ||
811 | - def OnCrossMouseClick(self, evt, obj): | ||
812 | - self.ChangeCrossPosition() | ||
813 | - | ||
814 | - def OnCrossMove(self, evt, obj): | ||
815 | - # The user moved the mouse with left button pressed | ||
816 | - if self.left_pressed: | ||
817 | - self.ChangeCrossPosition() | ||
818 | - | ||
819 | - def ChangeCrossPosition(self): | ||
820 | - mouse_x, mouse_y = self.interactor.GetEventPosition() | ||
821 | - renderer = self.slice_data.renderer | ||
822 | - self.pick.Pick(mouse_x, mouse_y, 0, renderer) | ||
823 | - | ||
824 | - # Get in what slice data the click occurred | ||
825 | - # pick to get click position in the 3d world | ||
826 | - coord_cross = self.get_coordinate_cursor() | ||
827 | - position = self.slice_data.actor.GetInput().FindPoint(coord_cross) | ||
828 | - # Forcing focal point to be setted in the center of the pixel. | ||
829 | - coord_cross = self.slice_data.actor.GetInput().GetPoint(position) | ||
830 | - | ||
831 | - coord = self.calcultate_scroll_position(position) | ||
832 | - self.ScrollSlice(coord) | ||
833 | - | ||
834 | - Publisher.sendMessage('Update cross position', coord_cross) | ||
835 | - Publisher.sendMessage('Set ball reference position based on bound', | ||
836 | - coord_cross) | ||
837 | - Publisher.sendMessage('Set camera in volume', coord_cross) | ||
838 | - Publisher.sendMessage('Render volume viewer') | ||
839 | - | ||
840 | - self.interactor.Render() | ||
841 | - | ||
842 | def Navigation(self, pubsub_evt): | 828 | def Navigation(self, pubsub_evt): |
843 | # Get point from base change | 829 | # Get point from base change |
844 | x, y, z = pubsub_evt.data | 830 | x, y, z = pubsub_evt.data |