Commit 34f7ab2744786ef0fc220f059231a18170fd6be7
Committed by
Thiago Franco de Moraes
1 parent
8a5a3505
Exists in
master
Set target (#125)
* working * Highlight target and added target icon * The markers angles are saved now. Target ID created. -Load markers works for data with angles and/or without angles data -Load markers recognizes target ID. * bug fix * Task panel and dialog to configure coil tracking - ADD: task panel for coil registration - ADD: dialog with vtk window and coil obj * Fixed markers angles bug -Sometimes the sendmessage "Navigation Status" was arriving before the while thread is over. * testing... * Improve coil tracking and tracker communication - ADD: dialog for coil registration - ADD: panel for coil registration - ENH: coordinate recording with Polhemus * Improve Polhemus communication and trackers management - ENH: Polhemus wrapper communication, connect and disconnect - ENH: Choice of trackers - FIX: Polhemus Fastrak navigation * show/hide coil tracker -tracker angles is updating coil tracker * Just one target can be set * Uncheck camera volume box when "set target" is selected * Create target and track distance and orientation -coil turn to green when orientation is less than the threshold (accept) * Camera position and focus is based on target - set transparency on target - reposition 'distance txt' * Improve coordinates handling and coil tracking interface - ENH: coordinates managed with one array, each row is one tracker sensor - FIX: several minor bugs and wx import masking - ENH: better dialog for coil tracking - ADD: coil tracking test thread simulating navigation - ENH: dynamic reference must be applied outside coordinates module * Disabled the set target menu when navigation is off - Avoided bug if user set ID as TARGET * Temporary coil tracking pipeline - ENH: improved dialog UI for coil registration - ENH: multiple coregistration methods depending on coil registration - ADD: coil tracking to volume viewer * Minor adjustments in UI and coil tracking * Zoom camera with target distance -set arrow upper limit * Fixed distance text * Fixed camera position * Add translate of coil object in vtk matrix * Set colors * Text improvements * Changes in coil registration - ENH: improve variables and general bugs - FIX: math for coil registration almost working * Created coil tracker constants * Coil coord threshold constants * Fixed object orientation in dynamic and static navigation - FIX: coil registration in static mode (sensor 1,2 working) - ENH: object update in volume viewer improved - ENH: coregistration adpated to object tracking - TODO: allow coil registration with sensor 3 * Fixed object tracking and registration bugs - ENH: UI for object tracking - ADD: load and save object registration files - Obs: orientation tracking works for static coil mode (1 ref) - Obs: orientation tracking works for static and dynamic navigation * Wrappers are set as metric * Bug fix -Avoided bug when no data is selected and the user tries to remove marker * Removed unused icon * Code refactoring for object registration - ADD: button for object registration save in navigation panel - ADD: dynamic object registration (3 sensors) - ENH: object registration information saved - Code cleaned and removed axes for visualization * Group coil tracking interface with navigation refactoring - Merge of vhosouza and rmatsuda branchs to track coil - Navigation still not fully working - Coil registration not working properly * Enhance dynamic reference transformation equation * Created ctrls angles and dist threshold * Created option to record coordinates and angles along time * Improve object orientation tracking in dynamic reference * improvements * Trials to fix track object orientation * Fixed coils position * Created message box for objects registration * Coregistration changed to matrix multiplication - Using transformations module - Only Dynamic Reference without object is working * Attempts to coil tracking improved * Changes in object tracking algorithm - Still not working * Coregistration improvements * New changes to track object orientation * Object tracking with commented code working, axis rotation alligned * Dynamic object registration working for rotation - ENH: object registration base creation - ENH: object registration algorithm for rotation - TODO: translation and offset not working properly * Dynamic object registration trying to fix object offset * Dynamic object registration working - FIX: offset of sensor to object center * Code refactoring for navigation with object registration - FIX: Close project not restarting navigation panel - ENH: Navigation coregistration algorithms - ENH: Object data handling in viewer volume - ENH: Panels and controls in navigation task * More closing project fix for navigation mode * Enable object new, load and save buttons - FIX: minor fix in navigation interface * Improvement in object tracking panel - FIX: restart variables while closing project - ENH: object show and track checkboxes * Update to correct Neurosoft Fig 8 TMS coil model * Coil tracking improvements -TODO: remove dummy coil when set target mode is off -TODO: Enable target mode button only when target and object are set * Enable target mode button only when target and object are set -TODO: remove dummy coil when set target mode is off * Disable target mode when track obj is off * Fixed arrow directions and created text for obj registration * Crucial fix to the object registration algorithm - object was registered to the head m_head, now registered to the object basis in image space - minor changes in variables related to navigation * Camera orientation and distance text update -Dist text is showing with dummy coil actor -Camera is always orienting parallel to dummy coil actor -TODO: fix - when target mode is off the dist text and the dummy coil keeps green -TODO: fix - if the obj are not loaded the load markers (target) doesnt work properly * Fixed initial current_coord * Fixed fiducials creation markers and ENH: just showing coil when nav is on * Target is reset when target mode is off * Remove messages when project is closed * Disable ref mode for obj registration * Improvements on camera position * Set vol. camera default true - TODO: when nav is off, vol is reset to front position * Camera is not reset when nav is off, but when target_mode is off * Nav off do not set target mode off anymore
Showing
18 changed files
with
2461 additions
and
257 deletions
Show diff stats
564 Bytes
invesalius/constants.py
@@ -56,6 +56,7 @@ STEREO_ANAGLYPH = _("Anaglyph") | @@ -56,6 +56,7 @@ STEREO_ANAGLYPH = _("Anaglyph") | ||
56 | TEXT_SIZE_SMALL = 11 | 56 | TEXT_SIZE_SMALL = 11 |
57 | TEXT_SIZE = 12 | 57 | TEXT_SIZE = 12 |
58 | TEXT_SIZE_LARGE = 16 | 58 | TEXT_SIZE_LARGE = 16 |
59 | +TEXT_SIZE_EXTRA_LARGE = 20 | ||
59 | TEXT_COLOUR = (1,1,1) | 60 | TEXT_COLOUR = (1,1,1) |
60 | 61 | ||
61 | (X,Y) = (0.03, 0.97) | 62 | (X,Y) = (0.03, 0.97) |
@@ -678,6 +679,10 @@ DYNAMIC_REF = 1 | @@ -678,6 +679,10 @@ DYNAMIC_REF = 1 | ||
678 | DEFAULT_REF_MODE = DYNAMIC_REF | 679 | DEFAULT_REF_MODE = DYNAMIC_REF |
679 | REF_MODE = [_("Static ref."), _("Dynamic ref.")] | 680 | REF_MODE = [_("Static ref."), _("Dynamic ref.")] |
680 | 681 | ||
682 | +DEFAULT_COIL = SELECT | ||
683 | +COIL = [_("Select coil:"), _("Neurosoft Figure-8"), | ||
684 | + _("Magstim 70 mm"), _("Nexstim")] | ||
685 | + | ||
681 | IR1 = wx.NewId() | 686 | IR1 = wx.NewId() |
682 | IR2 = wx.NewId() | 687 | IR2 = wx.NewId() |
683 | IR3 = wx.NewId() | 688 | IR3 = wx.NewId() |
@@ -708,5 +713,34 @@ TIPS_TRK = [_("Select left ear with spatial tracker"), | @@ -708,5 +713,34 @@ TIPS_TRK = [_("Select left ear with spatial tracker"), | ||
708 | _("Select nasion with spatial tracker"), | 713 | _("Select nasion with spatial tracker"), |
709 | _("Show set coordinates in image")] | 714 | _("Show set coordinates in image")] |
710 | 715 | ||
716 | +OBJL = wx.NewId() | ||
717 | +OBJR = wx.NewId() | ||
718 | +OBJA = wx.NewId() | ||
719 | +OBJC = wx.NewId() | ||
720 | +OBJF = wx.NewId() | ||
721 | + | ||
722 | +BTNS_OBJ = {OBJL: {0: _('Left')}, | ||
723 | + OBJR: {1: _('Right')}, | ||
724 | + OBJA: {2: _('Anterior')}, | ||
725 | + OBJC: {3: _('Center')}, | ||
726 | + OBJF: {4: _('Fixed')}} | ||
727 | + | ||
728 | +TIPS_OBJ = [_("Select left object fiducial"), | ||
729 | + _("Select right object fiducial"), | ||
730 | + _("Select anterior object fiducial"), | ||
731 | + _("Select object center"), | ||
732 | + _("Attach sensor to object")] | ||
733 | + | ||
711 | CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles')) | 734 | CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles')) |
712 | MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers')) | 735 | MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers')) |
736 | + | ||
737 | +#OBJECT TRACKING | ||
738 | +OBJ_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'objects')) | ||
739 | +ARROW_SCALE = 3 | ||
740 | +ARROW_UPPER_LIMIT = 30 | ||
741 | +#COIL_ANGLES_THRESHOLD = 3 * ARROW_SCALE | ||
742 | +COIL_ANGLES_THRESHOLD = 3 | ||
743 | +COIL_COORD_THRESHOLD = 3 | ||
744 | +TIMESTAMP = 2.0 | ||
745 | + | ||
746 | +CAM_MODE = True |
invesalius/data/bases.py
1 | -from math import sqrt | 1 | +from math import sqrt, pi |
2 | import numpy as np | 2 | import numpy as np |
3 | +import invesalius.data.coordinates as dco | ||
4 | +import invesalius.data.transformations as tr | ||
3 | 5 | ||
4 | 6 | ||
5 | def angle_calculation(ap_axis, coil_axis): | 7 | def angle_calculation(ap_axis, coil_axis): |
@@ -19,7 +21,7 @@ def angle_calculation(ap_axis, coil_axis): | @@ -19,7 +21,7 @@ def angle_calculation(ap_axis, coil_axis): | ||
19 | return float(angle) | 21 | return float(angle) |
20 | 22 | ||
21 | 23 | ||
22 | -def base_creation(fiducials): | 24 | +def base_creation_old(fiducials): |
23 | """ | 25 | """ |
24 | Calculate the origin and matrix for coordinate system | 26 | Calculate the origin and matrix for coordinate system |
25 | transformation. | 27 | transformation. |
@@ -55,31 +57,70 @@ def base_creation(fiducials): | @@ -55,31 +57,70 @@ def base_creation(fiducials): | ||
55 | [g2[0], g2[1], g2[2]], | 57 | [g2[0], g2[1], g2[2]], |
56 | [g3[0], g3[1], g3[2]]]) | 58 | [g3[0], g3[1], g3[2]]]) |
57 | 59 | ||
58 | - q.shape = (3, 1) | ||
59 | - q = np.matrix(q.copy()) | ||
60 | m_inv = m.I | 60 | m_inv = m.I |
61 | 61 | ||
62 | - # print"M: ", m | ||
63 | - # print"q: ", q | 62 | + return m, q, m_inv |
63 | + | ||
64 | + | ||
65 | +def base_creation(fiducials): | ||
66 | + """ | ||
67 | + Calculate the origin and matrix for coordinate system | ||
68 | + transformation. | ||
69 | + q: origin of coordinate system | ||
70 | + g1, g2, g3: orthogonal vectors of coordinate system | ||
71 | + | ||
72 | + :param fiducials: array of 3 rows (p1, p2, p3) and 3 columns (x, y, z) with fiducials coordinates | ||
73 | + :return: matrix and origin for base transformation | ||
74 | + """ | ||
75 | + | ||
76 | + p1 = fiducials[0, :] | ||
77 | + p2 = fiducials[1, :] | ||
78 | + p3 = fiducials[2, :] | ||
79 | + | ||
80 | + sub1 = p2 - p1 | ||
81 | + sub2 = p3 - p1 | ||
82 | + lamb = (sub1[0]*sub2[0]+sub1[1]*sub2[1]+sub1[2]*sub2[2])/np.dot(sub1, sub1) | ||
83 | + | ||
84 | + q = p1 + lamb*sub1 | ||
85 | + g1 = p3 - q | ||
86 | + g2 = p1 - q | ||
87 | + | ||
88 | + if not g1.any(): | ||
89 | + g1 = p2 - q | ||
90 | + | ||
91 | + g3 = np.cross(g1, g2) | ||
92 | + | ||
93 | + g1 = g1/sqrt(np.dot(g1, g1)) | ||
94 | + g2 = g2/sqrt(np.dot(g2, g2)) | ||
95 | + g3 = g3/sqrt(np.dot(g3, g3)) | ||
96 | + | ||
97 | + m = np.matrix([[g1[0], g2[0], g3[0]], | ||
98 | + [g1[1], g2[1], g3[1]], | ||
99 | + [g1[2], g2[2], g3[2]]]) | ||
100 | + | ||
101 | + m_inv = m.I | ||
64 | 102 | ||
65 | return m, q, m_inv | 103 | return m, q, m_inv |
66 | 104 | ||
67 | 105 | ||
68 | -def calculate_fre(fiducials, minv, n, q1, q2): | 106 | +def calculate_fre(fiducials, minv, n, q, o): |
69 | """ | 107 | """ |
70 | Calculate the Fiducial Registration Error for neuronavigation. | 108 | Calculate the Fiducial Registration Error for neuronavigation. |
71 | 109 | ||
72 | :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates | 110 | :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates |
73 | :param minv: inverse matrix given by base creation | 111 | :param minv: inverse matrix given by base creation |
74 | :param n: base change matrix given by base creation | 112 | :param n: base change matrix given by base creation |
75 | - :param q1: origin of first base | ||
76 | - :param q2: origin of second base | 113 | + :param q: origin of first base |
114 | + :param o: origin of second base | ||
77 | :return: float number of fiducial registration error | 115 | :return: float number of fiducial registration error |
78 | """ | 116 | """ |
79 | 117 | ||
80 | img = np.zeros([3, 3]) | 118 | img = np.zeros([3, 3]) |
81 | dist = np.zeros([3, 1]) | 119 | dist = np.zeros([3, 1]) |
82 | 120 | ||
121 | + q1 = np.mat(q).reshape(3, 1) | ||
122 | + q2 = np.mat(o).reshape(3, 1) | ||
123 | + | ||
83 | p1 = np.mat(fiducials[3, :]).reshape(3, 1) | 124 | p1 = np.mat(fiducials[3, :]).reshape(3, 1) |
84 | p2 = np.mat(fiducials[4, :]).reshape(3, 1) | 125 | p2 = np.mat(fiducials[4, :]).reshape(3, 1) |
85 | p3 = np.mat(fiducials[5, :]).reshape(3, 1) | 126 | p3 = np.mat(fiducials[5, :]).reshape(3, 1) |
@@ -133,3 +174,98 @@ def flip_x(point): | @@ -133,3 +174,98 @@ def flip_x(point): | ||
133 | x, y, z = point_rot.tolist()[0][:3] | 174 | x, y, z = point_rot.tolist()[0][:3] |
134 | 175 | ||
135 | return x, y, z | 176 | return x, y, z |
177 | + | ||
178 | + | ||
179 | +def flip_x_m(point): | ||
180 | + """ | ||
181 | + Rotate coordinates of a vector by pi around X axis in static reference frame. | ||
182 | + | ||
183 | + InVesalius also require to multiply the z coordinate by (-1). Possibly | ||
184 | + because the origin of coordinate system of imagedata is | ||
185 | + located in superior left corner and the origin of VTK scene coordinate | ||
186 | + system (polygonal surface) is in the interior left corner. Second | ||
187 | + possibility is the order of slice stacking | ||
188 | + | ||
189 | + :param point: list of coordinates x, y and z | ||
190 | + :return: rotated coordinates | ||
191 | + """ | ||
192 | + | ||
193 | + point_4 = np.hstack((point, 1.)).reshape([4, 1]) | ||
194 | + point_4[2, 0] = -point_4[2, 0] | ||
195 | + | ||
196 | + m_rot = np.asmatrix(tr.euler_matrix(pi, 0, 0)) | ||
197 | + | ||
198 | + point_rot = m_rot*point_4 | ||
199 | + | ||
200 | + return point_rot[0, 0], point_rot[1, 0], point_rot[2, 0] | ||
201 | + | ||
202 | + | ||
203 | +def object_registration(fiducials, orients, coord_raw, m_change): | ||
204 | + """ | ||
205 | + | ||
206 | + :param fiducials: 3x3 array of fiducials translations | ||
207 | + :param orients: 3x3 array of fiducials orientations in degrees | ||
208 | + :param coord_raw: nx6 array of coordinates from tracking device where n = 1 is the reference attached to the head | ||
209 | + :param m_change: 3x3 array representing change of basis from head in tracking system to vtk head system | ||
210 | + :return: | ||
211 | + """ | ||
212 | + | ||
213 | + coords_aux = np.hstack((fiducials, orients)) | ||
214 | + mask = np.ones(len(coords_aux), dtype=bool) | ||
215 | + mask[[3]] = False | ||
216 | + coords = coords_aux[mask] | ||
217 | + | ||
218 | + fids_dyn = np.zeros([4, 6]) | ||
219 | + fids_img = np.zeros([4, 6]) | ||
220 | + fids_raw = np.zeros([3, 3]) | ||
221 | + | ||
222 | + # compute fiducials of object with reference to the fixed probe in source frame | ||
223 | + for ic in range(0, 3): | ||
224 | + fids_raw[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coords[3, :])[:3] | ||
225 | + | ||
226 | + # compute initial alignment of probe fixed in the object in source frame | ||
227 | + t_s0_raw = np.asmatrix(tr.translation_matrix(coords[3, :3])) | ||
228 | + r_s0_raw = np.asmatrix(tr.euler_matrix(np.radians(coords[3, 3]), np.radians(coords[3, 4]), | ||
229 | + np.radians(coords[3, 5]), 'rzyx')) | ||
230 | + s0_raw = np.asmatrix(tr.concatenate_matrices(t_s0_raw, r_s0_raw)) | ||
231 | + | ||
232 | + # compute change of basis for object fiducials in source frame | ||
233 | + base_obj_raw, q_obj_raw, base_inv_obj_raw = base_creation(fids_raw[:3, :3]) | ||
234 | + r_obj_raw = np.asmatrix(np.identity(4)) | ||
235 | + r_obj_raw[:3, :3] = base_obj_raw[:3, :3] | ||
236 | + t_obj_raw = np.asmatrix(tr.translation_matrix(q_obj_raw)) | ||
237 | + m_obj_raw = np.asmatrix(tr.concatenate_matrices(t_obj_raw, r_obj_raw)) | ||
238 | + | ||
239 | + for ic in range(0, 4): | ||
240 | + if coord_raw.any(): | ||
241 | + # compute object fiducials in reference frame | ||
242 | + fids_dyn[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coord_raw[1, :]) | ||
243 | + fids_dyn[ic, 2] = -fids_dyn[ic, 2] | ||
244 | + else: | ||
245 | + # compute object fiducials in source frame | ||
246 | + fids_dyn[ic, :] = coords[ic, :] | ||
247 | + | ||
248 | + # compute object fiducials in vtk head frame | ||
249 | + a, b, g = np.radians(fids_dyn[ic, 3:]) | ||
250 | + T_p = tr.translation_matrix(fids_dyn[ic, :3]) | ||
251 | + R_p = tr.euler_matrix(a, b, g, 'rzyx') | ||
252 | + M_p = np.asmatrix(tr.concatenate_matrices(T_p, R_p)) | ||
253 | + M_img = np.asmatrix(m_change) * M_p | ||
254 | + | ||
255 | + angles_img = np.degrees(np.asarray(tr.euler_from_matrix(M_img, 'rzyx'))) | ||
256 | + coord_img = np.asarray(flip_x_m(tr.translation_from_matrix(M_img))) | ||
257 | + | ||
258 | + fids_img[ic, :] = np.hstack((coord_img, angles_img)) | ||
259 | + | ||
260 | + # compute object base change in vtk head frame | ||
261 | + base_obj_img, q_obj_img, base_inv_obj_img = base_creation(fids_img[:3, :3]) | ||
262 | + r_obj_img = np.asmatrix(np.identity(4)) | ||
263 | + r_obj_img[:3, :3] = base_obj_img[:3, :3] | ||
264 | + | ||
265 | + # compute initial alignment of probe fixed in the object in reference (or static) frame | ||
266 | + s0_trans_dyn = np.asmatrix(tr.translation_matrix(fids_dyn[3, :3])) | ||
267 | + s0_rot_dyn = np.asmatrix(tr.euler_matrix(np.radians(fids_dyn[3, 3]), np.radians(fids_dyn[3, 4]), | ||
268 | + np.radians(fids_dyn[3, 5]), 'rzyx')) | ||
269 | + s0_dyn = np.asmatrix(tr.concatenate_matrices(s0_trans_dyn, s0_rot_dyn)) | ||
270 | + | ||
271 | + return t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img |
invesalius/data/coordinates.py
@@ -20,10 +20,13 @@ | @@ -20,10 +20,13 @@ | ||
20 | from math import sin, cos | 20 | from math import sin, cos |
21 | import numpy as np | 21 | import numpy as np |
22 | 22 | ||
23 | +import invesalius.data.transformations as tr | ||
24 | + | ||
23 | from time import sleep | 25 | from time import sleep |
24 | from random import uniform | 26 | from random import uniform |
25 | from wx.lib.pubsub import pub as Publisher | 27 | from wx.lib.pubsub import pub as Publisher |
26 | 28 | ||
29 | + | ||
27 | def GetCoordinates(trck_init, trck_id, ref_mode): | 30 | def GetCoordinates(trck_init, trck_id, ref_mode): |
28 | 31 | ||
29 | """ | 32 | """ |
@@ -54,7 +57,7 @@ def ClaronCoord(trck_init, trck_id, ref_mode): | @@ -54,7 +57,7 @@ def ClaronCoord(trck_init, trck_id, ref_mode): | ||
54 | scale = np.array([1.0, 1.0, -1.0]) | 57 | scale = np.array([1.0, 1.0, -1.0]) |
55 | coord = None | 58 | coord = None |
56 | k = 0 | 59 | k = 0 |
57 | - # TODO: try to replace while and use some Claron internal computation | 60 | + # TODO: try to replace 'while' and use some Claron internal computation |
58 | 61 | ||
59 | if ref_mode: | 62 | if ref_mode: |
60 | while k < 20: | 63 | while k < 20: |
@@ -77,6 +80,7 @@ def ClaronCoord(trck_init, trck_id, ref_mode): | @@ -77,6 +80,7 @@ def ClaronCoord(trck_init, trck_id, ref_mode): | ||
77 | trck.Run() | 80 | trck.Run() |
78 | coord = np.array([trck.PositionTooltipX1 * scale[0], trck.PositionTooltipY1 * scale[1], | 81 | coord = np.array([trck.PositionTooltipX1 * scale[0], trck.PositionTooltipY1 * scale[1], |
79 | trck.PositionTooltipZ1 * scale[2], trck.AngleX1, trck.AngleY1, trck.AngleZ1]) | 82 | trck.PositionTooltipZ1 * scale[2], trck.AngleX1, trck.AngleY1, trck.AngleZ1]) |
83 | + | ||
80 | k = 30 | 84 | k = 30 |
81 | except AttributeError: | 85 | except AttributeError: |
82 | k += 1 | 86 | k += 1 |
@@ -104,27 +108,23 @@ def PolhemusCoord(trck, trck_id, ref_mode): | @@ -104,27 +108,23 @@ def PolhemusCoord(trck, trck_id, ref_mode): | ||
104 | 108 | ||
105 | def PolhemusWrapperCoord(trck, trck_id, ref_mode): | 109 | def PolhemusWrapperCoord(trck, trck_id, ref_mode): |
106 | 110 | ||
107 | - scale = 25.4 * np.array([1., 1.0, -1.0]) | ||
108 | - coord = None | 111 | + trck.Run() |
112 | + scale = 10.0 * np.array([1., 1., 1.]) | ||
109 | 113 | ||
110 | - if ref_mode: | ||
111 | - trck.Run() | ||
112 | - probe = np.array([float(trck.PositionTooltipX1), float(trck.PositionTooltipY1), | ||
113 | - float(trck.PositionTooltipZ1), float(trck.AngleX1), float(trck.AngleY1), | ||
114 | - float(trck.AngleZ1)]) | ||
115 | - reference = np.array([float(trck.PositionTooltipX2), float(trck.PositionTooltipY2), | ||
116 | - float(trck.PositionTooltipZ2), float(trck.AngleX2), float(trck.AngleY2), | ||
117 | - float(trck.AngleZ2)]) | 114 | + coord1 = np.array([float(trck.PositionTooltipX1)*scale[0], float(trck.PositionTooltipY1)*scale[1], |
115 | + float(trck.PositionTooltipZ1)*scale[2], | ||
116 | + float(trck.AngleX1), float(trck.AngleY1), float(trck.AngleZ1)]) | ||
118 | 117 | ||
119 | - if probe.all() and reference.all(): | ||
120 | - coord = dynamic_reference(probe, reference) | ||
121 | - coord = (coord[0] * scale[0], coord[1] * scale[1], coord[2] * scale[2], coord[3], coord[4], coord[5]) | 118 | + coord2 = np.array([float(trck.PositionTooltipX2)*scale[0], float(trck.PositionTooltipY2)*scale[1], |
119 | + float(trck.PositionTooltipZ2)*scale[2], | ||
120 | + float(trck.AngleX2), float(trck.AngleY2), float(trck.AngleZ2)]) | ||
121 | + coord = np.vstack([coord1, coord2]) | ||
122 | 122 | ||
123 | - else: | ||
124 | - trck.Run() | ||
125 | - coord = np.array([float(trck.PositionTooltipX1) * scale[0], float(trck.PositionTooltipY1) * scale[1], | ||
126 | - float(trck.PositionTooltipZ1) * scale[2], float(trck.AngleX1), float(trck.AngleY1), | ||
127 | - float(trck.AngleZ1)]) | 123 | + if trck_id == 2: |
124 | + coord3 = np.array([float(trck.PositionTooltipX3) * scale[0], float(trck.PositionTooltipY3) * scale[1], | ||
125 | + float(trck.PositionTooltipZ3) * scale[2], | ||
126 | + float(trck.AngleX3), float(trck.AngleY3), float(trck.AngleZ3)]) | ||
127 | + coord = np.vstack([coord, coord3]) | ||
128 | 128 | ||
129 | if trck.StylusButton: | 129 | if trck.StylusButton: |
130 | Publisher.sendMessage('PLH Stylus Button On') | 130 | Publisher.sendMessage('PLH Stylus Button On') |
@@ -213,22 +213,21 @@ def DebugCoord(trk_init, trck_id, ref_mode): | @@ -213,22 +213,21 @@ def DebugCoord(trk_init, trck_id, ref_mode): | ||
213 | :param trck_id: id of tracking device | 213 | :param trck_id: id of tracking device |
214 | :return: six coordinates x, y, z, alfa, beta and gama | 214 | :return: six coordinates x, y, z, alfa, beta and gama |
215 | """ | 215 | """ |
216 | - sleep(0.2) | ||
217 | - if ref_mode: | ||
218 | - probe = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
219 | - uniform(1, 200), uniform(1, 200), uniform(1, 200)]) | ||
220 | - reference = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
221 | - uniform(1, 200), uniform(1, 200), uniform(1, 200)]) | ||
222 | 216 | ||
223 | - coord = dynamic_reference(probe, reference) | 217 | + sleep(0.05) |
224 | 218 | ||
225 | - else: | ||
226 | - coord = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
227 | - uniform(1, 200), uniform(1, 200), uniform(1, 200)]) | 219 | + coord1 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), |
220 | + uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
221 | + | ||
222 | + coord2 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
223 | + uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
224 | + | ||
225 | + coord3 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
226 | + uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
228 | 227 | ||
229 | Publisher.sendMessage('Sensors ID', [int(uniform(0, 5)), int(uniform(0, 5))]) | 228 | Publisher.sendMessage('Sensors ID', [int(uniform(0, 5)), int(uniform(0, 5))]) |
230 | 229 | ||
231 | - return coord | 230 | + return np.vstack([coord1, coord2, coord3]) |
232 | 231 | ||
233 | 232 | ||
234 | def dynamic_reference(probe, reference): | 233 | def dynamic_reference(probe, reference): |
@@ -245,28 +244,143 @@ def dynamic_reference(probe, reference): | @@ -245,28 +244,143 @@ def dynamic_reference(probe, reference): | ||
245 | """ | 244 | """ |
246 | a, b, g = np.radians(reference[3:6]) | 245 | a, b, g = np.radians(reference[3:6]) |
247 | 246 | ||
248 | - vet = probe[0:3] - reference[0:3] | ||
249 | - vet = np.mat(vet.reshape(3, 1)) | 247 | + vet = np.asmatrix(probe[0:3] - reference[0:3]) |
248 | + # vet = np.mat(vet.reshape(3, 1)) | ||
249 | + | ||
250 | + # Attitude matrix given by Patriot manual | ||
251 | + # a: rotation of plane (X, Y) around Z axis (azimuth) | ||
252 | + # b: rotation of plane (X', Z) around Y' axis (elevation) | ||
253 | + # a: rotation of plane (Y', Z') around X'' axis (roll) | ||
254 | + m_rot = np.mat([[cos(a) * cos(b), sin(b) * sin(g) * cos(a) - cos(g) * sin(a), | ||
255 | + cos(a) * sin(b) * cos(g) + sin(a) * sin(g)], | ||
256 | + [cos(b) * sin(a), sin(b) * sin(g) * sin(a) + cos(g) * cos(a), | ||
257 | + cos(g) * sin(b) * sin(a) - sin(g) * cos(a)], | ||
258 | + [-sin(b), sin(g) * cos(b), cos(b) * cos(g)]]) | ||
259 | + | ||
260 | + # coord_rot = m_rot.T * vet | ||
261 | + coord_rot = vet*m_rot | ||
262 | + coord_rot = np.squeeze(np.asarray(coord_rot)) | ||
263 | + | ||
264 | + return coord_rot[0], coord_rot[1], -coord_rot[2], probe[3], probe[4], probe[5] | ||
250 | 265 | ||
251 | - # Attitude Matrix given by Patriot Manual | ||
252 | - Mrot = np.mat([[cos(a) * cos(b), sin(b) * sin(g) * cos(a) - cos(g) * sin(a), | ||
253 | - cos(a) * sin(b) * cos(g) + sin(a) * sin(g)], | ||
254 | - [cos(b) * sin(a), sin(b) * sin(g) * sin(a) + cos(g) * cos(a), | ||
255 | - cos(g) * sin(b) * sin(a) - sin(g) * cos(a)], | ||
256 | - [-sin(b), sin(g) * cos(b), cos(b) * cos(g)]]) | ||
257 | 266 | ||
258 | - coord_rot = Mrot.T * vet | 267 | +def dynamic_reference_m(probe, reference): |
268 | + """ | ||
269 | + Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama | ||
270 | + rotation angles of reference to rotate the probe coordinate and returns the x, y, z | ||
271 | + difference between probe and reference. Angles sequences and equation was extracted from | ||
272 | + Polhemus manual and Attitude matrix in Wikipedia. | ||
273 | + General equation is: | ||
274 | + coord = Mrot * (probe - reference) | ||
275 | + :param probe: sensor one defined as probe | ||
276 | + :param reference: sensor two defined as reference | ||
277 | + :return: rotated and translated coordinates | ||
278 | + """ | ||
279 | + | ||
280 | + a, b, g = np.radians(reference[3:6]) | ||
281 | + | ||
282 | + T = tr.translation_matrix(reference[:3]) | ||
283 | + R = tr.euler_matrix(a, b, g, 'rzyx') | ||
284 | + M = np.asmatrix(tr.concatenate_matrices(T, R)) | ||
285 | + # M = tr.compose_matrix(angles=np.radians(reference[3:6]), translate=reference[:3]) | ||
286 | + # print M | ||
287 | + probe_4 = np.vstack((np.asmatrix(probe[:3]).reshape([3, 1]), 1.)) | ||
288 | + coord_rot = M.I * probe_4 | ||
259 | coord_rot = np.squeeze(np.asarray(coord_rot)) | 289 | coord_rot = np.squeeze(np.asarray(coord_rot)) |
260 | 290 | ||
261 | - return coord_rot[0], coord_rot[1], coord_rot[2], probe[3], probe[4], probe[5] | 291 | + return coord_rot[0], coord_rot[1], -coord_rot[2], probe[3], probe[4], probe[5] |
292 | + | ||
293 | +def dynamic_reference_m2(probe, reference): | ||
294 | + """ | ||
295 | + Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama | ||
296 | + rotation angles of reference to rotate the probe coordinate and returns the x, y, z | ||
297 | + difference between probe and reference. Angles sequences and equation was extracted from | ||
298 | + Polhemus manual and Attitude matrix in Wikipedia. | ||
299 | + General equation is: | ||
300 | + coord = Mrot * (probe - reference) | ||
301 | + :param probe: sensor one defined as probe | ||
302 | + :param reference: sensor two defined as reference | ||
303 | + :return: rotated and translated coordinates | ||
304 | + """ | ||
305 | + | ||
306 | + a, b, g = np.radians(reference[3:6]) | ||
307 | + a_p, b_p, g_p = np.radians(probe[3:6]) | ||
308 | + | ||
309 | + T = tr.translation_matrix(reference[:3]) | ||
310 | + T_p = tr.translation_matrix(probe[:3]) | ||
311 | + R = tr.euler_matrix(a, b, g, 'rzyx') | ||
312 | + R_p = tr.euler_matrix(a_p, b_p, g_p, 'rzyx') | ||
313 | + M = np.asmatrix(tr.concatenate_matrices(T, R)) | ||
314 | + M_p = np.asmatrix(tr.concatenate_matrices(T_p, R_p)) | ||
315 | + # M = tr.compose_matrix(angles=np.radians(reference[3:6]), translate=reference[:3]) | ||
316 | + # print M | ||
317 | + | ||
318 | + M_dyn = M.I * M_p | ||
319 | + | ||
320 | + al, be, ga = tr.euler_from_matrix(M_dyn, 'rzyx') | ||
321 | + coord_rot = tr.translation_from_matrix(M_dyn) | ||
322 | + | ||
323 | + coord_rot = np.squeeze(coord_rot) | ||
324 | + | ||
325 | + # probe_4 = np.vstack((np.asmatrix(probe[:3]).reshape([3, 1]), 1.)) | ||
326 | + # coord_rot_test = M.I * probe_4 | ||
327 | + # coord_rot_test = np.squeeze(np.asarray(coord_rot_test)) | ||
328 | + # | ||
329 | + # print "coord_rot: ", coord_rot | ||
330 | + # print "coord_rot_test: ", coord_rot_test | ||
331 | + # print "test: ", np.allclose(coord_rot, coord_rot_test[:3]) | ||
332 | + | ||
333 | + return coord_rot[0], coord_rot[1], coord_rot[2], np.degrees(al), np.degrees(be), np.degrees(ga) | ||
334 | + | ||
335 | +# def dynamic_reference_m3(probe, reference): | ||
336 | +# """ | ||
337 | +# Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama | ||
338 | +# rotation angles of reference to rotate the probe coordinate and returns the x, y, z | ||
339 | +# difference between probe and reference. Angles sequences and equation was extracted from | ||
340 | +# Polhemus manual and Attitude matrix in Wikipedia. | ||
341 | +# General equation is: | ||
342 | +# coord = Mrot * (probe - reference) | ||
343 | +# :param probe: sensor one defined as probe | ||
344 | +# :param reference: sensor two defined as reference | ||
345 | +# :return: rotated and translated coordinates | ||
346 | +# """ | ||
347 | +# | ||
348 | +# a, b, g = np.radians(reference[3:6]) | ||
349 | +# a_p, b_p, g_p = np.radians(probe[3:6]) | ||
350 | +# | ||
351 | +# T = tr.translation_matrix(reference[:3]) | ||
352 | +# T_p = tr.translation_matrix(probe[:3]) | ||
353 | +# R = tr.euler_matrix(a, b, g, 'rzyx') | ||
354 | +# R_p = tr.euler_matrix(a_p, b_p, g_p, 'rzyx') | ||
355 | +# M = np.asmatrix(tr.concatenate_matrices(T, R)) | ||
356 | +# M_p = np.asmatrix(tr.concatenate_matrices(T_p, R_p)) | ||
357 | +# # M = tr.compose_matrix(angles=np.radians(reference[3:6]), translate=reference[:3]) | ||
358 | +# # print M | ||
359 | +# | ||
360 | +# M_dyn = M.I * M_p | ||
361 | +# | ||
362 | +# # al, be, ga = tr.euler_from_matrix(M_dyn, 'rzyx') | ||
363 | +# # coord_rot = tr.translation_from_matrix(M_dyn) | ||
364 | +# # | ||
365 | +# # coord_rot = np.squeeze(coord_rot) | ||
366 | +# | ||
367 | +# # probe_4 = np.vstack((np.asmatrix(probe[:3]).reshape([3, 1]), 1.)) | ||
368 | +# # coord_rot_test = M.I * probe_4 | ||
369 | +# # coord_rot_test = np.squeeze(np.asarray(coord_rot_test)) | ||
370 | +# # | ||
371 | +# # print "coord_rot: ", coord_rot | ||
372 | +# # print "coord_rot_test: ", coord_rot_test | ||
373 | +# # print "test: ", np.allclose(coord_rot, coord_rot_test[:3]) | ||
374 | +# | ||
375 | +# return M_dyn | ||
262 | 376 | ||
263 | 377 | ||
264 | def str2float(data): | 378 | def str2float(data): |
265 | """ | 379 | """ |
266 | - Converts string detected wth Polhemus device to float array of coordinates. THis method applies | 380 | + Converts string detected wth Polhemus device to float array of coordinates. This method applies |
267 | a correction for the minus sign in string that raises error while splitting the string into coordinates. | 381 | a correction for the minus sign in string that raises error while splitting the string into coordinates. |
268 | :param data: string of coordinates read with Polhemus | 382 | :param data: string of coordinates read with Polhemus |
269 | - :return: six float coordinates x, y, z, alfa, beta and gama | 383 | + :return: six float coordinates x, y, z, alpha, beta and gamma |
270 | """ | 384 | """ |
271 | 385 | ||
272 | count = 0 | 386 | count = 0 |
invesalius/data/coregistration.py
@@ -20,16 +20,123 @@ | @@ -20,16 +20,123 @@ | ||
20 | import threading | 20 | import threading |
21 | from time import sleep | 21 | from time import sleep |
22 | 22 | ||
23 | -from numpy import mat | 23 | +from numpy import asmatrix, mat, degrees, radians, identity |
24 | import wx | 24 | import wx |
25 | from wx.lib.pubsub import pub as Publisher | 25 | from wx.lib.pubsub import pub as Publisher |
26 | 26 | ||
27 | import invesalius.data.coordinates as dco | 27 | import invesalius.data.coordinates as dco |
28 | +import invesalius.data.transformations as tr | ||
28 | 29 | ||
29 | # TODO: Optimize navigation thread. Remove the infinite loop and optimize sleep. | 30 | # TODO: Optimize navigation thread. Remove the infinite loop and optimize sleep. |
30 | 31 | ||
31 | 32 | ||
32 | -class Coregistration(threading.Thread): | 33 | +class CoregistrationStatic(threading.Thread): |
34 | + """ | ||
35 | + Thread to update the coordinates with the fiducial points | ||
36 | + co-registration method while the Navigation Button is pressed. | ||
37 | + Sleep function in run method is used to avoid blocking GUI and | ||
38 | + for better real-time navigation | ||
39 | + """ | ||
40 | + | ||
41 | + def __init__(self, coreg_data, nav_id, trck_info): | ||
42 | + threading.Thread.__init__(self) | ||
43 | + self.coreg_data = coreg_data | ||
44 | + self.nav_id = nav_id | ||
45 | + self.trck_info = trck_info | ||
46 | + self._pause_ = False | ||
47 | + self.start() | ||
48 | + | ||
49 | + def stop(self): | ||
50 | + self._pause_ = True | ||
51 | + | ||
52 | + def run(self): | ||
53 | + # m_change = self.coreg_data[0] | ||
54 | + # obj_ref_mode = self.coreg_data[2] | ||
55 | + # | ||
56 | + # trck_init = self.trck_info[0] | ||
57 | + # trck_id = self.trck_info[1] | ||
58 | + # trck_mode = self.trck_info[2] | ||
59 | + | ||
60 | + m_change, obj_ref_mode = self.coreg_data | ||
61 | + trck_init, trck_id, trck_mode = self.trck_info | ||
62 | + | ||
63 | + while self.nav_id: | ||
64 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
65 | + | ||
66 | + psi, theta, phi = coord_raw[obj_ref_mode, 3:] | ||
67 | + t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
68 | + | ||
69 | + t_probe_raw[2, -1] = -t_probe_raw[2, -1] | ||
70 | + | ||
71 | + m_img = m_change * t_probe_raw | ||
72 | + | ||
73 | + coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], psi, theta, phi | ||
74 | + | ||
75 | + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', (m_img, coord)) | ||
76 | + | ||
77 | + # TODO: Optimize the value of sleep for each tracking device. | ||
78 | + sleep(0.175) | ||
79 | + | ||
80 | + if self._pause_: | ||
81 | + return | ||
82 | + | ||
83 | + | ||
84 | +class CoregistrationDynamic(threading.Thread): | ||
85 | + """ | ||
86 | + Thread to update the coordinates with the fiducial points | ||
87 | + co-registration method while the Navigation Button is pressed. | ||
88 | + Sleep function in run method is used to avoid blocking GUI and | ||
89 | + for better real-time navigation | ||
90 | + """ | ||
91 | + | ||
92 | + def __init__(self, coreg_data, nav_id, trck_info): | ||
93 | + threading.Thread.__init__(self) | ||
94 | + self.coreg_data = coreg_data | ||
95 | + self.nav_id = nav_id | ||
96 | + self.trck_info = trck_info | ||
97 | + self._pause_ = False | ||
98 | + self.start() | ||
99 | + | ||
100 | + def stop(self): | ||
101 | + self._pause_ = True | ||
102 | + | ||
103 | + def run(self): | ||
104 | + m_change, obj_ref_mode = self.coreg_data | ||
105 | + trck_init, trck_id, trck_mode = self.trck_info | ||
106 | + | ||
107 | + while self.nav_id: | ||
108 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
109 | + | ||
110 | + psi, theta, phi = radians(coord_raw[obj_ref_mode, 3:]) | ||
111 | + r_probe = tr.euler_matrix(psi, theta, phi, 'rzyx') | ||
112 | + t_probe = tr.translation_matrix(coord_raw[obj_ref_mode, :3]) | ||
113 | + m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
114 | + | ||
115 | + psi_ref, theta_ref, phi_ref = radians(coord_raw[1, 3:]) | ||
116 | + r_ref = tr.euler_matrix(psi_ref, theta_ref, phi_ref, 'rzyx') | ||
117 | + t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
118 | + m_ref = asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
119 | + | ||
120 | + m_dyn = m_ref.I * m_probe | ||
121 | + m_dyn[2, -1] = -m_dyn[2, -1] | ||
122 | + | ||
123 | + m_img = m_change * m_dyn | ||
124 | + | ||
125 | + scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
126 | + | ||
127 | + coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
128 | + degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
129 | + | ||
130 | + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', (m_img, coord)) | ||
131 | + | ||
132 | + # TODO: Optimize the value of sleep for each tracking device. | ||
133 | + sleep(0.175) | ||
134 | + | ||
135 | + if self._pause_: | ||
136 | + return | ||
137 | + | ||
138 | + | ||
139 | +class CoregistrationDynamic_old(threading.Thread): | ||
33 | """ | 140 | """ |
34 | Thread to update the coordinates with the fiducial points | 141 | Thread to update the coordinates with the fiducial points |
35 | co-registration method while the Navigation Button is pressed. | 142 | co-registration method while the Navigation Button is pressed. |
@@ -44,10 +151,10 @@ class Coregistration(threading.Thread): | @@ -44,10 +151,10 @@ class Coregistration(threading.Thread): | ||
44 | self.trck_info = trck_info | 151 | self.trck_info = trck_info |
45 | self._pause_ = False | 152 | self._pause_ = False |
46 | self.start() | 153 | self.start() |
47 | - | 154 | + |
48 | def stop(self): | 155 | def stop(self): |
49 | self._pause_ = True | 156 | self._pause_ = True |
50 | - | 157 | + |
51 | def run(self): | 158 | def run(self): |
52 | m_inv = self.bases[0] | 159 | m_inv = self.bases[0] |
53 | n = self.bases[1] | 160 | n = self.bases[1] |
@@ -58,24 +165,199 @@ class Coregistration(threading.Thread): | @@ -58,24 +165,199 @@ class Coregistration(threading.Thread): | ||
58 | trck_mode = self.trck_info[2] | 165 | trck_mode = self.trck_info[2] |
59 | 166 | ||
60 | while self.nav_id: | 167 | while self.nav_id: |
61 | - trck_coord = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
62 | - trck_xyz = mat([[trck_coord[0]], [trck_coord[1]], [trck_coord[2]]]) | 168 | + # trck_coord, probe, reference = dco.GetCoordinates(trck_init, trck_id, trck_mode) |
169 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
63 | 170 | ||
64 | - img = q1 + (m_inv*n)*(trck_xyz - q2) | 171 | + trck_coord = dco.dynamic_reference(coord_raw[0, :], coord_raw[1, :]) |
172 | + | ||
173 | + trck_xyz = mat([[trck_coord[0]], [trck_coord[1]], [trck_coord[2]]]) | ||
174 | + img = q1 + (m_inv * n) * (trck_xyz - q2) | ||
65 | 175 | ||
66 | coord = (float(img[0]), float(img[1]), float(img[2]), trck_coord[3], | 176 | coord = (float(img[0]), float(img[1]), float(img[2]), trck_coord[3], |
67 | trck_coord[4], trck_coord[5]) | 177 | trck_coord[4], trck_coord[5]) |
178 | + angles = coord_raw[0, 3:6] | ||
68 | 179 | ||
69 | # Tried several combinations and different locations to send the messages, | 180 | # Tried several combinations and different locations to send the messages, |
70 | # however only this one does not block the GUI during navigation. | 181 | # however only this one does not block the GUI during navigation. |
71 | - wx.CallAfter(Publisher.sendMessage, 'Co-registered points', coord[0:3]) | ||
72 | - wx.CallAfter(Publisher.sendMessage, 'Set camera in volume', coord[0:3]) | 182 | + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', coord) |
183 | + wx.CallAfter(Publisher.sendMessage, 'Set camera in volume', coord) | ||
184 | + wx.CallAfter(Publisher.sendMessage, 'Update tracker angles', angles) | ||
73 | 185 | ||
74 | # TODO: Optimize the value of sleep for each tracking device. | 186 | # TODO: Optimize the value of sleep for each tracking device. |
75 | # Debug tracker is not working with 0.175 so changed to 0.2 | 187 | # Debug tracker is not working with 0.175 so changed to 0.2 |
76 | # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | 188 | # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. |
77 | - #sleep(.3) | 189 | + # sleep(.3) |
190 | + sleep(0.175) | ||
191 | + | ||
192 | + if self._pause_: | ||
193 | + return | ||
194 | + | ||
195 | + | ||
196 | +class CoregistrationObjectStatic(threading.Thread): | ||
197 | + """ | ||
198 | + Thread to update the coordinates with the fiducial points | ||
199 | + co-registration method while the Navigation Button is pressed. | ||
200 | + Sleep function in run method is used to avoid blocking GUI and | ||
201 | + for better real-time navigation | ||
202 | + """ | ||
203 | + | ||
204 | + def __init__(self, coreg_data, nav_id, trck_info): | ||
205 | + threading.Thread.__init__(self) | ||
206 | + self.coreg_data = coreg_data | ||
207 | + self.nav_id = nav_id | ||
208 | + self.trck_info = trck_info | ||
209 | + self._pause_ = False | ||
210 | + self.start() | ||
211 | + | ||
212 | + def stop(self): | ||
213 | + self._pause_ = True | ||
214 | + | ||
215 | + def run(self): | ||
216 | + # m_change = self.coreg_data[0] | ||
217 | + # t_obj_raw = self.coreg_data[1] | ||
218 | + # s0_raw = self.coreg_data[2] | ||
219 | + # r_s0_raw = self.coreg_data[3] | ||
220 | + # s0_dyn = self.coreg_data[4] | ||
221 | + # m_obj_raw = self.coreg_data[5] | ||
222 | + # r_obj_img = self.coreg_data[6] | ||
223 | + # obj_ref_mode = self.coreg_data[7] | ||
224 | + # | ||
225 | + # trck_init = self.trck_info[0] | ||
226 | + # trck_id = self.trck_info[1] | ||
227 | + # trck_mode = self.trck_info[2] | ||
228 | + | ||
229 | + m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = self.coreg_data | ||
230 | + trck_init, trck_id, trck_mode = self.trck_info | ||
231 | + | ||
232 | + while self.nav_id: | ||
233 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
234 | + | ||
235 | + as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
236 | + r_probe = asmatrix(tr.euler_matrix(as1, bs1, gs1, 'rzyx')) | ||
237 | + t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
238 | + t_offset_aux = r_s0_raw.I * r_probe * t_obj_raw | ||
239 | + t_offset = asmatrix(identity(4)) | ||
240 | + t_offset[:, -1] = t_offset_aux[:, -1] | ||
241 | + t_probe = s0_raw * t_offset * s0_raw.I * t_probe_raw | ||
242 | + m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
243 | + | ||
244 | + m_probe[2, -1] = -m_probe[2, -1] | ||
245 | + | ||
246 | + m_img = m_change * m_probe | ||
247 | + r_obj = r_obj_img * m_obj_raw.I * s0_dyn.I * m_probe * m_obj_raw | ||
248 | + | ||
249 | + m_img[:3, :3] = r_obj[:3, :3] | ||
250 | + | ||
251 | + scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
252 | + | ||
253 | + coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
254 | + degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
255 | + | ||
256 | + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', (m_img, coord)) | ||
257 | + wx.CallAfter(Publisher.sendMessage, 'Update object matrix', (m_img, coord)) | ||
258 | + | ||
259 | + # TODO: Optimize the value of sleep for each tracking device. | ||
78 | sleep(0.175) | 260 | sleep(0.175) |
79 | 261 | ||
262 | + # Debug tracker is not working with 0.175 so changed to 0.2 | ||
263 | + # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
264 | + # sleep(.3) | ||
265 | + | ||
266 | + # partially working for translate and offset, | ||
267 | + # but offset is kept always in same axis, have to fix for rotation | ||
268 | + # M_dyn = M_reference.I * T_stylus | ||
269 | + # M_dyn[2, -1] = -M_dyn[2, -1] | ||
270 | + # M_dyn_ch = M_change * M_dyn | ||
271 | + # ddd = M_dyn_ch[0, -1], M_dyn_ch[1, -1], M_dyn_ch[2, -1] | ||
272 | + # M_dyn_ch[:3, -1] = asmatrix(db.flip_x_m(ddd)).reshape([3, 1]) | ||
273 | + # M_final = S0 * M_obj_trans_0 * S0.I * M_dyn_ch | ||
274 | + | ||
275 | + # this works for static reference object rotation | ||
276 | + # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_raw.I * R_stylus * M_obj_rot_raw | ||
277 | + # this works for dynamic reference in rotation but not in translation | ||
278 | + # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_dyn.I * R_reference.I * R_stylus * M_obj_rot_raw | ||
279 | + | ||
80 | if self._pause_: | 280 | if self._pause_: |
81 | return | 281 | return |
282 | + | ||
283 | + | ||
284 | +class CoregistrationObjectDynamic(threading.Thread): | ||
285 | + """ | ||
286 | + Thread to update the coordinates with the fiducial points | ||
287 | + co-registration method while the Navigation Button is pressed. | ||
288 | + Sleep function in run method is used to avoid blocking GUI and | ||
289 | + for better real-time navigation | ||
290 | + """ | ||
291 | + | ||
292 | + def __init__(self, coreg_data, nav_id, trck_info): | ||
293 | + threading.Thread.__init__(self) | ||
294 | + self.coreg_data = coreg_data | ||
295 | + self.nav_id = nav_id | ||
296 | + self.trck_info = trck_info | ||
297 | + self._pause_ = False | ||
298 | + self.start() | ||
299 | + | ||
300 | + def stop(self): | ||
301 | + self._pause_ = True | ||
302 | + | ||
303 | + def run(self): | ||
304 | + | ||
305 | + m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = self.coreg_data | ||
306 | + trck_init, trck_id, trck_mode = self.trck_info | ||
307 | + | ||
308 | + while self.nav_id: | ||
309 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
310 | + | ||
311 | + as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
312 | + r_probe = asmatrix(tr.euler_matrix(as1, bs1, gs1, 'rzyx')) | ||
313 | + t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
314 | + t_offset_aux = r_s0_raw.I * r_probe * t_obj_raw | ||
315 | + t_offset = asmatrix(identity(4)) | ||
316 | + t_offset[:, -1] = t_offset_aux[:, -1] | ||
317 | + t_probe = s0_raw * t_offset * s0_raw.I * t_probe_raw | ||
318 | + m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
319 | + | ||
320 | + a, b, g = radians(coord_raw[1, 3:]) | ||
321 | + r_ref = tr.euler_matrix(a, b, g, 'rzyx') | ||
322 | + t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
323 | + m_ref = asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
324 | + | ||
325 | + m_dyn = m_ref.I * m_probe | ||
326 | + m_dyn[2, -1] = -m_dyn[2, -1] | ||
327 | + | ||
328 | + m_img = m_change * m_dyn | ||
329 | + r_obj = r_obj_img * m_obj_raw.I * s0_dyn.I * m_dyn * m_obj_raw | ||
330 | + | ||
331 | + m_img[:3, :3] = r_obj[:3, :3] | ||
332 | + | ||
333 | + scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
334 | + | ||
335 | + coord = m_img[0, -1], m_img[1, -1], m_img[2, -1],\ | ||
336 | + degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
337 | + | ||
338 | + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', (m_img, coord)) | ||
339 | + wx.CallAfter(Publisher.sendMessage, 'Update object matrix', (m_img, coord)) | ||
340 | + | ||
341 | + # TODO: Optimize the value of sleep for each tracking device. | ||
342 | + sleep(0.175) | ||
343 | + | ||
344 | + # Debug tracker is not working with 0.175 so changed to 0.2 | ||
345 | + # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
346 | + # sleep(.3) | ||
347 | + | ||
348 | + # partially working for translate and offset, | ||
349 | + # but offset is kept always in same axis, have to fix for rotation | ||
350 | + # M_dyn = M_reference.I * T_stylus | ||
351 | + # M_dyn[2, -1] = -M_dyn[2, -1] | ||
352 | + # M_dyn_ch = M_change * M_dyn | ||
353 | + # ddd = M_dyn_ch[0, -1], M_dyn_ch[1, -1], M_dyn_ch[2, -1] | ||
354 | + # M_dyn_ch[:3, -1] = asmatrix(db.flip_x_m(ddd)).reshape([3, 1]) | ||
355 | + # M_final = S0 * M_obj_trans_0 * S0.I * M_dyn_ch | ||
356 | + | ||
357 | + # this works for static reference object rotation | ||
358 | + # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_raw.I * R_stylus * M_obj_rot_raw | ||
359 | + # this works for dynamic reference in rotation but not in translation | ||
360 | + # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_dyn.I * R_reference.I * R_stylus * M_obj_rot_raw | ||
361 | + | ||
362 | + if self._pause_: | ||
363 | + return | ||
82 | \ No newline at end of file | 364 | \ No newline at end of file |
@@ -0,0 +1,67 @@ | @@ -0,0 +1,67 @@ | ||
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 threading | ||
21 | +import time | ||
22 | + | ||
23 | +import wx | ||
24 | +from numpy import array, savetxt, hstack,vstack, asarray | ||
25 | +import invesalius.gui.dialogs as dlg | ||
26 | +from wx.lib.pubsub import pub as Publisher | ||
27 | + | ||
28 | + | ||
29 | +class Record(threading.Thread): | ||
30 | + """ | ||
31 | + Thread created to save obj coords with software during neuronavigation | ||
32 | + """ | ||
33 | + | ||
34 | + def __init__(self, nav_id, timestamp): | ||
35 | + threading.Thread.__init__(self) | ||
36 | + self.nav_id = nav_id | ||
37 | + self.coord = None | ||
38 | + self.timestamp = timestamp | ||
39 | + self.coord_list = array([]) | ||
40 | + self.__bind_events() | ||
41 | + self._pause_ = False | ||
42 | + self.start() | ||
43 | + | ||
44 | + def __bind_events(self): | ||
45 | + Publisher.subscribe(self.UpdateCurrentCoords, 'Co-registered points') | ||
46 | + | ||
47 | + def UpdateCurrentCoords(self, pubsub_evt): | ||
48 | + self.coord = asarray(pubsub_evt.data[1]) | ||
49 | + | ||
50 | + def stop(self): | ||
51 | + self._pause_ = True | ||
52 | + #save coords dialog | ||
53 | + filename = dlg.ShowSaveCoordsDialog("coords.csv") | ||
54 | + if filename: | ||
55 | + savetxt(filename, self.coord_list, delimiter=',', fmt='%.4f', header="time, x, y, z, a, b, g", comments="") | ||
56 | + | ||
57 | + def run(self): | ||
58 | + initial_time = time.time() | ||
59 | + while self.nav_id: | ||
60 | + relative_time = asarray(time.time() - initial_time) | ||
61 | + time.sleep(self.timestamp) | ||
62 | + if self.coord_list.size == 0: | ||
63 | + self.coord_list = hstack((relative_time, self.coord)) | ||
64 | + else: | ||
65 | + self.coord_list = vstack((self.coord_list, hstack((relative_time, self.coord)))) | ||
66 | + if self._pause_: | ||
67 | + return | ||
0 | \ No newline at end of file | 68 | \ No newline at end of file |
invesalius/data/styles.py
@@ -238,7 +238,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): | @@ -238,7 +238,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): | ||
238 | Publisher.sendMessage('Update cross position', (wx, wy, wz)) | 238 | Publisher.sendMessage('Update cross position', (wx, wy, wz)) |
239 | self.ScrollSlice(coord) | 239 | self.ScrollSlice(coord) |
240 | Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) | 240 | Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) |
241 | - Publisher.sendMessage('Set camera in volume', (wx, wy, wz)) | 241 | + Publisher.sendMessage('Co-registered points', (None, (wx, wy, wz, 0., 0., 0.))) |
242 | 242 | ||
243 | iren.Render() | 243 | iren.Render() |
244 | 244 |
invesalius/data/trackers.py
@@ -21,18 +21,19 @@ | @@ -21,18 +21,19 @@ | ||
21 | # TODO: Test if there are too many prints when connection fails | 21 | # TODO: Test if there are too many prints when connection fails |
22 | 22 | ||
23 | 23 | ||
24 | -def TrackerConnection(tracker_id, action): | 24 | +def TrackerConnection(tracker_id, trck_init, action): |
25 | """ | 25 | """ |
26 | - Initialize spatial trackers for coordinate detection during navigation. | 26 | + Initialize or disconnect spatial trackers for coordinate detection during navigation. |
27 | 27 | ||
28 | :param tracker_id: ID of tracking device. | 28 | :param tracker_id: ID of tracking device. |
29 | + :param trck_init: tracker initialization instance. | ||
29 | :param action: string with to decide whether connect or disconnect the selected device. | 30 | :param action: string with to decide whether connect or disconnect the selected device. |
30 | :return spatial tracker initialization instance or None if could not open device. | 31 | :return spatial tracker initialization instance or None if could not open device. |
31 | """ | 32 | """ |
32 | 33 | ||
33 | if action == 'connect': | 34 | if action == 'connect': |
34 | trck_fcn = {1: ClaronTracker, | 35 | trck_fcn = {1: ClaronTracker, |
35 | - 2: PolhemusTrackerFT, # FASTRAK | 36 | + 2: PolhemusTracker, # FASTRAK |
36 | 3: PolhemusTracker, # ISOTRAK | 37 | 3: PolhemusTracker, # ISOTRAK |
37 | 4: PolhemusTracker, # PATRIOT | 38 | 4: PolhemusTracker, # PATRIOT |
38 | 5: DebugTracker} | 39 | 5: DebugTracker} |
@@ -40,7 +41,7 @@ def TrackerConnection(tracker_id, action): | @@ -40,7 +41,7 @@ def TrackerConnection(tracker_id, action): | ||
40 | trck_init = trck_fcn[tracker_id](tracker_id) | 41 | trck_init = trck_fcn[tracker_id](tracker_id) |
41 | 42 | ||
42 | elif action == 'disconnect': | 43 | elif action == 'disconnect': |
43 | - trck_init = DisconnectTracker(tracker_id) | 44 | + trck_init = DisconnectTracker(tracker_id, trck_init) |
44 | 45 | ||
45 | return trck_init | 46 | return trck_init |
46 | 47 | ||
@@ -87,29 +88,10 @@ def ClaronTracker(tracker_id): | @@ -87,29 +88,10 @@ def ClaronTracker(tracker_id): | ||
87 | 88 | ||
88 | return trck_init, lib_mode | 89 | return trck_init, lib_mode |
89 | 90 | ||
90 | -def PolhemusTrackerFT(tracker_id): | ||
91 | - trck_init = None | ||
92 | - lib_mode = 'wrapper' | ||
93 | - try: | ||
94 | - import polhemusFT | ||
95 | - | ||
96 | - trck_init = polhemusFT.polhemusFT() | ||
97 | - trck_check = trck_init.Initialize() | ||
98 | - | ||
99 | - if trck_check: | ||
100 | - # First run is necessary to discard the first coord collection | ||
101 | - trck_init.Run() | ||
102 | - else: | ||
103 | - trck_init = trck_check | ||
104 | - except: | ||
105 | - print 'Could not connect to Polhemus via wrapper.' | ||
106 | - | ||
107 | - return trck_init, lib_mode | ||
108 | 91 | ||
109 | def PolhemusTracker(tracker_id): | 92 | def PolhemusTracker(tracker_id): |
110 | - trck_init = None | ||
111 | try: | 93 | try: |
112 | - trck_init = PlhWrapperConnection() | 94 | + trck_init = PlhWrapperConnection(tracker_id) |
113 | lib_mode = 'wrapper' | 95 | lib_mode = 'wrapper' |
114 | if not trck_init: | 96 | if not trck_init: |
115 | print 'Could not connect with Polhemus wrapper, trying USB connection...' | 97 | print 'Could not connect with Polhemus wrapper, trying USB connection...' |
@@ -120,6 +102,7 @@ def PolhemusTracker(tracker_id): | @@ -120,6 +102,7 @@ def PolhemusTracker(tracker_id): | ||
120 | trck_init = PlhSerialConnection(tracker_id) | 102 | trck_init = PlhSerialConnection(tracker_id) |
121 | lib_mode = 'serial' | 103 | lib_mode = 'serial' |
122 | except: | 104 | except: |
105 | + trck_init = None | ||
123 | lib_mode = 'error' | 106 | lib_mode = 'error' |
124 | print 'Could not connect to Polhemus.' | 107 | print 'Could not connect to Polhemus.' |
125 | 108 | ||
@@ -132,21 +115,29 @@ def DebugTracker(tracker_id): | @@ -132,21 +115,29 @@ def DebugTracker(tracker_id): | ||
132 | return trck_init, 'debug' | 115 | return trck_init, 'debug' |
133 | 116 | ||
134 | 117 | ||
135 | -def PlhWrapperConnection(): | ||
136 | - trck_init = None | 118 | +def PlhWrapperConnection(tracker_id): |
137 | try: | 119 | try: |
138 | - import polhemus | 120 | + from time import sleep |
121 | + if tracker_id == 2: | ||
122 | + import polhemusFT | ||
123 | + trck_init = polhemusFT.polhemusFT() | ||
124 | + else: | ||
125 | + import polhemus | ||
126 | + trck_init = polhemus.polhemus() | ||
139 | 127 | ||
140 | - trck_init = polhemus.polhemus() | ||
141 | trck_check = trck_init.Initialize() | 128 | trck_check = trck_init.Initialize() |
142 | 129 | ||
143 | if trck_check: | 130 | if trck_check: |
144 | - # First run is necessary to discard the first coord collection | ||
145 | - trck_init.Run() | 131 | + # Sequence of runs necessary to throw away unnecessary data |
132 | + for n in range(0, 5): | ||
133 | + trck_init.Run() | ||
134 | + sleep(0.175) | ||
146 | else: | 135 | else: |
147 | - trck_init = trck_check | 136 | + trck_init = None |
137 | + print 'Could not connect to Polhemus via wrapper without error.' | ||
148 | except: | 138 | except: |
149 | - print 'Could not connect to Polhemus via wrapper.' | 139 | + trck_init = None |
140 | + print 'Could not connect to Polhemus via wrapper with error.' | ||
150 | 141 | ||
151 | return trck_init | 142 | return trck_init |
152 | 143 | ||
@@ -172,10 +163,11 @@ def PlhSerialConnection(tracker_id): | @@ -172,10 +163,11 @@ def PlhSerialConnection(tracker_id): | ||
172 | 163 | ||
173 | if not data: | 164 | if not data: |
174 | trck_init = None | 165 | trck_init = None |
166 | + print 'Could not connect to Polhemus serial without error.' | ||
175 | 167 | ||
176 | except: | 168 | except: |
177 | trck_init = None | 169 | trck_init = None |
178 | - print 'Could not connect to Polhemus serial.' | 170 | + print 'Could not connect to Polhemus serial with error.' |
179 | 171 | ||
180 | return trck_init | 172 | return trck_init |
181 | 173 | ||
@@ -184,7 +176,10 @@ def PlhUSBConnection(tracker_id): | @@ -184,7 +176,10 @@ def PlhUSBConnection(tracker_id): | ||
184 | trck_init = None | 176 | trck_init = None |
185 | try: | 177 | try: |
186 | import usb.core as uc | 178 | import usb.core as uc |
187 | - trck_init = uc.find(idVendor=0x0F44, idProduct=0x0003) | 179 | + # Check the idProduct using the usbdeview software, the idProduct is unique for each |
180 | + # device and connection fails when is incorrect | ||
181 | + # trck_init = uc.find(idVendor=0x0F44, idProduct=0x0003) [used in a different device] | ||
182 | + trck_init = uc.find(idVendor=0x0F44, idProduct=0xEF12) | ||
188 | cfg = trck_init.get_active_configuration() | 183 | cfg = trck_init.get_active_configuration() |
189 | for i in cfg: | 184 | for i in cfg: |
190 | for x in i: | 185 | for x in i: |
@@ -203,58 +198,35 @@ def PlhUSBConnection(tracker_id): | @@ -203,58 +198,35 @@ def PlhUSBConnection(tracker_id): | ||
203 | endpoint.wMaxPacketSize) | 198 | endpoint.wMaxPacketSize) |
204 | if not data: | 199 | if not data: |
205 | trck_init = None | 200 | trck_init = None |
201 | + print 'Could not connect to Polhemus USB without error.' | ||
206 | 202 | ||
207 | except: | 203 | except: |
208 | - print 'Could not connect to Polhemus USB.' | 204 | + print 'Could not connect to Polhemus USB with error.' |
209 | 205 | ||
210 | return trck_init | 206 | return trck_init |
211 | 207 | ||
212 | 208 | ||
213 | -def DisconnectTracker(tracker_id): | 209 | +def DisconnectTracker(tracker_id, trck_init): |
214 | """ | 210 | """ |
215 | Disconnect current spatial tracker | 211 | Disconnect current spatial tracker |
216 | 212 | ||
217 | :param tracker_id: ID of tracking device. | 213 | :param tracker_id: ID of tracking device. |
214 | + :param trck_init: Initialization variable of tracking device. | ||
218 | """ | 215 | """ |
219 | - from wx.lib.pubsub import pub as Publisher | ||
220 | - Publisher.sendMessage('Update status text in GUI', _("Disconnecting tracker ...")) | ||
221 | - Publisher.sendMessage('Remove sensors ID') | ||
222 | - trck_init = None | ||
223 | - # TODO: create individual functions to disconnect each other device, e.g. Polhemus. | ||
224 | - if tracker_id == 1: | ||
225 | - try: | ||
226 | - import pyclaron | ||
227 | - pyclaron.pyclaron().Close() | ||
228 | - lib_mode = 'wrapper' | ||
229 | - print 'Claron tracker disconnected.' | ||
230 | - except ImportError: | ||
231 | - lib_mode = 'error' | ||
232 | - print 'The ClaronTracker library is not installed.' | ||
233 | - | ||
234 | - elif tracker_id == 2: | ||
235 | - try: | ||
236 | - import polhemusFT | ||
237 | - polhemusFT.polhemusFT().Close() | ||
238 | - lib_mode = 'wrapper' | ||
239 | - print 'Polhemus tracker disconnected.' | ||
240 | - except ImportError: | ||
241 | - lib_mode = 'error' | ||
242 | - print 'The polhemus library is not installed.' | ||
243 | 216 | ||
244 | - elif tracker_id == 4: | 217 | + if tracker_id == 5: |
218 | + trck_init = False | ||
219 | + lib_mode = 'debug' | ||
220 | + print 'Debug tracker disconnected.' | ||
221 | + else: | ||
245 | try: | 222 | try: |
246 | - import polhemus | ||
247 | - polhemus.polhemus().Close() | 223 | + trck_init.Close() |
224 | + trck_init = False | ||
248 | lib_mode = 'wrapper' | 225 | lib_mode = 'wrapper' |
249 | - print 'Polhemus tracker disconnected.' | ||
250 | - except ImportError: | 226 | + print 'Tracker disconnected.' |
227 | + except: | ||
228 | + trck_init = True | ||
251 | lib_mode = 'error' | 229 | lib_mode = 'error' |
252 | - print 'The polhemus library is not installed.' | ||
253 | - | ||
254 | - elif tracker_id == 5: | ||
255 | - print 'Debug tracker disconnected.' | ||
256 | - lib_mode = 'debug' | ||
257 | - | ||
258 | - Publisher.sendMessage('Update status text in GUI', _("Ready")) | 230 | + print 'The tracker could not be disconnected.' |
259 | 231 | ||
260 | return trck_init, lib_mode | 232 | return trck_init, lib_mode |
261 | \ No newline at end of file | 233 | \ No newline at end of file |
invesalius/data/viewer_slice.py
@@ -934,13 +934,13 @@ class Viewer(wx.Panel): | @@ -934,13 +934,13 @@ class Viewer(wx.Panel): | ||
934 | 934 | ||
935 | def UpdateSlicesNavigation(self, pubsub_evt): | 935 | def UpdateSlicesNavigation(self, pubsub_evt): |
936 | # Get point from base change | 936 | # Get point from base change |
937 | - wx, wy, wz = pubsub_evt.data | ||
938 | - px, py = self.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | 937 | + ux, uy, uz = pubsub_evt.data[1][:3] |
938 | + px, py = self.get_slice_pixel_coord_by_world_pos(ux, uy, uz) | ||
939 | coord = self.calcultate_scroll_position(px, py) | 939 | coord = self.calcultate_scroll_position(px, py) |
940 | 940 | ||
941 | - self.cross.SetFocalPoint((wx, wy, wz)) | 941 | + self.cross.SetFocalPoint((ux, uy, uz)) |
942 | self.ScrollSlice(coord) | 942 | self.ScrollSlice(coord) |
943 | - Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) | 943 | + Publisher.sendMessage('Set ball reference position', (ux, uy, uz)) |
944 | 944 | ||
945 | def ScrollSlice(self, coord): | 945 | def ScrollSlice(self, coord): |
946 | if self.orientation == "AXIAL": | 946 | if self.orientation == "AXIAL": |
invesalius/data/viewer_volume.py
@@ -20,6 +20,7 @@ | @@ -20,6 +20,7 @@ | ||
20 | # detalhes. | 20 | # detalhes. |
21 | #-------------------------------------------------------------------------- | 21 | #-------------------------------------------------------------------------- |
22 | 22 | ||
23 | +from math import cos, sin | ||
23 | import os | 24 | import os |
24 | import sys | 25 | import sys |
25 | 26 | ||
@@ -29,14 +30,16 @@ import wx | @@ -29,14 +30,16 @@ import wx | ||
29 | import vtk | 30 | import vtk |
30 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor | 31 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
31 | from wx.lib.pubsub import pub as Publisher | 32 | from wx.lib.pubsub import pub as Publisher |
33 | +import random | ||
34 | +from scipy.spatial import distance | ||
32 | 35 | ||
33 | import invesalius.constants as const | 36 | import invesalius.constants as const |
34 | import invesalius.data.bases as bases | 37 | import invesalius.data.bases as bases |
38 | +import invesalius.data.transformations as tr | ||
35 | import invesalius.data.vtk_utils as vtku | 39 | import invesalius.data.vtk_utils as vtku |
36 | import invesalius.project as prj | 40 | import invesalius.project as prj |
37 | import invesalius.style as st | 41 | import invesalius.style as st |
38 | import invesalius.utils as utils | 42 | import invesalius.utils as utils |
39 | -import invesalius.data.measures as measures | ||
40 | 43 | ||
41 | if sys.platform == 'win32': | 44 | if sys.platform == 'win32': |
42 | try: | 45 | try: |
@@ -95,6 +98,14 @@ class Viewer(wx.Panel): | @@ -95,6 +98,14 @@ class Viewer(wx.Panel): | ||
95 | self.text.SetValue("") | 98 | self.text.SetValue("") |
96 | self.ren.AddActor(self.text.actor) | 99 | self.ren.AddActor(self.text.actor) |
97 | 100 | ||
101 | + # axes = vtk.vtkAxesActor() | ||
102 | + # axes.SetXAxisLabelText('x') | ||
103 | + # axes.SetYAxisLabelText('y') | ||
104 | + # axes.SetZAxisLabelText('z') | ||
105 | + # axes.SetTotalLength(50, 50, 50) | ||
106 | + # | ||
107 | + # self.ren.AddActor(axes) | ||
108 | + | ||
98 | 109 | ||
99 | self.slice_plane = None | 110 | self.slice_plane = None |
100 | 111 | ||
@@ -123,9 +134,20 @@ class Viewer(wx.Panel): | @@ -123,9 +134,20 @@ class Viewer(wx.Panel): | ||
123 | self.repositioned_coronal_plan = 0 | 134 | self.repositioned_coronal_plan = 0 |
124 | self.added_actor = 0 | 135 | self.added_actor = 0 |
125 | 136 | ||
126 | - self.camera_state = True | 137 | + self.camera_state = const.CAM_MODE |
138 | + | ||
139 | + self.nav_status = False | ||
127 | 140 | ||
128 | self.ball_actor = None | 141 | self.ball_actor = None |
142 | + self.obj_actor = None | ||
143 | + self.obj_axes = None | ||
144 | + self.obj_name = False | ||
145 | + self.obj_state = None | ||
146 | + self.obj_actor_list = None | ||
147 | + self.arrow_actor_list = None | ||
148 | + | ||
149 | + # self.obj_axes = None | ||
150 | + | ||
129 | self._mode_cross = False | 151 | self._mode_cross = False |
130 | self._to_show_ball = 0 | 152 | self._to_show_ball = 0 |
131 | self._ball_ref_visibility = False | 153 | self._ball_ref_visibility = False |
@@ -136,6 +158,13 @@ class Viewer(wx.Panel): | @@ -136,6 +158,13 @@ class Viewer(wx.Panel): | ||
136 | self.timer = False | 158 | self.timer = False |
137 | self.index = False | 159 | self.index = False |
138 | 160 | ||
161 | + self.target_coord = None | ||
162 | + self.aim_actor = None | ||
163 | + self.dummy_coil_actor = None | ||
164 | + self.target_mode = False | ||
165 | + self.anglethreshold = const.COIL_ANGLES_THRESHOLD | ||
166 | + self.distthreshold = const.COIL_COORD_THRESHOLD | ||
167 | + | ||
139 | def __bind_events(self): | 168 | def __bind_events(self): |
140 | Publisher.subscribe(self.LoadActor, | 169 | Publisher.subscribe(self.LoadActor, |
141 | 'Load surface actor into viewer') | 170 | 'Load surface actor into viewer') |
@@ -159,7 +188,7 @@ class Viewer(wx.Panel): | @@ -159,7 +188,7 @@ class Viewer(wx.Panel): | ||
159 | 'Update raycasting preset') | 188 | 'Update raycasting preset') |
160 | ### | 189 | ### |
161 | Publisher.subscribe(self.AppendActor,'AppendActor') | 190 | Publisher.subscribe(self.AppendActor,'AppendActor') |
162 | - Publisher.subscribe(self.SetWidgetInteractor, | 191 | + Publisher.subscribe(self.SetWidgetInteractor, |
163 | 'Set Widget Interactor') | 192 | 'Set Widget Interactor') |
164 | Publisher.subscribe(self.OnSetViewAngle, | 193 | Publisher.subscribe(self.OnSetViewAngle, |
165 | 'Set volume view angle') | 194 | 'Set volume view angle') |
@@ -173,7 +202,8 @@ class Viewer(wx.Panel): | @@ -173,7 +202,8 @@ class Viewer(wx.Panel): | ||
173 | Publisher.subscribe(self.LoadSlicePlane, 'Load slice plane') | 202 | Publisher.subscribe(self.LoadSlicePlane, 'Load slice plane') |
174 | 203 | ||
175 | Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range') | 204 | Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range') |
176 | - Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') | 205 | + Publisher.subscribe(self.SetVolumeCamera, 'Co-registered points') |
206 | + # Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') | ||
177 | Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') | 207 | Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') |
178 | 208 | ||
179 | Publisher.subscribe(self.OnEnableStyle, 'Enable style') | 209 | Publisher.subscribe(self.OnEnableStyle, 'Enable style') |
@@ -190,16 +220,16 @@ class Viewer(wx.Panel): | @@ -190,16 +220,16 @@ class Viewer(wx.Panel): | ||
190 | Publisher.subscribe(self.OnCloseProject, 'Close project data') | 220 | Publisher.subscribe(self.OnCloseProject, 'Close project data') |
191 | 221 | ||
192 | Publisher.subscribe(self.RemoveAllActor, 'Remove all volume actors') | 222 | Publisher.subscribe(self.RemoveAllActor, 'Remove all volume actors') |
193 | - | 223 | + |
194 | Publisher.subscribe(self.OnExportPicture,'Export picture to file') | 224 | Publisher.subscribe(self.OnExportPicture,'Export picture to file') |
195 | 225 | ||
196 | Publisher.subscribe(self.OnStartSeed,'Create surface by seeding - start') | 226 | Publisher.subscribe(self.OnStartSeed,'Create surface by seeding - start') |
197 | Publisher.subscribe(self.OnEndSeed,'Create surface by seeding - end') | 227 | Publisher.subscribe(self.OnEndSeed,'Create surface by seeding - end') |
198 | 228 | ||
199 | Publisher.subscribe(self.SetStereoMode, 'Set stereo mode') | 229 | Publisher.subscribe(self.SetStereoMode, 'Set stereo mode') |
200 | - | 230 | + |
201 | Publisher.subscribe(self.Reposition3DPlane, 'Reposition 3D Plane') | 231 | Publisher.subscribe(self.Reposition3DPlane, 'Reposition 3D Plane') |
202 | - | 232 | + |
203 | Publisher.subscribe(self.RemoveVolume, 'Remove Volume') | 233 | Publisher.subscribe(self.RemoveVolume, 'Remove Volume') |
204 | 234 | ||
205 | Publisher.subscribe(self.SetBallReferencePosition, | 235 | Publisher.subscribe(self.SetBallReferencePosition, |
@@ -219,10 +249,25 @@ class Viewer(wx.Panel): | @@ -219,10 +249,25 @@ class Viewer(wx.Panel): | ||
219 | Publisher.subscribe(self.BlinkMarker, 'Blink Marker') | 249 | Publisher.subscribe(self.BlinkMarker, 'Blink Marker') |
220 | Publisher.subscribe(self.StopBlinkMarker, 'Stop Blink Marker') | 250 | Publisher.subscribe(self.StopBlinkMarker, 'Stop Blink Marker') |
221 | 251 | ||
252 | + # Related to object tracking during neuronavigation | ||
253 | + Publisher.subscribe(self.OnNavigationStatus, 'Navigation status') | ||
254 | + Publisher.subscribe(self.UpdateObjectOrientation, 'Update object matrix') | ||
255 | + Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state') | ||
256 | + Publisher.subscribe(self.UpdateShowObjectState, 'Update show object state') | ||
257 | + | ||
258 | + Publisher.subscribe(self.ActivateTargetMode, 'Target navigation mode') | ||
259 | + Publisher.subscribe(self.OnUpdateObjectTargetGuide, 'Update object matrix') | ||
260 | + Publisher.subscribe(self.OnUpdateTargetCoordinates, 'Update target') | ||
261 | + Publisher.subscribe(self.OnRemoveTarget, 'Disable or enable coil tracker') | ||
262 | + # Publisher.subscribe(self.UpdateObjectTargetView, 'Co-registered points') | ||
263 | + Publisher.subscribe(self.OnTargetMarkerTransparency, 'Set target transparency') | ||
264 | + Publisher.subscribe(self.OnUpdateAngleThreshold, 'Update angle threshold') | ||
265 | + Publisher.subscribe(self.OnUpdateDistThreshold, 'Update dist threshold') | ||
266 | + | ||
222 | def SetStereoMode(self, pubsub_evt): | 267 | def SetStereoMode(self, pubsub_evt): |
223 | mode = pubsub_evt.data | 268 | mode = pubsub_evt.data |
224 | ren_win = self.interactor.GetRenderWindow() | 269 | ren_win = self.interactor.GetRenderWindow() |
225 | - | 270 | + |
226 | if mode == const.STEREO_OFF: | 271 | if mode == const.STEREO_OFF: |
227 | ren_win.StereoRenderOff() | 272 | ren_win.StereoRenderOff() |
228 | else: | 273 | else: |
@@ -245,7 +290,7 @@ class Viewer(wx.Panel): | @@ -245,7 +290,7 @@ class Viewer(wx.Panel): | ||
245 | ren_win.SetStereoTypeToAnaglyph() | 290 | ren_win.SetStereoTypeToAnaglyph() |
246 | 291 | ||
247 | ren_win.StereoRenderOn() | 292 | ren_win.StereoRenderOn() |
248 | - | 293 | + |
249 | self.interactor.Render() | 294 | self.interactor.Render() |
250 | 295 | ||
251 | def _check_ball_reference(self, pubsub_evt): | 296 | def _check_ball_reference(self, pubsub_evt): |
@@ -318,10 +363,10 @@ class Viewer(wx.Panel): | @@ -318,10 +363,10 @@ class Viewer(wx.Panel): | ||
318 | def OnStartSeed(self, pubsub_evt): | 363 | def OnStartSeed(self, pubsub_evt): |
319 | index = pubsub_evt.data | 364 | index = pubsub_evt.data |
320 | self.seed_points = [] | 365 | self.seed_points = [] |
321 | - | 366 | + |
322 | def OnEndSeed(self, pubsub_evt): | 367 | def OnEndSeed(self, pubsub_evt): |
323 | Publisher.sendMessage("Create surface from seeds", | 368 | Publisher.sendMessage("Create surface from seeds", |
324 | - self.seed_points) | 369 | + self.seed_points) |
325 | 370 | ||
326 | def OnExportPicture(self, pubsub_evt): | 371 | def OnExportPicture(self, pubsub_evt): |
327 | id, filename, filetype = pubsub_evt.data | 372 | id, filename, filetype = pubsub_evt.data |
@@ -458,8 +503,7 @@ class Viewer(wx.Panel): | @@ -458,8 +503,7 @@ class Viewer(wx.Panel): | ||
458 | 503 | ||
459 | def AddMarker(self, pubsub_evt): | 504 | def AddMarker(self, pubsub_evt): |
460 | """ | 505 | """ |
461 | - Markers create by navigation tools and | ||
462 | - rendered in volume viewer. | 506 | + Markers created by navigation tools and rendered in volume viewer. |
463 | """ | 507 | """ |
464 | self.ball_id = pubsub_evt.data[0] | 508 | self.ball_id = pubsub_evt.data[0] |
465 | ballsize = pubsub_evt.data[1] | 509 | ballsize = pubsub_evt.data[1] |
@@ -487,7 +531,7 @@ class Viewer(wx.Panel): | @@ -487,7 +531,7 @@ class Viewer(wx.Panel): | ||
487 | self.ball_id = self.ball_id + 1 | 531 | self.ball_id = self.ball_id + 1 |
488 | #self.UpdateRender() | 532 | #self.UpdateRender() |
489 | self.Refresh() | 533 | self.Refresh() |
490 | - | 534 | + |
491 | def HideAllMarkers(self, pubsub_evt): | 535 | def HideAllMarkers(self, pubsub_evt): |
492 | ballid = pubsub_evt.data | 536 | ballid = pubsub_evt.data |
493 | for i in range(0, ballid): | 537 | for i in range(0, ballid): |
@@ -521,11 +565,11 @@ class Viewer(wx.Panel): | @@ -521,11 +565,11 @@ class Viewer(wx.Panel): | ||
521 | self.staticballs[self.index].SetVisibility(1) | 565 | self.staticballs[self.index].SetVisibility(1) |
522 | self.index = pubsub_evt.data | 566 | self.index = pubsub_evt.data |
523 | self.timer = wx.Timer(self) | 567 | self.timer = wx.Timer(self) |
524 | - self.Bind(wx.EVT_TIMER, self.blink, self.timer) | 568 | + self.Bind(wx.EVT_TIMER, self.OnBlinkMarker, self.timer) |
525 | self.timer.Start(500) | 569 | self.timer.Start(500) |
526 | self.timer_count = 0 | 570 | self.timer_count = 0 |
527 | 571 | ||
528 | - def blink(self, evt): | 572 | + def OnBlinkMarker(self, evt): |
529 | self.staticballs[self.index].SetVisibility(int(self.timer_count % 2)) | 573 | self.staticballs[self.index].SetVisibility(int(self.timer_count % 2)) |
530 | self.Refresh() | 574 | self.Refresh() |
531 | self.timer_count += 1 | 575 | self.timer_count += 1 |
@@ -533,11 +577,499 @@ class Viewer(wx.Panel): | @@ -533,11 +577,499 @@ class Viewer(wx.Panel): | ||
533 | def StopBlinkMarker(self, pubsub_evt): | 577 | def StopBlinkMarker(self, pubsub_evt): |
534 | if self.timer: | 578 | if self.timer: |
535 | self.timer.Stop() | 579 | self.timer.Stop() |
536 | - if pubsub_evt.data == None: | 580 | + if pubsub_evt.data is None: |
537 | self.staticballs[self.index].SetVisibility(1) | 581 | self.staticballs[self.index].SetVisibility(1) |
538 | self.Refresh() | 582 | self.Refresh() |
539 | self.index = False | 583 | self.index = False |
540 | 584 | ||
585 | + def OnTargetMarkerTransparency(self, pubsub_evt): | ||
586 | + status = pubsub_evt.data[0] | ||
587 | + index = pubsub_evt.data[1] | ||
588 | + if status: | ||
589 | + self.staticballs[index].GetProperty().SetOpacity(1) | ||
590 | + # self.staticballs[index].GetProperty().SetOpacity(0.4) | ||
591 | + else: | ||
592 | + self.staticballs[index].GetProperty().SetOpacity(1) | ||
593 | + | ||
594 | + def OnUpdateAngleThreshold(self, pubsub_evt): | ||
595 | + self.anglethreshold = pubsub_evt.data | ||
596 | + | ||
597 | + def OnUpdateDistThreshold(self, pubsub_evt): | ||
598 | + self.distthreshold = pubsub_evt.data | ||
599 | + | ||
600 | + def ActivateTargetMode(self, pubsub_evt): | ||
601 | + self.target_mode = pubsub_evt.data | ||
602 | + if self.target_coord and self.target_mode: | ||
603 | + self.CreateTargetAim() | ||
604 | + | ||
605 | + # Create a line | ||
606 | + self.ren.SetViewport(0, 0, 0.75, 1) | ||
607 | + self.ren2 = vtk.vtkRenderer() | ||
608 | + | ||
609 | + self.interactor.GetRenderWindow().AddRenderer(self.ren2) | ||
610 | + self.ren2.SetViewport(0.75, 0, 1, 1) | ||
611 | + self.CreateTextDistance() | ||
612 | + | ||
613 | + obj_polydata = self.CreateObjectPolyData(self.obj_name) | ||
614 | + | ||
615 | + normals = vtk.vtkPolyDataNormals() | ||
616 | + normals.SetInputData(obj_polydata) | ||
617 | + normals.SetFeatureAngle(80) | ||
618 | + normals.AutoOrientNormalsOn() | ||
619 | + normals.Update() | ||
620 | + | ||
621 | + mapper = vtk.vtkPolyDataMapper() | ||
622 | + mapper.SetInputData(normals.GetOutput()) | ||
623 | + mapper.ScalarVisibilityOff() | ||
624 | + mapper.ImmediateModeRenderingOn() # improve performance | ||
625 | + | ||
626 | + obj_roll = vtk.vtkActor() | ||
627 | + obj_roll.SetMapper(mapper) | ||
628 | + obj_roll.SetPosition(0, 25, -30) | ||
629 | + obj_roll.RotateX(-60) | ||
630 | + obj_roll.RotateZ(180) | ||
631 | + | ||
632 | + obj_yaw = vtk.vtkActor() | ||
633 | + obj_yaw.SetMapper(mapper) | ||
634 | + obj_yaw.SetPosition(0, -115, 5) | ||
635 | + obj_yaw.RotateZ(180) | ||
636 | + | ||
637 | + obj_pitch = vtk.vtkActor() | ||
638 | + obj_pitch.SetMapper(mapper) | ||
639 | + obj_pitch.SetPosition(5, -265, 5) | ||
640 | + obj_pitch.RotateY(90) | ||
641 | + obj_pitch.RotateZ(180) | ||
642 | + | ||
643 | + arrow_roll_z1 = self.CreateArrowActor([-50, -35, 12], [-50, -35, 50]) | ||
644 | + arrow_roll_z1.GetProperty().SetColor(1, 1, 0) | ||
645 | + arrow_roll_z1.RotateX(-60) | ||
646 | + arrow_roll_z1.RotateZ(180) | ||
647 | + arrow_roll_z2 = self.CreateArrowActor([50, -35, 0], [50, -35, -50]) | ||
648 | + arrow_roll_z2.GetProperty().SetColor(1, 1, 0) | ||
649 | + arrow_roll_z2.RotateX(-60) | ||
650 | + arrow_roll_z2.RotateZ(180) | ||
651 | + | ||
652 | + arrow_yaw_y1 = self.CreateArrowActor([-50, -35, 0], [-50, 5, 0]) | ||
653 | + arrow_yaw_y1.GetProperty().SetColor(0, 1, 0) | ||
654 | + arrow_yaw_y1.SetPosition(0, -150, 0) | ||
655 | + arrow_yaw_y1.RotateZ(180) | ||
656 | + arrow_yaw_y2 = self.CreateArrowActor([50, -35, 0], [50, -75, 0]) | ||
657 | + arrow_yaw_y2.GetProperty().SetColor(0, 1, 0) | ||
658 | + arrow_yaw_y2.SetPosition(0, -150, 0) | ||
659 | + arrow_yaw_y2.RotateZ(180) | ||
660 | + | ||
661 | + arrow_pitch_x1 = self.CreateArrowActor([0, 65, 38], [0, 65, 68]) | ||
662 | + arrow_pitch_x1.GetProperty().SetColor(1, 0, 0) | ||
663 | + arrow_pitch_x1.SetPosition(0, -300, 0) | ||
664 | + arrow_pitch_x1.RotateY(90) | ||
665 | + arrow_pitch_x1.RotateZ(180) | ||
666 | + arrow_pitch_x2 = self.CreateArrowActor([0, -55, 5], [0, -55, -30]) | ||
667 | + arrow_pitch_x2.GetProperty().SetColor(1, 0, 0) | ||
668 | + arrow_pitch_x2.SetPosition(0, -300, 0) | ||
669 | + arrow_pitch_x2.RotateY(90) | ||
670 | + arrow_pitch_x2.RotateZ(180) | ||
671 | + | ||
672 | + self.obj_actor_list = obj_roll, obj_yaw, obj_pitch | ||
673 | + self.arrow_actor_list = arrow_roll_z1, arrow_roll_z2, arrow_yaw_y1, arrow_yaw_y2,\ | ||
674 | + arrow_pitch_x1, arrow_pitch_x2 | ||
675 | + | ||
676 | + for ind in self.obj_actor_list: | ||
677 | + self.ren2.AddActor(ind) | ||
678 | + | ||
679 | + for ind in self.arrow_actor_list: | ||
680 | + self.ren2.AddActor(ind) | ||
681 | + | ||
682 | + self.ren.ResetCamera() | ||
683 | + self.SetCameraTarget() | ||
684 | + #self.ren.GetActiveCamera().Zoom(4) | ||
685 | + | ||
686 | + self.ren2.ResetCamera() | ||
687 | + self.ren2.GetActiveCamera().Zoom(2) | ||
688 | + self.ren2.InteractiveOff() | ||
689 | + self.interactor.Render() | ||
690 | + | ||
691 | + else: | ||
692 | + self.DisableCoilTracker() | ||
693 | + | ||
694 | + def OnUpdateObjectTargetGuide(self, pubsub_evt): | ||
695 | + coord = pubsub_evt.data[1] | ||
696 | + if self.target_coord and self.target_mode: | ||
697 | + | ||
698 | + target_dist = distance.euclidean(coord[0:3], | ||
699 | + (self.target_coord[0], -self.target_coord[1], self.target_coord[2])) | ||
700 | + | ||
701 | + # self.txt.SetCoilDistanceValue(target_dist) | ||
702 | + self.textSource.SetText('Dist: ' + str("{:06.2f}".format(target_dist)) + ' mm') | ||
703 | + self.ren.ResetCamera() | ||
704 | + self.SetCameraTarget() | ||
705 | + if target_dist > 100: | ||
706 | + target_dist = 100 | ||
707 | + # ((-0.0404*dst) + 5.0404) is the linear equation to normalize the zoom between 1 and 5 times with | ||
708 | + # the distance between 1 and 100 mm | ||
709 | + self.ren.GetActiveCamera().Zoom((-0.0404 * target_dist) + 5.0404) | ||
710 | + | ||
711 | + if target_dist <= self.distthreshold: | ||
712 | + thrdist = True | ||
713 | + self.aim_actor.GetProperty().SetColor(0, 1, 0) | ||
714 | + else: | ||
715 | + thrdist = False | ||
716 | + self.aim_actor.GetProperty().SetColor(1, 1, 1) | ||
717 | + | ||
718 | + coordx = self.target_coord[3] - coord[3] | ||
719 | + if coordx > const.ARROW_UPPER_LIMIT: | ||
720 | + coordx = const.ARROW_UPPER_LIMIT | ||
721 | + elif coordx < -const.ARROW_UPPER_LIMIT: | ||
722 | + coordx = -const.ARROW_UPPER_LIMIT | ||
723 | + coordx = const.ARROW_SCALE * coordx | ||
724 | + | ||
725 | + coordy = self.target_coord[4] - coord[4] | ||
726 | + if coordy > const.ARROW_UPPER_LIMIT: | ||
727 | + coordy = const.ARROW_UPPER_LIMIT | ||
728 | + elif coordy < -const.ARROW_UPPER_LIMIT: | ||
729 | + coordy = -const.ARROW_UPPER_LIMIT | ||
730 | + coordy = const.ARROW_SCALE * coordy | ||
731 | + | ||
732 | + coordz = self.target_coord[5] - coord[5] | ||
733 | + if coordz > const.ARROW_UPPER_LIMIT: | ||
734 | + coordz = const.ARROW_UPPER_LIMIT | ||
735 | + elif coordz < -const.ARROW_UPPER_LIMIT: | ||
736 | + coordz = -const.ARROW_UPPER_LIMIT | ||
737 | + coordz = const.ARROW_SCALE * coordz | ||
738 | + | ||
739 | + for ind in self.arrow_actor_list: | ||
740 | + self.ren2.RemoveActor(ind) | ||
741 | + | ||
742 | + if self.anglethreshold * const.ARROW_SCALE > coordx > -self.anglethreshold * const.ARROW_SCALE: | ||
743 | + thrcoordx = True | ||
744 | + self.obj_actor_list[0].GetProperty().SetColor(0, 1, 0) | ||
745 | + else: | ||
746 | + thrcoordx = False | ||
747 | + self.obj_actor_list[0].GetProperty().SetColor(1, 1, 1) | ||
748 | + | ||
749 | + offset = 5 | ||
750 | + | ||
751 | + arrow_roll_x1 = self.CreateArrowActor([-55, -35, offset], [-55, -35, offset - coordx]) | ||
752 | + arrow_roll_x1.RotateX(-60) | ||
753 | + arrow_roll_x1.RotateZ(180) | ||
754 | + arrow_roll_x1.GetProperty().SetColor(1, 1, 0) | ||
755 | + | ||
756 | + arrow_roll_x2 = self.CreateArrowActor([55, -35, offset], [55, -35, offset + coordx]) | ||
757 | + arrow_roll_x2.RotateX(-60) | ||
758 | + arrow_roll_x2.RotateZ(180) | ||
759 | + arrow_roll_x2.GetProperty().SetColor(1, 1, 0) | ||
760 | + | ||
761 | + if self.anglethreshold * const.ARROW_SCALE > coordz > -self.anglethreshold * const.ARROW_SCALE: | ||
762 | + thrcoordz = True | ||
763 | + self.obj_actor_list[1].GetProperty().SetColor(0, 1, 0) | ||
764 | + else: | ||
765 | + thrcoordz = False | ||
766 | + self.obj_actor_list[1].GetProperty().SetColor(1, 1, 1) | ||
767 | + | ||
768 | + offset = -35 | ||
769 | + | ||
770 | + arrow_yaw_z1 = self.CreateArrowActor([-55, offset, 0], [-55, offset - coordz, 0]) | ||
771 | + arrow_yaw_z1.SetPosition(0, -150, 0) | ||
772 | + arrow_yaw_z1.RotateZ(180) | ||
773 | + arrow_yaw_z1.GetProperty().SetColor(0, 1, 0) | ||
774 | + | ||
775 | + arrow_yaw_z2 = self.CreateArrowActor([55, offset, 0], [55, offset + coordz, 0]) | ||
776 | + arrow_yaw_z2.SetPosition(0, -150, 0) | ||
777 | + arrow_yaw_z2.RotateZ(180) | ||
778 | + arrow_yaw_z2.GetProperty().SetColor(0, 1, 0) | ||
779 | + | ||
780 | + if self.anglethreshold * const.ARROW_SCALE > coordy > -self.anglethreshold * const.ARROW_SCALE: | ||
781 | + thrcoordy = True | ||
782 | + self.obj_actor_list[2].GetProperty().SetColor(0, 1, 0) | ||
783 | + else: | ||
784 | + thrcoordy = False | ||
785 | + self.obj_actor_list[2].GetProperty().SetColor(1, 1, 1) | ||
786 | + | ||
787 | + offset = 38 | ||
788 | + arrow_pitch_y1 = self.CreateArrowActor([0, 65, offset], [0, 65, offset + coordy]) | ||
789 | + arrow_pitch_y1.SetPosition(0, -300, 0) | ||
790 | + arrow_pitch_y1.RotateY(90) | ||
791 | + arrow_pitch_y1.RotateZ(180) | ||
792 | + arrow_pitch_y1.GetProperty().SetColor(1, 0, 0) | ||
793 | + | ||
794 | + offset = 5 | ||
795 | + arrow_pitch_y2 = self.CreateArrowActor([0, -55, offset], [0, -55, offset - coordy]) | ||
796 | + arrow_pitch_y2.SetPosition(0, -300, 0) | ||
797 | + arrow_pitch_y2.RotateY(90) | ||
798 | + arrow_pitch_y2.RotateZ(180) | ||
799 | + arrow_pitch_y2.GetProperty().SetColor(1, 0, 0) | ||
800 | + | ||
801 | + if thrdist and thrcoordx and thrcoordy and thrcoordz: | ||
802 | + self.dummy_coil_actor.GetProperty().SetColor(0, 1, 0) | ||
803 | + else: | ||
804 | + self.dummy_coil_actor.GetProperty().SetColor(1, 1, 1) | ||
805 | + | ||
806 | + self.arrow_actor_list = arrow_roll_x1, arrow_roll_x2, arrow_yaw_z1, arrow_yaw_z2, \ | ||
807 | + arrow_pitch_y1, arrow_pitch_y2 | ||
808 | + | ||
809 | + for ind in self.arrow_actor_list: | ||
810 | + self.ren2.AddActor(ind) | ||
811 | + | ||
812 | + self.Refresh() | ||
813 | + | ||
814 | + def OnUpdateTargetCoordinates(self, pubsub_evt): | ||
815 | + self.target_coord = pubsub_evt.data[0:6] | ||
816 | + self.target_coord[1] = -self.target_coord[1] | ||
817 | + self.CreateTargetAim() | ||
818 | + | ||
819 | + def OnRemoveTarget(self, pubsub_evt): | ||
820 | + status = pubsub_evt.data | ||
821 | + if not status: | ||
822 | + self.target_mode = None | ||
823 | + self.target_coord = None | ||
824 | + self.RemoveTargetAim() | ||
825 | + self.DisableCoilTracker() | ||
826 | + | ||
827 | + def CreateTargetAim(self): | ||
828 | + if self.aim_actor: | ||
829 | + self.RemoveTargetAim() | ||
830 | + self.aim_actor = None | ||
831 | + | ||
832 | + self.textSource = vtk.vtkVectorText() | ||
833 | + mapper = vtk.vtkPolyDataMapper() | ||
834 | + mapper.SetInputConnection(self.textSource.GetOutputPort()) | ||
835 | + tactor = vtk.vtkFollower() | ||
836 | + tactor.SetMapper(mapper) | ||
837 | + tactor.GetProperty().SetColor(1.0, 0.25, 0.0) | ||
838 | + tactor.SetScale(5) | ||
839 | + tactor.SetPosition(self.target_coord[0]+10, self.target_coord[1]+30, self.target_coord[2]+20) | ||
840 | + self.ren.AddActor(tactor) | ||
841 | + self.tactor = tactor | ||
842 | + tactor.SetCamera(self.ren.GetActiveCamera()) | ||
843 | + | ||
844 | + | ||
845 | + # v3, M_plane_inv = self.Plane(self.target_coord[0:3], self.pTarget) | ||
846 | + # mat4x4 = vtk.vtkMatrix4x4() | ||
847 | + # for i in range(4): | ||
848 | + # mat4x4.SetElement(i, 0, M_plane_inv[i][0]) | ||
849 | + # mat4x4.SetElement(i, 1, M_plane_inv[i][1]) | ||
850 | + # mat4x4.SetElement(i, 2, M_plane_inv[i][2]) | ||
851 | + # mat4x4.SetElement(i, 3, M_plane_inv[i][3]) | ||
852 | + | ||
853 | + a, b, g = np.radians(self.target_coord[3:]) | ||
854 | + r_ref = tr.euler_matrix(a, b, g, 'sxyz') | ||
855 | + t_ref = tr.translation_matrix(self.target_coord[:3]) | ||
856 | + m_img = np.asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
857 | + | ||
858 | + m_img_vtk = vtk.vtkMatrix4x4() | ||
859 | + | ||
860 | + for row in range(0, 4): | ||
861 | + for col in range(0, 4): | ||
862 | + m_img_vtk.SetElement(row, col, m_img[row, col]) | ||
863 | + | ||
864 | + self.m_img_vtk = m_img_vtk | ||
865 | + | ||
866 | + filename = os.path.join(const.OBJ_DIR, "aim.stl") | ||
867 | + | ||
868 | + reader = vtk.vtkSTLReader() | ||
869 | + reader.SetFileName(filename) | ||
870 | + mapper = vtk.vtkPolyDataMapper() | ||
871 | + mapper.SetInputConnection(reader.GetOutputPort()) | ||
872 | + | ||
873 | + # Transform the polydata | ||
874 | + transform = vtk.vtkTransform() | ||
875 | + transform.SetMatrix(m_img_vtk) | ||
876 | + #transform.SetMatrix(mat4x4) | ||
877 | + transformPD = vtk.vtkTransformPolyDataFilter() | ||
878 | + transformPD.SetTransform(transform) | ||
879 | + transformPD.SetInputConnection(reader.GetOutputPort()) | ||
880 | + transformPD.Update() | ||
881 | + # mapper transform | ||
882 | + mapper.SetInputConnection(transformPD.GetOutputPort()) | ||
883 | + | ||
884 | + aim_actor = vtk.vtkActor() | ||
885 | + aim_actor.SetMapper(mapper) | ||
886 | + aim_actor.GetProperty().SetColor(1, 1, 1) | ||
887 | + aim_actor.GetProperty().SetOpacity(0.6) | ||
888 | + self.aim_actor = aim_actor | ||
889 | + self.ren.AddActor(aim_actor) | ||
890 | + | ||
891 | + obj_polydata = self.CreateObjectPolyData(os.path.join(const.OBJ_DIR, "magstim_fig8_coil_no_handle.stl")) | ||
892 | + | ||
893 | + transform = vtk.vtkTransform() | ||
894 | + transform.RotateZ(90) | ||
895 | + | ||
896 | + transform_filt = vtk.vtkTransformPolyDataFilter() | ||
897 | + transform_filt.SetTransform(transform) | ||
898 | + transform_filt.SetInputData(obj_polydata) | ||
899 | + transform_filt.Update() | ||
900 | + | ||
901 | + normals = vtk.vtkPolyDataNormals() | ||
902 | + normals.SetInputData(transform_filt.GetOutput()) | ||
903 | + normals.SetFeatureAngle(80) | ||
904 | + normals.AutoOrientNormalsOn() | ||
905 | + normals.Update() | ||
906 | + | ||
907 | + obj_mapper = vtk.vtkPolyDataMapper() | ||
908 | + obj_mapper.SetInputData(normals.GetOutput()) | ||
909 | + obj_mapper.ScalarVisibilityOff() | ||
910 | + obj_mapper.ImmediateModeRenderingOn() # improve performance | ||
911 | + | ||
912 | + self.dummy_coil_actor = vtk.vtkActor() | ||
913 | + self.dummy_coil_actor.SetMapper(obj_mapper) | ||
914 | + self.dummy_coil_actor.GetProperty().SetOpacity(0.4) | ||
915 | + self.dummy_coil_actor.SetVisibility(1) | ||
916 | + self.dummy_coil_actor.SetUserMatrix(m_img_vtk) | ||
917 | + | ||
918 | + self.ren.AddActor(self.dummy_coil_actor) | ||
919 | + | ||
920 | + self.Refresh() | ||
921 | + | ||
922 | + def RemoveTargetAim(self): | ||
923 | + self.ren.RemoveActor(self.aim_actor) | ||
924 | + self.ren.RemoveActor(self.dummy_coil_actor) | ||
925 | + self.ren.RemoveActor(self.tactor) | ||
926 | + self.Refresh() | ||
927 | + | ||
928 | + def CreateTextDistance(self): | ||
929 | + txt = vtku.Text() | ||
930 | + txt.SetSize(const.TEXT_SIZE_EXTRA_LARGE) | ||
931 | + txt.SetPosition((0.76, 0.05)) | ||
932 | + txt.ShadowOff() | ||
933 | + txt.BoldOn() | ||
934 | + self.txt = txt | ||
935 | + self.ren2.AddActor(txt.actor) | ||
936 | + | ||
937 | + def DisableCoilTracker(self): | ||
938 | + try: | ||
939 | + self.ren.SetViewport(0, 0, 1, 1) | ||
940 | + self.interactor.GetRenderWindow().RemoveRenderer(self.ren2) | ||
941 | + self.SetViewAngle(const.VOL_FRONT) | ||
942 | + self.ren.RemoveActor(self.txt.actor) | ||
943 | + self.CreateTargetAim() | ||
944 | + self.interactor.Render() | ||
945 | + except: | ||
946 | + None | ||
947 | + | ||
948 | + def CreateArrowActor(self, startPoint, endPoint): | ||
949 | + # Compute a basis | ||
950 | + normalizedX = [0 for i in range(3)] | ||
951 | + normalizedY = [0 for i in range(3)] | ||
952 | + normalizedZ = [0 for i in range(3)] | ||
953 | + | ||
954 | + # The X axis is a vector from start to end | ||
955 | + math = vtk.vtkMath() | ||
956 | + math.Subtract(endPoint, startPoint, normalizedX) | ||
957 | + length = math.Norm(normalizedX) | ||
958 | + math.Normalize(normalizedX) | ||
959 | + | ||
960 | + # The Z axis is an arbitrary vector cross X | ||
961 | + arbitrary = [0 for i in range(3)] | ||
962 | + arbitrary[0] = random.uniform(-10, 10) | ||
963 | + arbitrary[1] = random.uniform(-10, 10) | ||
964 | + arbitrary[2] = random.uniform(-10, 10) | ||
965 | + math.Cross(normalizedX, arbitrary, normalizedZ) | ||
966 | + math.Normalize(normalizedZ) | ||
967 | + | ||
968 | + # The Y axis is Z cross X | ||
969 | + math.Cross(normalizedZ, normalizedX, normalizedY) | ||
970 | + matrix = vtk.vtkMatrix4x4() | ||
971 | + | ||
972 | + # Create the direction cosine matrix | ||
973 | + matrix.Identity() | ||
974 | + for i in range(3): | ||
975 | + matrix.SetElement(i, 0, normalizedX[i]) | ||
976 | + matrix.SetElement(i, 1, normalizedY[i]) | ||
977 | + matrix.SetElement(i, 2, normalizedZ[i]) | ||
978 | + | ||
979 | + # Apply the transforms arrow 1 | ||
980 | + transform_1 = vtk.vtkTransform() | ||
981 | + transform_1.Translate(startPoint) | ||
982 | + transform_1.Concatenate(matrix) | ||
983 | + transform_1.Scale(length, length, length) | ||
984 | + # source | ||
985 | + arrowSource1 = vtk.vtkArrowSource() | ||
986 | + arrowSource1.SetTipResolution(50) | ||
987 | + # Create a mapper and actor | ||
988 | + mapper = vtk.vtkPolyDataMapper() | ||
989 | + mapper.SetInputConnection(arrowSource1.GetOutputPort()) | ||
990 | + # Transform the polydata | ||
991 | + transformPD = vtk.vtkTransformPolyDataFilter() | ||
992 | + transformPD.SetTransform(transform_1) | ||
993 | + transformPD.SetInputConnection(arrowSource1.GetOutputPort()) | ||
994 | + # mapper transform | ||
995 | + mapper.SetInputConnection(transformPD.GetOutputPort()) | ||
996 | + # actor | ||
997 | + actor_arrow = vtk.vtkActor() | ||
998 | + actor_arrow.SetMapper(mapper) | ||
999 | + | ||
1000 | + return actor_arrow | ||
1001 | + | ||
1002 | + def CenterOfMass(self): | ||
1003 | + proj = prj.Project() | ||
1004 | + surface = proj.surface_dict[0].polydata | ||
1005 | + barycenter = [0.0, 0.0, 0.0] | ||
1006 | + n = surface.GetNumberOfPoints() | ||
1007 | + for i in range(n): | ||
1008 | + point = surface.GetPoint(i) | ||
1009 | + barycenter[0] += point[0] | ||
1010 | + barycenter[1] += point[1] | ||
1011 | + barycenter[2] += point[2] | ||
1012 | + barycenter[0] /= n | ||
1013 | + barycenter[1] /= n | ||
1014 | + barycenter[2] /= n | ||
1015 | + | ||
1016 | + return barycenter | ||
1017 | + | ||
1018 | + def Plane(self, x0, pTarget): | ||
1019 | + v3 = np.array(pTarget) - x0 # normal to the plane | ||
1020 | + v3 = v3 / np.linalg.norm(v3) # unit vector | ||
1021 | + | ||
1022 | + d = np.dot(v3, x0) | ||
1023 | + # prevents division by zero. | ||
1024 | + if v3[0] == 0.0: | ||
1025 | + v3[0] = 1e-09 | ||
1026 | + | ||
1027 | + x1 = np.array([(d - v3[1] - v3[2]) / v3[0], 1, 1]) | ||
1028 | + v2 = x1 - x0 | ||
1029 | + v2 = v2 / np.linalg.norm(v2) # unit vector | ||
1030 | + v1 = np.cross(v3, v2) | ||
1031 | + v1 = v1 / np.linalg.norm(v1) # unit vector | ||
1032 | + x2 = x0 + v1 | ||
1033 | + # calculates the matrix for the change of coordinate systems (from canonical to the plane's). | ||
1034 | + # remember that, in np.dot(M,p), even though p is a line vector (e.g.,np.array([1,2,3])), it is treated as a column for the dot multiplication. | ||
1035 | + M_plane_inv = np.array([[v1[0], v2[0], v3[0], x0[0]], | ||
1036 | + [v1[1], v2[1], v3[1], x0[1]], | ||
1037 | + [v1[2], v2[2], v3[2], x0[2]], | ||
1038 | + [0, 0, 0, 1]]) | ||
1039 | + | ||
1040 | + return v3, M_plane_inv | ||
1041 | + | ||
1042 | + def SetCameraTarget(self): | ||
1043 | + cam_focus = self.target_coord[0:3] | ||
1044 | + cam = self.ren.GetActiveCamera() | ||
1045 | + | ||
1046 | + oldcamVTK = vtk.vtkMatrix4x4() | ||
1047 | + oldcamVTK.DeepCopy(cam.GetViewTransformMatrix()) | ||
1048 | + | ||
1049 | + newvtk = vtk.vtkMatrix4x4() | ||
1050 | + newvtk.Multiply4x4(self.m_img_vtk, oldcamVTK, newvtk) | ||
1051 | + | ||
1052 | + transform = vtk.vtkTransform() | ||
1053 | + transform.SetMatrix(newvtk) | ||
1054 | + transform.Update() | ||
1055 | + cam.ApplyTransform(transform) | ||
1056 | + | ||
1057 | + cam.Roll(90) | ||
1058 | + | ||
1059 | + cam_pos0 = np.array(cam.GetPosition()) | ||
1060 | + cam_focus0 = np.array(cam.GetFocalPoint()) | ||
1061 | + v0 = cam_pos0 - cam_focus0 | ||
1062 | + v0n = np.sqrt(inner1d(v0, v0)) | ||
1063 | + | ||
1064 | + v1 = (cam_focus[0] - cam_focus0[0], cam_focus[1] - cam_focus0[1], cam_focus[2] - cam_focus0[2]) | ||
1065 | + v1n = np.sqrt(inner1d(v1, v1)) | ||
1066 | + if not v1n: | ||
1067 | + v1n = 1.0 | ||
1068 | + cam_pos = (v1 / v1n) * v0n + cam_focus | ||
1069 | + cam.SetFocalPoint(cam_focus) | ||
1070 | + cam.SetPosition(cam_pos) | ||
1071 | + | ||
1072 | + | ||
541 | def CreateBallReference(self): | 1073 | def CreateBallReference(self): |
542 | """ | 1074 | """ |
543 | Red sphere on volume visualization to reference center of | 1075 | Red sphere on volume visualization to reference center of |
@@ -545,7 +1077,7 @@ class Viewer(wx.Panel): | @@ -545,7 +1077,7 @@ class Viewer(wx.Panel): | ||
545 | The sphere's radius will be scale times bigger than the average of | 1077 | The sphere's radius will be scale times bigger than the average of |
546 | image spacing values. | 1078 | image spacing values. |
547 | """ | 1079 | """ |
548 | - scale = 3.0 | 1080 | + scale = 2.0 |
549 | proj = prj.Project() | 1081 | proj = prj.Project() |
550 | s = proj.spacing | 1082 | s = proj.spacing |
551 | r = (s[0] + s[1] + s[2]) / 3.0 * scale | 1083 | r = (s[0] + s[1] + s[2]) / 3.0 * scale |
@@ -588,6 +1120,129 @@ class Viewer(wx.Panel): | @@ -588,6 +1120,129 @@ class Viewer(wx.Panel): | ||
588 | else: | 1120 | else: |
589 | self.RemoveBallReference() | 1121 | self.RemoveBallReference() |
590 | 1122 | ||
1123 | + def CreateObjectPolyData(self, filename): | ||
1124 | + """ | ||
1125 | + Coil for navigation rendered in volume viewer. | ||
1126 | + """ | ||
1127 | + | ||
1128 | + if filename: | ||
1129 | + if filename.lower().endswith('.stl'): | ||
1130 | + reader = vtk.vtkSTLReader() | ||
1131 | + elif filename.lower().endswith('.ply'): | ||
1132 | + reader = vtk.vtkPLYReader() | ||
1133 | + elif filename.lower().endswith('.obj'): | ||
1134 | + reader = vtk.vtkOBJReader() | ||
1135 | + elif filename.lower().endswith('.vtp'): | ||
1136 | + reader = vtk.vtkXMLPolyDataReader() | ||
1137 | + else: | ||
1138 | + wx.MessageBox(_("File format not reconized by InVesalius"), _("Import surface error")) | ||
1139 | + return | ||
1140 | + else: | ||
1141 | + filename = os.path.join(const.OBJ_DIR, "magstim_fig8_coil.stl") | ||
1142 | + reader = vtk.vtkSTLReader() | ||
1143 | + | ||
1144 | + if _has_win32api: | ||
1145 | + obj_name = win32api.GetShortPathName(filename).encode(const.FS_ENCODE) | ||
1146 | + else: | ||
1147 | + obj_name = filename.encode(const.FS_ENCODE) | ||
1148 | + | ||
1149 | + reader.SetFileName(obj_name) | ||
1150 | + reader.Update() | ||
1151 | + obj_polydata = reader.GetOutput() | ||
1152 | + | ||
1153 | + if obj_polydata.GetNumberOfPoints() == 0: | ||
1154 | + wx.MessageBox(_("InVesalius was not able to import this surface"), _("Import surface error")) | ||
1155 | + obj_polydata = None | ||
1156 | + | ||
1157 | + return obj_polydata | ||
1158 | + | ||
1159 | + def AddObjectActor(self, obj_name): | ||
1160 | + """ | ||
1161 | + Coil for navigation rendered in volume viewer. | ||
1162 | + """ | ||
1163 | + | ||
1164 | + obj_polydata = self.CreateObjectPolyData(obj_name) | ||
1165 | + | ||
1166 | + transform = vtk.vtkTransform() | ||
1167 | + transform.RotateZ(90) | ||
1168 | + | ||
1169 | + transform_filt = vtk.vtkTransformPolyDataFilter() | ||
1170 | + transform_filt.SetTransform(transform) | ||
1171 | + transform_filt.SetInputData(obj_polydata) | ||
1172 | + transform_filt.Update() | ||
1173 | + | ||
1174 | + normals = vtk.vtkPolyDataNormals() | ||
1175 | + normals.SetInputData(transform_filt.GetOutput()) | ||
1176 | + normals.SetFeatureAngle(80) | ||
1177 | + normals.AutoOrientNormalsOn() | ||
1178 | + normals.Update() | ||
1179 | + | ||
1180 | + obj_mapper = vtk.vtkPolyDataMapper() | ||
1181 | + obj_mapper.SetInputData(normals.GetOutput()) | ||
1182 | + obj_mapper.ScalarVisibilityOff() | ||
1183 | + obj_mapper.ImmediateModeRenderingOn() # improve performance | ||
1184 | + | ||
1185 | + self.obj_actor = vtk.vtkActor() | ||
1186 | + self.obj_actor.SetMapper(obj_mapper) | ||
1187 | + self.obj_actor.GetProperty().SetOpacity(0.9) | ||
1188 | + self.obj_actor.SetVisibility(0) | ||
1189 | + | ||
1190 | + self.ren.AddActor(self.obj_actor) | ||
1191 | + | ||
1192 | + # self.obj_axes = vtk.vtkAxesActor() | ||
1193 | + # self.obj_axes.SetShaftTypeToCylinder() | ||
1194 | + # self.obj_axes.SetXAxisLabelText("x") | ||
1195 | + # self.obj_axes.SetYAxisLabelText("y") | ||
1196 | + # self.obj_axes.SetZAxisLabelText("z") | ||
1197 | + # self.obj_axes.SetTotalLength(50.0, 50.0, 50.0) | ||
1198 | + | ||
1199 | + # self.ren.AddActor(self.obj_axes) | ||
1200 | + | ||
1201 | + def OnNavigationStatus(self, pubsub_evt): | ||
1202 | + self.nav_status = pubsub_evt.data | ||
1203 | + self.pTarget = self.CenterOfMass() | ||
1204 | + if self.obj_actor and self.nav_status: | ||
1205 | + self.obj_actor.SetVisibility(self.obj_state) | ||
1206 | + if not self.obj_state: | ||
1207 | + self.Refresh() | ||
1208 | + | ||
1209 | + def UpdateObjectOrientation(self, pubsub_evt): | ||
1210 | + | ||
1211 | + m_img = pubsub_evt.data[0] | ||
1212 | + | ||
1213 | + m_img[:3, -1] = np.asmatrix(bases.flip_x_m((m_img[0, -1], m_img[1, -1], m_img[2, -1]))).reshape([3, 1]) | ||
1214 | + | ||
1215 | + m_img_vtk = vtk.vtkMatrix4x4() | ||
1216 | + | ||
1217 | + for row in range(0, 4): | ||
1218 | + for col in range(0, 4): | ||
1219 | + m_img_vtk.SetElement(row, col, m_img[row, col]) | ||
1220 | + | ||
1221 | + self.obj_actor.SetUserMatrix(m_img_vtk) | ||
1222 | + # self.obj_axes.SetUserMatrix(m_rot_vtk) | ||
1223 | + | ||
1224 | + self.Refresh() | ||
1225 | + | ||
1226 | + def UpdateTrackObjectState(self, pubsub_evt): | ||
1227 | + if pubsub_evt.data[0]: | ||
1228 | + self.obj_name = pubsub_evt.data[1] | ||
1229 | + | ||
1230 | + if not self.obj_actor: | ||
1231 | + self.AddObjectActor(self.obj_name) | ||
1232 | + | ||
1233 | + else: | ||
1234 | + if self.obj_actor: | ||
1235 | + self.ren.RemoveActor(self.obj_actor) | ||
1236 | + self.obj_actor = None | ||
1237 | + | ||
1238 | + self.Refresh() | ||
1239 | + | ||
1240 | + def UpdateShowObjectState(self, pubsub_evt): | ||
1241 | + self.obj_state = pubsub_evt.data | ||
1242 | + if self.obj_actor and not self.obj_state: | ||
1243 | + self.obj_actor.SetVisibility(self.obj_state) | ||
1244 | + self.Refresh() | ||
1245 | + | ||
591 | def __bind_events_wx(self): | 1246 | def __bind_events_wx(self): |
592 | #self.Bind(wx.EVT_SIZE, self.OnSize) | 1247 | #self.Bind(wx.EVT_SIZE, self.OnSize) |
593 | pass | 1248 | pass |
@@ -613,7 +1268,7 @@ class Viewer(wx.Panel): | @@ -613,7 +1268,7 @@ class Viewer(wx.Panel): | ||
613 | "LeftButtonReleaseEvent": self.OnReleaseSpinClick, | 1268 | "LeftButtonReleaseEvent": self.OnReleaseSpinClick, |
614 | }, | 1269 | }, |
615 | const.STATE_WL: | 1270 | const.STATE_WL: |
616 | - { | 1271 | + { |
617 | "MouseMoveEvent": self.OnWindowLevelMove, | 1272 | "MouseMoveEvent": self.OnWindowLevelMove, |
618 | "LeftButtonPressEvent": self.OnWindowLevelClick, | 1273 | "LeftButtonPressEvent": self.OnWindowLevelClick, |
619 | "LeftButtonReleaseEvent":self.OnWindowLevelRelease | 1274 | "LeftButtonReleaseEvent":self.OnWindowLevelRelease |
@@ -661,7 +1316,7 @@ class Viewer(wx.Panel): | @@ -661,7 +1316,7 @@ class Viewer(wx.Panel): | ||
661 | else: | 1316 | else: |
662 | style = vtk.vtkInteractorStyleTrackballCamera() | 1317 | style = vtk.vtkInteractorStyleTrackballCamera() |
663 | self.interactor.SetInteractorStyle(style) | 1318 | self.interactor.SetInteractorStyle(style) |
664 | - self.style = style | 1319 | + self.style = style |
665 | 1320 | ||
666 | # Check each event available for each mode | 1321 | # Check each event available for each mode |
667 | for event in action[state]: | 1322 | for event in action[state]: |
@@ -755,8 +1410,8 @@ class Viewer(wx.Panel): | @@ -755,8 +1410,8 @@ class Viewer(wx.Panel): | ||
755 | 1410 | ||
756 | def SetVolumeCamera(self, pubsub_evt): | 1411 | def SetVolumeCamera(self, pubsub_evt): |
757 | if self.camera_state: | 1412 | if self.camera_state: |
758 | - #TODO: exclude dependency on initial focus | ||
759 | - cam_focus = np.array(bases.flip_x(pubsub_evt.data)) | 1413 | + # TODO: exclude dependency on initial focus |
1414 | + cam_focus = np.array(bases.flip_x(pubsub_evt.data[1][:3])) | ||
760 | cam = self.ren.GetActiveCamera() | 1415 | cam = self.ren.GetActiveCamera() |
761 | 1416 | ||
762 | if self.initial_focus is None: | 1417 | if self.initial_focus is None: |
@@ -764,11 +1419,14 @@ class Viewer(wx.Panel): | @@ -764,11 +1419,14 @@ class Viewer(wx.Panel): | ||
764 | 1419 | ||
765 | cam_pos0 = np.array(cam.GetPosition()) | 1420 | cam_pos0 = np.array(cam.GetPosition()) |
766 | cam_focus0 = np.array(cam.GetFocalPoint()) | 1421 | cam_focus0 = np.array(cam.GetFocalPoint()) |
767 | - | ||
768 | v0 = cam_pos0 - cam_focus0 | 1422 | v0 = cam_pos0 - cam_focus0 |
769 | v0n = np.sqrt(inner1d(v0, v0)) | 1423 | v0n = np.sqrt(inner1d(v0, v0)) |
770 | 1424 | ||
771 | - v1 = (cam_focus - self.initial_focus) | 1425 | + if self.obj_state: |
1426 | + v1 = (cam_focus[0] - self.pTarget[0], cam_focus[1] - self.pTarget[1], cam_focus[2] - self.pTarget[2]) | ||
1427 | + else: | ||
1428 | + v1 = (cam_focus - self.initial_focus) | ||
1429 | + | ||
772 | v1n = np.sqrt(inner1d(v1, v1)) | 1430 | v1n = np.sqrt(inner1d(v1, v1)) |
773 | if not v1n: | 1431 | if not v1n: |
774 | v1n = 1.0 | 1432 | v1n = 1.0 |
@@ -960,7 +1618,7 @@ class Viewer(wx.Panel): | @@ -960,7 +1618,7 @@ class Viewer(wx.Panel): | ||
960 | cam.SetViewUp(xv,yv,zv) | 1618 | cam.SetViewUp(xv,yv,zv) |
961 | cam.SetPosition(xp,yp,zp) | 1619 | cam.SetPosition(xp,yp,zp) |
962 | 1620 | ||
963 | - self.ren.ResetCameraClippingRange() | 1621 | + self.ren.ResetCameraClippingRange() |
964 | self.ren.ResetCamera() | 1622 | self.ren.ResetCamera() |
965 | self.interactor.Render() | 1623 | self.interactor.Render() |
966 | 1624 | ||
@@ -1020,10 +1678,10 @@ class Viewer(wx.Panel): | @@ -1020,10 +1678,10 @@ class Viewer(wx.Panel): | ||
1020 | x,y = self.interactor.GetEventPosition() | 1678 | x,y = self.interactor.GetEventPosition() |
1021 | self.measure_picker.Pick(x, y, 0, self.ren) | 1679 | self.measure_picker.Pick(x, y, 0, self.ren) |
1022 | x, y, z = self.measure_picker.GetPickPosition() | 1680 | x, y, z = self.measure_picker.GetPickPosition() |
1023 | - | 1681 | + |
1024 | proj = prj.Project() | 1682 | proj = prj.Project() |
1025 | radius = min(proj.spacing) * PROP_MEASURE | 1683 | radius = min(proj.spacing) * PROP_MEASURE |
1026 | - if self.measure_picker.GetActor(): | 1684 | + if self.measure_picker.GetActor(): |
1027 | # if not self.measures or self.measures[-1].IsComplete(): | 1685 | # if not self.measures or self.measures[-1].IsComplete(): |
1028 | # m = measures.LinearMeasure(self.ren) | 1686 | # m = measures.LinearMeasure(self.ren) |
1029 | # m.AddPoint(x, y, z) | 1687 | # m.AddPoint(x, y, z) |
@@ -1032,7 +1690,7 @@ class Viewer(wx.Panel): | @@ -1032,7 +1690,7 @@ class Viewer(wx.Panel): | ||
1032 | # m = self.measures[-1] | 1690 | # m = self.measures[-1] |
1033 | # m.AddPoint(x, y, z) | 1691 | # m.AddPoint(x, y, z) |
1034 | # if m.IsComplete(): | 1692 | # if m.IsComplete(): |
1035 | - # Publisher.sendMessage("Add measure to list", | 1693 | + # Publisher.sendMessage("Add measure to list", |
1036 | # (u"3D", _(u"%.3f mm" % m.GetValue()))) | 1694 | # (u"3D", _(u"%.3f mm" % m.GetValue()))) |
1037 | Publisher.sendMessage("Add measurement point", | 1695 | Publisher.sendMessage("Add measurement point", |
1038 | ((x, y,z), const.LINEAR, const.SURFACE, radius)) | 1696 | ((x, y,z), const.LINEAR, const.SURFACE, radius)) |
@@ -1045,7 +1703,7 @@ class Viewer(wx.Panel): | @@ -1045,7 +1703,7 @@ class Viewer(wx.Panel): | ||
1045 | 1703 | ||
1046 | proj = prj.Project() | 1704 | proj = prj.Project() |
1047 | radius = min(proj.spacing) * PROP_MEASURE | 1705 | radius = min(proj.spacing) * PROP_MEASURE |
1048 | - if self.measure_picker.GetActor(): | 1706 | + if self.measure_picker.GetActor(): |
1049 | # if not self.measures or self.measures[-1].IsComplete(): | 1707 | # if not self.measures or self.measures[-1].IsComplete(): |
1050 | # m = measures.AngularMeasure(self.ren) | 1708 | # m = measures.AngularMeasure(self.ren) |
1051 | # m.AddPoint(x, y, z) | 1709 | # m.AddPoint(x, y, z) |
invesalius/data/vtk_utils.py
@@ -124,6 +124,9 @@ class Text(object): | @@ -124,6 +124,9 @@ class Text(object): | ||
124 | def ShadowOff(self): | 124 | def ShadowOff(self): |
125 | self.property.ShadowOff() | 125 | self.property.ShadowOff() |
126 | 126 | ||
127 | + def BoldOn(self): | ||
128 | + self.property.BoldOn() | ||
129 | + | ||
127 | def SetSize(self, size): | 130 | def SetSize(self, size): |
128 | self.property.SetFontSize(size) | 131 | self.property.SetFontSize(size) |
129 | 132 | ||
@@ -135,15 +138,32 @@ class Text(object): | @@ -135,15 +138,32 @@ class Text(object): | ||
135 | # With some encoding in some dicom fields (like name) raises a | 138 | # With some encoding in some dicom fields (like name) raises a |
136 | # UnicodeEncodeError because they have non-ascii characters. To avoid | 139 | # UnicodeEncodeError because they have non-ascii characters. To avoid |
137 | # that we encode in utf-8. | 140 | # that we encode in utf-8. |
138 | - | 141 | + |
139 | if sys.platform == 'win32': | 142 | if sys.platform == 'win32': |
140 | - self.mapper.SetInput(value.encode("utf-8")) | 143 | + self.mapper.SetInput(value.encode("utf-8")) |
141 | else: | 144 | else: |
142 | try: | 145 | try: |
143 | self.mapper.SetInput(value.encode("latin-1")) | 146 | self.mapper.SetInput(value.encode("latin-1")) |
144 | except(UnicodeEncodeError): | 147 | except(UnicodeEncodeError): |
145 | self.mapper.SetInput(value.encode("utf-8")) | 148 | self.mapper.SetInput(value.encode("utf-8")) |
146 | 149 | ||
150 | + def SetCoilDistanceValue(self, value): | ||
151 | + if isinstance(value, int) or isinstance(value, float): | ||
152 | + value = 'Dist: ' + str("{:06.2f}".format(value)) + ' mm' | ||
153 | + if sys.platform == 'win32': | ||
154 | + value += "" # Otherwise 0 is not shown under win32 | ||
155 | + # With some encoding in some dicom fields (like name) raises a | ||
156 | + # UnicodeEncodeError because they have non-ascii characters. To avoid | ||
157 | + # that we encode in utf-8. | ||
158 | + | ||
159 | + if sys.platform == 'win32': | ||
160 | + self.mapper.SetInput(value.encode("utf-8")) | ||
161 | + else: | ||
162 | + try: | ||
163 | + self.mapper.SetInput(value.encode("latin-1")) | ||
164 | + except(UnicodeEncodeError): | ||
165 | + self.mapper.SetInput(value.encode("utf-8")) | ||
166 | + | ||
147 | def SetPosition(self, position): | 167 | def SetPosition(self, position): |
148 | self.actor.GetPositionCoordinate().SetValue(position[0], | 168 | self.actor.GetPositionCoordinate().SetValue(position[0], |
149 | position[1]) | 169 | position[1]) |
invesalius/gui/default_viewers.py
@@ -306,7 +306,7 @@ import wx.lib.colourselect as csel | @@ -306,7 +306,7 @@ import wx.lib.colourselect as csel | ||
306 | 306 | ||
307 | import invesalius.constants as const | 307 | import invesalius.constants as const |
308 | 308 | ||
309 | -[BUTTON_RAYCASTING, BUTTON_VIEW, BUTTON_SLICE_PLANE, BUTTON_3D_STEREO] = [wx.NewId() for num in xrange(4)] | 309 | +[BUTTON_RAYCASTING, BUTTON_VIEW, BUTTON_SLICE_PLANE, BUTTON_3D_STEREO, BUTTON_TARGET] = [wx.NewId() for num in xrange(5)] |
310 | RAYCASTING_TOOLS = wx.NewId() | 310 | RAYCASTING_TOOLS = wx.NewId() |
311 | 311 | ||
312 | ID_TO_NAME = {} | 312 | ID_TO_NAME = {} |
@@ -346,6 +346,9 @@ class VolumeToolPanel(wx.Panel): | @@ -346,6 +346,9 @@ class VolumeToolPanel(wx.Panel): | ||
346 | BMP_3D_STEREO = wx.Bitmap(os.path.join(const.ICON_DIR, "3D_glasses.png"), | 346 | BMP_3D_STEREO = wx.Bitmap(os.path.join(const.ICON_DIR, "3D_glasses.png"), |
347 | wx.BITMAP_TYPE_PNG) | 347 | wx.BITMAP_TYPE_PNG) |
348 | 348 | ||
349 | + BMP_TARGET = wx.Bitmap(os.path.join(const.ICON_DIR, "target.png"), | ||
350 | + wx.BITMAP_TYPE_PNG) | ||
351 | + | ||
349 | 352 | ||
350 | button_raycasting = pbtn.PlateButton(self, BUTTON_RAYCASTING,"", | 353 | button_raycasting = pbtn.PlateButton(self, BUTTON_RAYCASTING,"", |
351 | BMP_RAYCASTING, style=pbtn.PB_STYLE_SQUARE, | 354 | BMP_RAYCASTING, style=pbtn.PB_STYLE_SQUARE, |
@@ -359,6 +362,11 @@ class VolumeToolPanel(wx.Panel): | @@ -359,6 +362,11 @@ class VolumeToolPanel(wx.Panel): | ||
359 | BMP_SLICE_PLANE, style=pbtn.PB_STYLE_SQUARE, | 362 | BMP_SLICE_PLANE, style=pbtn.PB_STYLE_SQUARE, |
360 | size=(32,32)) | 363 | size=(32,32)) |
361 | 364 | ||
365 | + button_target = self.button_target = pbtn.PlateButton(self, BUTTON_TARGET,"", | ||
366 | + BMP_TARGET, style=pbtn.PB_STYLE_SQUARE|pbtn.PB_STYLE_TOGGLE, | ||
367 | + size=(32,32)) | ||
368 | + self.button_target.Enable(0) | ||
369 | + | ||
362 | self.button_raycasting = button_raycasting | 370 | self.button_raycasting = button_raycasting |
363 | self.button_stereo = button_stereo | 371 | self.button_stereo = button_stereo |
364 | 372 | ||
@@ -389,7 +397,11 @@ class VolumeToolPanel(wx.Panel): | @@ -389,7 +397,11 @@ class VolumeToolPanel(wx.Panel): | ||
389 | sizer.Add(button_view, 0, wx.TOP|wx.BOTTOM, 1) | 397 | sizer.Add(button_view, 0, wx.TOP|wx.BOTTOM, 1) |
390 | sizer.Add(button_slice_plane, 0, wx.TOP|wx.BOTTOM, 1) | 398 | sizer.Add(button_slice_plane, 0, wx.TOP|wx.BOTTOM, 1) |
391 | sizer.Add(button_stereo, 0, wx.TOP|wx.BOTTOM, 1) | 399 | sizer.Add(button_stereo, 0, wx.TOP|wx.BOTTOM, 1) |
400 | + sizer.Add(button_target, 0, wx.TOP | wx.BOTTOM, 1) | ||
392 | 401 | ||
402 | + self.navigation_status = False | ||
403 | + self.status_target_select = False | ||
404 | + self.status_obj_tracker = False | ||
393 | 405 | ||
394 | sizer.Fit(self) | 406 | sizer.Fit(self) |
395 | 407 | ||
@@ -408,6 +420,8 @@ class VolumeToolPanel(wx.Panel): | @@ -408,6 +420,8 @@ class VolumeToolPanel(wx.Panel): | ||
408 | Publisher.subscribe(self.DisablePreset, 'Close project data') | 420 | Publisher.subscribe(self.DisablePreset, 'Close project data') |
409 | Publisher.subscribe(self.Uncheck, 'Uncheck image plane menu') | 421 | Publisher.subscribe(self.Uncheck, 'Uncheck image plane menu') |
410 | Publisher.subscribe(self.DisableVolumeCutMenu, 'Disable volume cut menu') | 422 | Publisher.subscribe(self.DisableVolumeCutMenu, 'Disable volume cut menu') |
423 | + Publisher.subscribe(self.StatusTargetSelect, 'Disable or enable coil tracker') | ||
424 | + Publisher.subscribe(self.StatusObjTracker, 'Status target button') | ||
411 | 425 | ||
412 | def DisablePreset(self, pubsub_evt): | 426 | def DisablePreset(self, pubsub_evt): |
413 | self.off_item.Check(1) | 427 | self.off_item.Check(1) |
@@ -419,6 +433,7 @@ class VolumeToolPanel(wx.Panel): | @@ -419,6 +433,7 @@ class VolumeToolPanel(wx.Panel): | ||
419 | self.button_view.Bind(wx.EVT_LEFT_DOWN, self.OnButtonView) | 433 | self.button_view.Bind(wx.EVT_LEFT_DOWN, self.OnButtonView) |
420 | self.button_colour.Bind(csel.EVT_COLOURSELECT, self.OnSelectColour) | 434 | self.button_colour.Bind(csel.EVT_COLOURSELECT, self.OnSelectColour) |
421 | self.button_stereo.Bind(wx.EVT_LEFT_DOWN, self.OnButtonStereo) | 435 | self.button_stereo.Bind(wx.EVT_LEFT_DOWN, self.OnButtonStereo) |
436 | + self.button_target.Bind(wx.EVT_LEFT_DOWN, self.OnButtonTarget) | ||
422 | 437 | ||
423 | def OnButtonRaycasting(self, evt): | 438 | def OnButtonRaycasting(self, evt): |
424 | # MENU RELATED TO RAYCASTING TYPES | 439 | # MENU RELATED TO RAYCASTING TYPES |
@@ -433,6 +448,29 @@ class VolumeToolPanel(wx.Panel): | @@ -433,6 +448,29 @@ class VolumeToolPanel(wx.Panel): | ||
433 | def OnButtonSlicePlane(self, evt): | 448 | def OnButtonSlicePlane(self, evt): |
434 | self.button_slice_plane.PopupMenu(self.slice_plane_menu) | 449 | self.button_slice_plane.PopupMenu(self.slice_plane_menu) |
435 | 450 | ||
451 | + def StatusObjTracker(self, pubsub_evt): | ||
452 | + self.status_obj_tracker = pubsub_evt.data | ||
453 | + self.StatusNavigation() | ||
454 | + | ||
455 | + def StatusTargetSelect(self, pubsub_evt): | ||
456 | + self.status_target_select = pubsub_evt.data | ||
457 | + self.StatusNavigation() | ||
458 | + | ||
459 | + def StatusNavigation(self): | ||
460 | + if self.status_target_select and self.status_obj_tracker: | ||
461 | + self.button_target.Enable(1) | ||
462 | + else: | ||
463 | + self.OnButtonTarget(False) | ||
464 | + self.button_target.Enable(0) | ||
465 | + | ||
466 | + def OnButtonTarget(self, evt): | ||
467 | + if not self.button_target.IsPressed() and evt is not False: | ||
468 | + self.button_target._pressed = True | ||
469 | + Publisher.sendMessage('Target navigation mode', self.button_target._pressed) | ||
470 | + elif self.button_target.IsPressed() or evt is False: | ||
471 | + self.button_target._pressed = False | ||
472 | + Publisher.sendMessage('Target navigation mode', self.button_target._pressed) | ||
473 | + | ||
436 | def OnSavePreset(self, evt): | 474 | def OnSavePreset(self, evt): |
437 | d = wx.TextEntryDialog(self, _("Preset name")) | 475 | d = wx.TextEntryDialog(self, _("Preset name")) |
438 | if d.ShowModal() == wx.ID_OK: | 476 | if d.ShowModal() == wx.ID_OK: |
invesalius/gui/dialogs.py
@@ -18,10 +18,20 @@ | @@ -18,10 +18,20 @@ | ||
18 | # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | 18 | # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais |
19 | # detalhes. | 19 | # detalhes. |
20 | #-------------------------------------------------------------------------- | 20 | #-------------------------------------------------------------------------- |
21 | + | ||
21 | import os | 22 | import os |
22 | import random | 23 | import random |
23 | import sys | 24 | import sys |
24 | 25 | ||
26 | +if sys.platform == 'win32': | ||
27 | + try: | ||
28 | + import win32api | ||
29 | + _has_win32api = True | ||
30 | + except ImportError: | ||
31 | + _has_win32api = False | ||
32 | +else: | ||
33 | + _has_win32api = False | ||
34 | + | ||
25 | import vtk | 35 | import vtk |
26 | import wx | 36 | import wx |
27 | import wx.combo | 37 | import wx.combo |
@@ -33,6 +43,7 @@ from wx.lib.wordwrap import wordwrap | @@ -33,6 +43,7 @@ from wx.lib.wordwrap import wordwrap | ||
33 | from wx.lib.pubsub import pub as Publisher | 43 | from wx.lib.pubsub import pub as Publisher |
34 | 44 | ||
35 | import invesalius.constants as const | 45 | import invesalius.constants as const |
46 | +import invesalius.data.coordinates as dco | ||
36 | import invesalius.gui.widgets.gradient as grad | 47 | import invesalius.gui.widgets.gradient as grad |
37 | import invesalius.session as ses | 48 | import invesalius.session as ses |
38 | import invesalius.utils as utils | 49 | import invesalius.utils as utils |
@@ -470,6 +481,36 @@ def ShowSaveMarkersDialog(default_filename=None): | @@ -470,6 +481,36 @@ def ShowSaveMarkersDialog(default_filename=None): | ||
470 | os.chdir(current_dir) | 481 | os.chdir(current_dir) |
471 | return filename | 482 | return filename |
472 | 483 | ||
484 | +def ShowSaveCoordsDialog(default_filename=None): | ||
485 | + current_dir = os.path.abspath(".") | ||
486 | + dlg = wx.FileDialog(None, | ||
487 | + _("Save coords as..."), # title | ||
488 | + "", # last used directory | ||
489 | + default_filename, | ||
490 | + _("Coordinates files (*.csv)|*.csv"), | ||
491 | + wx.SAVE | wx.OVERWRITE_PROMPT) | ||
492 | + # dlg.SetFilterIndex(0) # default is VTI | ||
493 | + | ||
494 | + filename = None | ||
495 | + try: | ||
496 | + if dlg.ShowModal() == wx.ID_OK: | ||
497 | + filename = dlg.GetPath() | ||
498 | + ok = 1 | ||
499 | + else: | ||
500 | + ok = 0 | ||
501 | + except(wx._core.PyAssertionError): # TODO: fix win64 | ||
502 | + filename = dlg.GetPath() | ||
503 | + ok = 1 | ||
504 | + | ||
505 | + if (ok): | ||
506 | + extension = "csv" | ||
507 | + if sys.platform != 'win32': | ||
508 | + if filename.split(".")[-1] != extension: | ||
509 | + filename = filename + "." + extension | ||
510 | + | ||
511 | + os.chdir(current_dir) | ||
512 | + return filename | ||
513 | + | ||
473 | 514 | ||
474 | def ShowLoadMarkersDialog(): | 515 | def ShowLoadMarkersDialog(): |
475 | current_dir = os.path.abspath(".") | 516 | current_dir = os.path.abspath(".") |
@@ -500,6 +541,66 @@ def ShowLoadMarkersDialog(): | @@ -500,6 +541,66 @@ def ShowLoadMarkersDialog(): | ||
500 | return filepath | 541 | return filepath |
501 | 542 | ||
502 | 543 | ||
544 | +def ShowSaveRegistrationDialog(default_filename=None): | ||
545 | + current_dir = os.path.abspath(".") | ||
546 | + dlg = wx.FileDialog(None, | ||
547 | + _("Save object registration as..."), # title | ||
548 | + "", # last used directory | ||
549 | + default_filename, | ||
550 | + _("Registration files (*.obr)|*.obr"), | ||
551 | + wx.SAVE | wx.OVERWRITE_PROMPT) | ||
552 | + # dlg.SetFilterIndex(0) # default is VTI | ||
553 | + | ||
554 | + filename = None | ||
555 | + try: | ||
556 | + if dlg.ShowModal() == wx.ID_OK: | ||
557 | + filename = dlg.GetPath() | ||
558 | + ok = 1 | ||
559 | + else: | ||
560 | + ok = 0 | ||
561 | + except(wx._core.PyAssertionError): # TODO: fix win64 | ||
562 | + filename = dlg.GetPath() | ||
563 | + ok = 1 | ||
564 | + | ||
565 | + if (ok): | ||
566 | + extension = "obr" | ||
567 | + if sys.platform != 'win32': | ||
568 | + if filename.split(".")[-1] != extension: | ||
569 | + filename = filename + "." + extension | ||
570 | + | ||
571 | + os.chdir(current_dir) | ||
572 | + return filename | ||
573 | + | ||
574 | + | ||
575 | +def ShowLoadRegistrationDialog(): | ||
576 | + current_dir = os.path.abspath(".") | ||
577 | + | ||
578 | + dlg = wx.FileDialog(None, message=_("Load object registration"), | ||
579 | + defaultDir="", | ||
580 | + defaultFile="", | ||
581 | + wildcard=_("Registration files (*.obr)|*.obr"), | ||
582 | + style=wx.OPEN|wx.CHANGE_DIR) | ||
583 | + | ||
584 | + # inv3 filter is default | ||
585 | + dlg.SetFilterIndex(0) | ||
586 | + | ||
587 | + # Show the dialog and retrieve the user response. If it is the OK response, | ||
588 | + # process the data. | ||
589 | + filepath = None | ||
590 | + try: | ||
591 | + if dlg.ShowModal() == wx.ID_OK: | ||
592 | + # This returns a Python list of files that were selected. | ||
593 | + filepath = dlg.GetPath() | ||
594 | + except(wx._core.PyAssertionError): # FIX: win64 | ||
595 | + filepath = dlg.GetPath() | ||
596 | + | ||
597 | + # Destroy the dialog. Don't do this until you are done with it! | ||
598 | + # BAD things can happen otherwise! | ||
599 | + dlg.Destroy() | ||
600 | + os.chdir(current_dir) | ||
601 | + return filepath | ||
602 | + | ||
603 | + | ||
503 | class MessageDialog(wx.Dialog): | 604 | class MessageDialog(wx.Dialog): |
504 | def __init__(self, message): | 605 | def __init__(self, message): |
505 | pre = wx.PreDialog() | 606 | pre = wx.PreDialog() |
@@ -729,7 +830,19 @@ def SurfaceSelectionRequiredForDuplication(): | @@ -729,7 +830,19 @@ def SurfaceSelectionRequiredForDuplication(): | ||
729 | 830 | ||
730 | # Dialogs for neuronavigation mode | 831 | # Dialogs for neuronavigation mode |
731 | def InvalidFiducials(): | 832 | def InvalidFiducials(): |
732 | - msg = _("Fiducials are invalid. Select six coordinates.") | 833 | + msg = _("Fiducials are invalid. Select all coordinates.") |
834 | + if sys.platform == 'darwin': | ||
835 | + dlg = wx.MessageDialog(None, "", msg, | ||
836 | + wx.ICON_INFORMATION | wx.OK) | ||
837 | + else: | ||
838 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
839 | + wx.ICON_INFORMATION | wx.OK) | ||
840 | + dlg.ShowModal() | ||
841 | + dlg.Destroy() | ||
842 | + | ||
843 | + | ||
844 | +def InvalidObjectRegistration(): | ||
845 | + msg = _("Perform coil registration before navigation.") | ||
733 | if sys.platform == 'darwin': | 846 | if sys.platform == 'darwin': |
734 | dlg = wx.MessageDialog(None, "", msg, | 847 | dlg = wx.MessageDialog(None, "", msg, |
735 | wx.ICON_INFORMATION | wx.OK) | 848 | wx.ICON_INFORMATION | wx.OK) |
@@ -793,6 +906,7 @@ def NoMarkerSelected(): | @@ -793,6 +906,7 @@ def NoMarkerSelected(): | ||
793 | dlg.ShowModal() | 906 | dlg.ShowModal() |
794 | dlg.Destroy() | 907 | dlg.Destroy() |
795 | 908 | ||
909 | + | ||
796 | def DeleteAllMarkers(): | 910 | def DeleteAllMarkers(): |
797 | msg = _("Do you really want to delete all markers?") | 911 | msg = _("Do you really want to delete all markers?") |
798 | if sys.platform == 'darwin': | 912 | if sys.platform == 'darwin': |
@@ -805,6 +919,38 @@ def DeleteAllMarkers(): | @@ -805,6 +919,38 @@ def DeleteAllMarkers(): | ||
805 | dlg.Destroy() | 919 | dlg.Destroy() |
806 | return result | 920 | return result |
807 | 921 | ||
922 | +def DeleteTarget(): | ||
923 | + msg = _("Target deleted") | ||
924 | + if sys.platform == 'darwin': | ||
925 | + dlg = wx.MessageDialog(None, "", msg, | ||
926 | + wx.ICON_INFORMATION | wx.OK) | ||
927 | + else: | ||
928 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
929 | + wx.ICON_INFORMATION | wx.OK) | ||
930 | + dlg.ShowModal() | ||
931 | + dlg.Destroy() | ||
932 | + | ||
933 | +def NewTarget(): | ||
934 | + msg = _("New target selected") | ||
935 | + if sys.platform == 'darwin': | ||
936 | + dlg = wx.MessageDialog(None, "", msg, | ||
937 | + wx.ICON_INFORMATION | wx.OK) | ||
938 | + else: | ||
939 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
940 | + wx.ICON_INFORMATION | wx.OK) | ||
941 | + dlg.ShowModal() | ||
942 | + dlg.Destroy() | ||
943 | + | ||
944 | +def InvalidTargetID(): | ||
945 | + msg = _("Sorry, you cannot use 'TARGET' ID") | ||
946 | + if sys.platform == 'darwin': | ||
947 | + dlg = wx.MessageDialog(None, "", msg, | ||
948 | + wx.ICON_INFORMATION | wx.OK) | ||
949 | + else: | ||
950 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
951 | + wx.ICON_INFORMATION | wx.OK) | ||
952 | + dlg.ShowModal() | ||
953 | + dlg.Destroy() | ||
808 | 954 | ||
809 | def EnterMarkerID(default): | 955 | def EnterMarkerID(default): |
810 | msg = _("Edit marker ID") | 956 | msg = _("Edit marker ID") |
@@ -2967,3 +3113,246 @@ class FillHolesAutoDialog(wx.Dialog): | @@ -2967,3 +3113,246 @@ class FillHolesAutoDialog(wx.Dialog): | ||
2967 | else: | 3113 | else: |
2968 | self.panel3dcon.Enable(1) | 3114 | self.panel3dcon.Enable(1) |
2969 | self.panel2dcon.Enable(0) | 3115 | self.panel2dcon.Enable(0) |
3116 | + | ||
3117 | + | ||
3118 | +class ObjectCalibrationDialog(wx.Dialog): | ||
3119 | + | ||
3120 | + def __init__(self, nav_prop): | ||
3121 | + | ||
3122 | + self.tracker_id = nav_prop[0] | ||
3123 | + self.trk_init = nav_prop[1] | ||
3124 | + self.obj_ref_id = 0 | ||
3125 | + self.obj_name = None | ||
3126 | + | ||
3127 | + self.obj_fiducials = np.full([5, 3], np.nan) | ||
3128 | + self.obj_orients = np.full([5, 3], np.nan) | ||
3129 | + | ||
3130 | + pre = wx.PreDialog() | ||
3131 | + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Object calibration"), size=(450, 440), | ||
3132 | + style=wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT) | ||
3133 | + self.PostCreate(pre) | ||
3134 | + | ||
3135 | + self._init_gui() | ||
3136 | + self.LoadObject() | ||
3137 | + | ||
3138 | + def _init_gui(self): | ||
3139 | + self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize()) | ||
3140 | + self.interactor.Enable(1) | ||
3141 | + self.ren = vtk.vtkRenderer() | ||
3142 | + self.interactor.GetRenderWindow().AddRenderer(self.ren) | ||
3143 | + | ||
3144 | + # Initialize list of buttons and txtctrls for wx objects | ||
3145 | + self.btns_coord = [None] * 5 | ||
3146 | + self.text_actors = [None] * 5 | ||
3147 | + self.ball_actors = [None] * 5 | ||
3148 | + self.txt_coord = [list(), list(), list(), list(), list()] | ||
3149 | + | ||
3150 | + # ComboBox for tracker reference mode | ||
3151 | + tooltip = wx.ToolTip(_(u"Choose the object reference mode")) | ||
3152 | + choice_ref = wx.ComboBox(self, -1, "", size=wx.Size(90, 23), | ||
3153 | + choices=const.REF_MODE, style=wx.CB_DROPDOWN | wx.CB_READONLY) | ||
3154 | + choice_ref.SetSelection(self.obj_ref_id) | ||
3155 | + choice_ref.SetToolTip(tooltip) | ||
3156 | + choice_ref.Bind(wx.EVT_COMBOBOX, self.OnChoiceRefMode) | ||
3157 | + choice_ref.Enable(0) | ||
3158 | + | ||
3159 | + # Buttons to finish or cancel object registration | ||
3160 | + tooltip = wx.ToolTip(_(u"Registration done")) | ||
3161 | + # btn_ok = wx.Button(self, -1, _(u"Done"), size=wx.Size(90, 30)) | ||
3162 | + btn_ok = wx.Button(self, wx.ID_OK, _(u"Done"), size=wx.Size(90, 30)) | ||
3163 | + btn_ok.SetToolTip(tooltip) | ||
3164 | + | ||
3165 | + extra_sizer = wx.FlexGridSizer(rows=2, cols=1, hgap=5, vgap=30) | ||
3166 | + extra_sizer.AddMany([choice_ref, | ||
3167 | + btn_ok]) | ||
3168 | + | ||
3169 | + # Push buttons for object fiducials | ||
3170 | + btns_obj = const.BTNS_OBJ | ||
3171 | + tips_obj = const.TIPS_OBJ | ||
3172 | + | ||
3173 | + for k in btns_obj: | ||
3174 | + n = btns_obj[k].keys()[0] | ||
3175 | + lab = btns_obj[k].values()[0] | ||
3176 | + self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(60, 23)) | ||
3177 | + self.btns_coord[n].SetToolTip(wx.ToolTip(tips_obj[n])) | ||
3178 | + self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnGetObjectFiducials) | ||
3179 | + | ||
3180 | + for m in range(0, 5): | ||
3181 | + for n in range(0, 3): | ||
3182 | + self.txt_coord[m].append(wx.StaticText(self, -1, label='-', | ||
3183 | + style=wx.ALIGN_RIGHT, size=wx.Size(40, 23))) | ||
3184 | + | ||
3185 | + coord_sizer = wx.GridBagSizer(hgap=20, vgap=5) | ||
3186 | + | ||
3187 | + for m in range(0, 5): | ||
3188 | + coord_sizer.Add(self.btns_coord[m], pos=wx.GBPosition(m, 0)) | ||
3189 | + for n in range(0, 3): | ||
3190 | + coord_sizer.Add(self.txt_coord[m][n], pos=wx.GBPosition(m, n + 1), flag=wx.TOP, border=5) | ||
3191 | + | ||
3192 | + group_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=50, vgap=5) | ||
3193 | + group_sizer.AddMany([(coord_sizer, 0, wx.LEFT, 20), | ||
3194 | + (extra_sizer, 0, wx.LEFT, 10)]) | ||
3195 | + | ||
3196 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
3197 | + main_sizer.Add(self.interactor, 0, wx.EXPAND) | ||
3198 | + main_sizer.Add(group_sizer, 0, | ||
3199 | + wx.EXPAND|wx.GROW|wx.LEFT|wx.TOP|wx.RIGHT|wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 10) | ||
3200 | + | ||
3201 | + self.SetSizer(main_sizer) | ||
3202 | + main_sizer.Fit(self) | ||
3203 | + | ||
3204 | + def ObjectImportDialog(self): | ||
3205 | + msg = _("Would like to use InVesalius default object?") | ||
3206 | + if sys.platform == 'darwin': | ||
3207 | + dlg = wx.MessageDialog(None, "", msg, | ||
3208 | + wx.ICON_QUESTION | wx.YES_NO) | ||
3209 | + else: | ||
3210 | + dlg = wx.MessageDialog(None, msg, | ||
3211 | + "InVesalius 3", | ||
3212 | + wx.ICON_QUESTION | wx.YES_NO) | ||
3213 | + answer = dlg.ShowModal() | ||
3214 | + dlg.Destroy() | ||
3215 | + | ||
3216 | + if answer == wx.ID_YES: | ||
3217 | + return 1 | ||
3218 | + else: # answer == wx.ID_NO: | ||
3219 | + return 0 | ||
3220 | + | ||
3221 | + def LoadObject(self): | ||
3222 | + default = self.ObjectImportDialog() | ||
3223 | + if not default: | ||
3224 | + filename = ShowImportMeshFilesDialog() | ||
3225 | + | ||
3226 | + if filename: | ||
3227 | + if filename.lower().endswith('.stl'): | ||
3228 | + reader = vtk.vtkSTLReader() | ||
3229 | + elif filename.lower().endswith('.ply'): | ||
3230 | + reader = vtk.vtkPLYReader() | ||
3231 | + elif filename.lower().endswith('.obj'): | ||
3232 | + reader = vtk.vtkOBJReader() | ||
3233 | + elif filename.lower().endswith('.vtp'): | ||
3234 | + reader = vtk.vtkXMLPolyDataReader() | ||
3235 | + else: | ||
3236 | + wx.MessageBox(_("File format not reconized by InVesalius"), _("Import surface error")) | ||
3237 | + return | ||
3238 | + else: | ||
3239 | + filename = os.path.join(const.OBJ_DIR, "magstim_fig8_coil.stl") | ||
3240 | + reader = vtk.vtkSTLReader() | ||
3241 | + else: | ||
3242 | + filename = os.path.join(const.OBJ_DIR, "magstim_fig8_coil.stl") | ||
3243 | + reader = vtk.vtkSTLReader() | ||
3244 | + | ||
3245 | + if _has_win32api: | ||
3246 | + self.obj_name = win32api.GetShortPathName(filename).encode(const.FS_ENCODE) | ||
3247 | + else: | ||
3248 | + self.obj_name = filename.encode(const.FS_ENCODE) | ||
3249 | + | ||
3250 | + reader.SetFileName(self.obj_name) | ||
3251 | + reader.Update() | ||
3252 | + polydata = reader.GetOutput() | ||
3253 | + | ||
3254 | + if polydata.GetNumberOfPoints() == 0: | ||
3255 | + wx.MessageBox(_("InVesalius was not able to import this surface"), _("Import surface error")) | ||
3256 | + | ||
3257 | + transform = vtk.vtkTransform() | ||
3258 | + transform.RotateZ(90) | ||
3259 | + | ||
3260 | + transform_filt = vtk.vtkTransformPolyDataFilter() | ||
3261 | + transform_filt.SetTransform(transform) | ||
3262 | + transform_filt.SetInputData(polydata) | ||
3263 | + transform_filt.Update() | ||
3264 | + | ||
3265 | + normals = vtk.vtkPolyDataNormals() | ||
3266 | + normals.SetInputData(transform_filt.GetOutput()) | ||
3267 | + normals.SetFeatureAngle(80) | ||
3268 | + normals.AutoOrientNormalsOn() | ||
3269 | + normals.Update() | ||
3270 | + | ||
3271 | + mapper = vtk.vtkPolyDataMapper() | ||
3272 | + mapper.SetInputData(normals.GetOutput()) | ||
3273 | + mapper.ScalarVisibilityOff() | ||
3274 | + mapper.ImmediateModeRenderingOn() | ||
3275 | + | ||
3276 | + obj_actor = vtk.vtkActor() | ||
3277 | + obj_actor.SetMapper(mapper) | ||
3278 | + | ||
3279 | + self.ball_actors[0], self.text_actors[0] = self.OnCreateObjectText('Left', (0,55,0)) | ||
3280 | + self.ball_actors[1], self.text_actors[1] = self.OnCreateObjectText('Right', (0,-55,0)) | ||
3281 | + self.ball_actors[2], self.text_actors[2] = self.OnCreateObjectText('Anterior', (23,0,0)) | ||
3282 | + | ||
3283 | + self.ren.AddActor(obj_actor) | ||
3284 | + self.ren.ResetCamera() | ||
3285 | + | ||
3286 | + self.interactor.Render() | ||
3287 | + | ||
3288 | + def OnCreateObjectText(self, name, coord): | ||
3289 | + ball_source = vtk.vtkSphereSource() | ||
3290 | + ball_source.SetRadius(3) | ||
3291 | + mapper = vtk.vtkPolyDataMapper() | ||
3292 | + mapper.SetInputConnection(ball_source.GetOutputPort()) | ||
3293 | + ball_actor = vtk.vtkActor() | ||
3294 | + ball_actor.SetMapper(mapper) | ||
3295 | + ball_actor.SetPosition(coord) | ||
3296 | + ball_actor.GetProperty().SetColor(1, 0, 0) | ||
3297 | + | ||
3298 | + textSource = vtk.vtkVectorText() | ||
3299 | + textSource.SetText(name) | ||
3300 | + | ||
3301 | + mapper = vtk.vtkPolyDataMapper() | ||
3302 | + mapper.SetInputConnection(textSource.GetOutputPort()) | ||
3303 | + tactor = vtk.vtkFollower() | ||
3304 | + tactor.SetMapper(mapper) | ||
3305 | + tactor.GetProperty().SetColor(1.0, 0.0, 0.0) | ||
3306 | + tactor.SetScale(5) | ||
3307 | + ball_position = ball_actor.GetPosition() | ||
3308 | + tactor.SetPosition(ball_position[0]+5, ball_position[1]+5, ball_position[2]+10) | ||
3309 | + self.ren.AddActor(tactor) | ||
3310 | + tactor.SetCamera(self.ren.GetActiveCamera()) | ||
3311 | + self.ren.AddActor(ball_actor) | ||
3312 | + return ball_actor, tactor | ||
3313 | + | ||
3314 | + def OnGetObjectFiducials(self, evt): | ||
3315 | + btn_id = const.BTNS_OBJ[evt.GetId()].keys()[0] | ||
3316 | + | ||
3317 | + if self.trk_init and self.tracker_id: | ||
3318 | + coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.obj_ref_id) | ||
3319 | + if self.obj_ref_id and btn_id == 4: | ||
3320 | + coord = coord_raw[2, :] | ||
3321 | + else: | ||
3322 | + coord = coord_raw[0, :] | ||
3323 | + else: | ||
3324 | + NavigationTrackerWarning(0, 'choose') | ||
3325 | + | ||
3326 | + # Update text controls with tracker coordinates | ||
3327 | + if coord is not None or np.sum(coord) != 0.0: | ||
3328 | + self.obj_fiducials[btn_id, :] = coord[:3] | ||
3329 | + self.obj_orients[btn_id, :] = coord[3:] | ||
3330 | + for n in [0, 1, 2]: | ||
3331 | + self.txt_coord[btn_id][n].SetLabel(str(round(coord[n], 1))) | ||
3332 | + if self.text_actors[btn_id]: | ||
3333 | + self.text_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0) | ||
3334 | + self.ball_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0) | ||
3335 | + self.Refresh() | ||
3336 | + else: | ||
3337 | + NavigationTrackerWarning(0, 'choose') | ||
3338 | + | ||
3339 | + def OnChoiceRefMode(self, evt): | ||
3340 | + # When ref mode is changed the tracker coordinates are set to nan | ||
3341 | + # This is for Polhemus FASTRAK wrapper, where the sensor attached to the object can be the stylus (Static | ||
3342 | + # reference - Selection 0 - index 0 for coordinates) or can be a 3rd sensor (Dynamic reference - Selection 1 - | ||
3343 | + # index 2 for coordinates) | ||
3344 | + # I use the index 2 directly here to send to the coregistration module where it is possible to access without | ||
3345 | + # any conditional statement the correct index of coordinates. | ||
3346 | + | ||
3347 | + if evt.GetSelection(): | ||
3348 | + self.obj_ref_id = 2 | ||
3349 | + else: | ||
3350 | + self.obj_ref_id = 0 | ||
3351 | + for m in range(0, 5): | ||
3352 | + self.obj_fiducials[m, :] = np.full([1, 3], np.nan) | ||
3353 | + self.obj_orients[m, :] = np.full([1, 3], np.nan) | ||
3354 | + for n in range(0, 3): | ||
3355 | + self.txt_coord[m][n].SetLabel('-') | ||
3356 | + | ||
3357 | + def GetValue(self): | ||
3358 | + return self.obj_fiducials, self.obj_orients, self.obj_ref_id, self.obj_name |
invesalius/gui/frame.py
@@ -707,7 +707,7 @@ class MenuBar(wx.MenuBar): | @@ -707,7 +707,7 @@ class MenuBar(wx.MenuBar): | ||
707 | sub(self.OnEnableState, "Enable state project") | 707 | sub(self.OnEnableState, "Enable state project") |
708 | sub(self.OnEnableUndo, "Enable undo") | 708 | sub(self.OnEnableUndo, "Enable undo") |
709 | sub(self.OnEnableRedo, "Enable redo") | 709 | sub(self.OnEnableRedo, "Enable redo") |
710 | - sub(self.OnEnableNavigation, "Navigation Status") | 710 | + sub(self.OnEnableNavigation, "Navigation status") |
711 | 711 | ||
712 | sub(self.OnAddMask, "Add mask") | 712 | sub(self.OnAddMask, "Add mask") |
713 | sub(self.OnRemoveMasks, "Remove masks") | 713 | sub(self.OnRemoveMasks, "Remove masks") |
invesalius/gui/task_navigator.py
@@ -19,6 +19,7 @@ | @@ -19,6 +19,7 @@ | ||
19 | 19 | ||
20 | from functools import partial | 20 | from functools import partial |
21 | import sys | 21 | import sys |
22 | +import os | ||
22 | 23 | ||
23 | import numpy as np | 24 | import numpy as np |
24 | import wx | 25 | import wx |
@@ -27,15 +28,25 @@ import wx.lib.masked.numctrl | @@ -27,15 +28,25 @@ import wx.lib.masked.numctrl | ||
27 | import wx.lib.foldpanelbar as fpb | 28 | import wx.lib.foldpanelbar as fpb |
28 | from wx.lib.pubsub import pub as Publisher | 29 | from wx.lib.pubsub import pub as Publisher |
29 | import wx.lib.colourselect as csel | 30 | import wx.lib.colourselect as csel |
31 | +import wx.lib.platebtn as pbtn | ||
30 | 32 | ||
33 | +from math import cos, sin, pi | ||
34 | +from time import sleep | ||
35 | + | ||
36 | +import invesalius.data.transformations as tr | ||
31 | import invesalius.constants as const | 37 | import invesalius.constants as const |
32 | import invesalius.data.bases as db | 38 | import invesalius.data.bases as db |
33 | import invesalius.data.coordinates as dco | 39 | import invesalius.data.coordinates as dco |
34 | import invesalius.data.coregistration as dcr | 40 | import invesalius.data.coregistration as dcr |
35 | import invesalius.data.trackers as dt | 41 | import invesalius.data.trackers as dt |
36 | import invesalius.data.trigger as trig | 42 | import invesalius.data.trigger as trig |
43 | +import invesalius.data.record_coords as rec | ||
37 | import invesalius.gui.dialogs as dlg | 44 | import invesalius.gui.dialogs as dlg |
38 | 45 | ||
46 | +BTN_NEW = wx.NewId() | ||
47 | +BTN_IMPORT_LOCAL = wx.NewId() | ||
48 | + | ||
49 | + | ||
39 | class TaskPanel(wx.Panel): | 50 | class TaskPanel(wx.Panel): |
40 | def __init__(self, parent): | 51 | def __init__(self, parent): |
41 | wx.Panel.__init__(self, parent) | 52 | wx.Panel.__init__(self, parent) |
@@ -71,7 +82,6 @@ class InnerTaskPanel(wx.Panel): | @@ -71,7 +82,6 @@ class InnerTaskPanel(wx.Panel): | ||
71 | fold_panel = FoldPanel(self) | 82 | fold_panel = FoldPanel(self) |
72 | fold_panel.SetBackgroundColour(default_colour) | 83 | fold_panel.SetBackgroundColour(default_colour) |
73 | 84 | ||
74 | - | ||
75 | # Add line sizer into main sizer | 85 | # Add line sizer into main sizer |
76 | main_sizer = wx.BoxSizer(wx.VERTICAL) | 86 | main_sizer = wx.BoxSizer(wx.VERTICAL) |
77 | main_sizer.Add(txt_sizer, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 5) | 87 | main_sizer.Add(txt_sizer, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 5) |
@@ -120,7 +130,7 @@ class InnerFoldPanel(wx.Panel): | @@ -120,7 +130,7 @@ class InnerFoldPanel(wx.Panel): | ||
120 | (10, 350), 0, fpb.FPB_SINGLE_FOLD) | 130 | (10, 350), 0, fpb.FPB_SINGLE_FOLD) |
121 | else: | 131 | else: |
122 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, | 132 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, |
123 | - (10, 293), 0, fpb.FPB_SINGLE_FOLD) | 133 | + (10, 320), 0, fpb.FPB_SINGLE_FOLD) |
124 | # Fold panel style | 134 | # Fold panel style |
125 | style = fpb.CaptionBarStyle() | 135 | style = fpb.CaptionBarStyle() |
126 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) | 136 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) |
@@ -132,11 +142,19 @@ class InnerFoldPanel(wx.Panel): | @@ -132,11 +142,19 @@ class InnerFoldPanel(wx.Panel): | ||
132 | ntw = NeuronavigationPanel(item) | 142 | ntw = NeuronavigationPanel(item) |
133 | 143 | ||
134 | fold_panel.ApplyCaptionStyle(item, style) | 144 | fold_panel.ApplyCaptionStyle(item, style) |
135 | - fold_panel.AddFoldPanelWindow(item, ntw, spacing= 0, | 145 | + fold_panel.AddFoldPanelWindow(item, ntw, spacing=0, |
136 | leftSpacing=0, rightSpacing=0) | 146 | leftSpacing=0, rightSpacing=0) |
137 | fold_panel.Expand(fold_panel.GetFoldPanel(0)) | 147 | fold_panel.Expand(fold_panel.GetFoldPanel(0)) |
138 | 148 | ||
139 | - # Fold 2 - Markers panel | 149 | + # Fold 2 - Object registration panel |
150 | + item = fold_panel.AddFoldPanel(_("Object registration"), collapsed=True) | ||
151 | + otw = ObjectRegistrationPanel(item) | ||
152 | + | ||
153 | + fold_panel.ApplyCaptionStyle(item, style) | ||
154 | + fold_panel.AddFoldPanelWindow(item, otw, spacing=0, | ||
155 | + leftSpacing=0, rightSpacing=0) | ||
156 | + | ||
157 | + # Fold 3 - Markers panel | ||
140 | item = fold_panel.AddFoldPanel(_("Extra tools"), collapsed=True) | 158 | item = fold_panel.AddFoldPanel(_("Extra tools"), collapsed=True) |
141 | mtw = MarkersPanel(item) | 159 | mtw = MarkersPanel(item) |
142 | 160 | ||
@@ -147,26 +165,38 @@ class InnerFoldPanel(wx.Panel): | @@ -147,26 +165,38 @@ class InnerFoldPanel(wx.Panel): | ||
147 | 165 | ||
148 | # Check box for camera update in volume rendering during navigation | 166 | # Check box for camera update in volume rendering during navigation |
149 | tooltip = wx.ToolTip(_("Update camera in volume")) | 167 | tooltip = wx.ToolTip(_("Update camera in volume")) |
150 | - checkcamera = wx.CheckBox(self, -1, _('Volume camera')) | 168 | + checkcamera = wx.CheckBox(self, -1, _('Vol. camera')) |
151 | checkcamera.SetToolTip(tooltip) | 169 | checkcamera.SetToolTip(tooltip) |
152 | - checkcamera.SetValue(True) | ||
153 | - checkcamera.Bind(wx.EVT_CHECKBOX, partial(self.UpdateVolumeCamera, ctrl=checkcamera)) | 170 | + checkcamera.SetValue(const.CAM_MODE) |
171 | + checkcamera.Bind(wx.EVT_CHECKBOX, self.OnVolumeCamera) | ||
172 | + self.checkcamera = checkcamera | ||
154 | 173 | ||
155 | - # Check box for camera update in volume rendering during navigation | 174 | + # Check box for trigger monitoring to create markers from serial port |
156 | tooltip = wx.ToolTip(_("Enable external trigger for creating markers")) | 175 | tooltip = wx.ToolTip(_("Enable external trigger for creating markers")) |
157 | - checktrigger = wx.CheckBox(self, -1, _('External trigger')) | 176 | + checktrigger = wx.CheckBox(self, -1, _('Ext. trigger')) |
158 | checktrigger.SetToolTip(tooltip) | 177 | checktrigger.SetToolTip(tooltip) |
159 | checktrigger.SetValue(False) | 178 | checktrigger.SetValue(False) |
160 | - checktrigger.Bind(wx.EVT_CHECKBOX, partial(self.UpdateExternalTrigger, ctrl=checktrigger)) | 179 | + checktrigger.Bind(wx.EVT_CHECKBOX, partial(self.OnExternalTrigger, ctrl=checktrigger)) |
161 | self.checktrigger = checktrigger | 180 | self.checktrigger = checktrigger |
162 | 181 | ||
182 | + # Check box for object position and orientation update in volume rendering during navigation | ||
183 | + tooltip = wx.ToolTip(_("Show and track TMS coil")) | ||
184 | + checkobj = wx.CheckBox(self, -1, _('Show coil')) | ||
185 | + checkobj.SetToolTip(tooltip) | ||
186 | + checkobj.SetValue(False) | ||
187 | + checkobj.Disable() | ||
188 | + checkobj.Bind(wx.EVT_CHECKBOX, self.OnShowObject) | ||
189 | + self.checkobj = checkobj | ||
190 | + | ||
163 | if sys.platform != 'win32': | 191 | if sys.platform != 'win32': |
164 | - checkcamera.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | 192 | + self.checkcamera.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) |
165 | checktrigger.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | 193 | checktrigger.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) |
194 | + checkobj.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | ||
166 | 195 | ||
167 | line_sizer = wx.BoxSizer(wx.HORIZONTAL) | 196 | line_sizer = wx.BoxSizer(wx.HORIZONTAL) |
168 | line_sizer.Add(checkcamera, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5) | 197 | line_sizer.Add(checkcamera, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5) |
169 | - line_sizer.Add(checktrigger, 1,wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5) | 198 | + line_sizer.Add(checktrigger, 0, wx.ALIGN_CENTER) |
199 | + line_sizer.Add(checkobj, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5) | ||
170 | line_sizer.Fit(self) | 200 | line_sizer.Fit(self) |
171 | 201 | ||
172 | # Panel sizer to expand fold panel | 202 | # Panel sizer to expand fold panel |
@@ -175,27 +205,49 @@ class InnerFoldPanel(wx.Panel): | @@ -175,27 +205,49 @@ class InnerFoldPanel(wx.Panel): | ||
175 | sizer.Add(line_sizer, 1, wx.GROW | wx.EXPAND) | 205 | sizer.Add(line_sizer, 1, wx.GROW | wx.EXPAND) |
176 | sizer.Fit(self) | 206 | sizer.Fit(self) |
177 | 207 | ||
208 | + self.track_obj = False | ||
209 | + | ||
178 | self.SetSizer(sizer) | 210 | self.SetSizer(sizer) |
179 | self.Update() | 211 | self.Update() |
180 | self.SetAutoLayout(1) | 212 | self.SetAutoLayout(1) |
181 | 213 | ||
182 | def __bind_events(self): | 214 | def __bind_events(self): |
183 | - Publisher.subscribe(self.OnTrigger, 'Navigation Status') | 215 | + Publisher.subscribe(self.OnCheckStatus, 'Navigation status') |
216 | + Publisher.subscribe(self.OnShowObject, 'Update track object state') | ||
217 | + Publisher.subscribe(self.OnVolumeCamera, 'Target navigation mode') | ||
184 | 218 | ||
185 | - def OnTrigger(self, pubsub_evt): | 219 | + def OnCheckStatus(self, pubsub_evt): |
186 | status = pubsub_evt.data | 220 | status = pubsub_evt.data |
187 | if status: | 221 | if status: |
188 | self.checktrigger.Enable(False) | 222 | self.checktrigger.Enable(False) |
223 | + self.checkobj.Enable(False) | ||
189 | else: | 224 | else: |
190 | self.checktrigger.Enable(True) | 225 | self.checktrigger.Enable(True) |
226 | + if self.track_obj: | ||
227 | + self.checkobj.Enable(True) | ||
191 | 228 | ||
192 | - def UpdateExternalTrigger(self, evt, ctrl): | 229 | + def OnExternalTrigger(self, evt, ctrl): |
193 | Publisher.sendMessage('Update trigger state', ctrl.GetValue()) | 230 | Publisher.sendMessage('Update trigger state', ctrl.GetValue()) |
194 | 231 | ||
195 | - def UpdateVolumeCamera(self, evt, ctrl): | ||
196 | - Publisher.sendMessage('Update volume camera state', ctrl.GetValue()) | 232 | + def OnShowObject(self, evt): |
233 | + if hasattr(evt, 'data'): | ||
234 | + if evt.data[0]: | ||
235 | + self.checkobj.Enable(True) | ||
236 | + self.track_obj = True | ||
237 | + Publisher.sendMessage('Status target button', True) | ||
238 | + else: | ||
239 | + self.checkobj.Enable(False) | ||
240 | + self.checkobj.SetValue(False) | ||
241 | + self.track_obj = False | ||
242 | + Publisher.sendMessage('Status target button', False) | ||
197 | 243 | ||
244 | + Publisher.sendMessage('Update show object state', self.checkobj.GetValue()) | ||
198 | 245 | ||
246 | + def OnVolumeCamera(self, evt): | ||
247 | + if hasattr(evt, 'data'): | ||
248 | + if evt.data is True: | ||
249 | + self.checkcamera.SetValue(0) | ||
250 | + Publisher.sendMessage('Update volume camera state', self.checkcamera.GetValue()) | ||
199 | 251 | ||
200 | 252 | ||
201 | class NeuronavigationPanel(wx.Panel): | 253 | class NeuronavigationPanel(wx.Panel): |
@@ -215,6 +267,9 @@ class NeuronavigationPanel(wx.Panel): | @@ -215,6 +267,9 @@ class NeuronavigationPanel(wx.Panel): | ||
215 | self.trk_init = None | 267 | self.trk_init = None |
216 | self.trigger = None | 268 | self.trigger = None |
217 | self.trigger_state = False | 269 | self.trigger_state = False |
270 | + self.obj_reg = None | ||
271 | + self.obj_reg_status = False | ||
272 | + self.track_obj = False | ||
218 | 273 | ||
219 | self.tracker_id = const.DEFAULT_TRACKER | 274 | self.tracker_id = const.DEFAULT_TRACKER |
220 | self.ref_mode_id = const.DEFAULT_REF_MODE | 275 | self.ref_mode_id = const.DEFAULT_REF_MODE |
@@ -230,6 +285,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -230,6 +285,7 @@ class NeuronavigationPanel(wx.Panel): | ||
230 | choice_trck.SetToolTip(tooltip) | 285 | choice_trck.SetToolTip(tooltip) |
231 | choice_trck.SetSelection(const.DEFAULT_TRACKER) | 286 | choice_trck.SetSelection(const.DEFAULT_TRACKER) |
232 | choice_trck.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceTracker, ctrl=choice_trck)) | 287 | choice_trck.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceTracker, ctrl=choice_trck)) |
288 | + self.choice_trck = choice_trck | ||
233 | 289 | ||
234 | # ComboBox for tracker reference mode | 290 | # ComboBox for tracker reference mode |
235 | tooltip = wx.ToolTip(_("Choose the navigation reference mode")) | 291 | tooltip = wx.ToolTip(_("Choose the navigation reference mode")) |
@@ -259,7 +315,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -259,7 +315,7 @@ class NeuronavigationPanel(wx.Panel): | ||
259 | lab = btns_trk[k].values()[0] | 315 | lab = btns_trk[k].values()[0] |
260 | self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(45, 23)) | 316 | self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(45, 23)) |
261 | self.btns_coord[n].SetToolTip(wx.ToolTip(tips_trk[n-3])) | 317 | self.btns_coord[n].SetToolTip(wx.ToolTip(tips_trk[n-3])) |
262 | - # Excepetion for event of button that set image coordinates | 318 | + # Exception for event of button that set image coordinates |
263 | if n == 6: | 319 | if n == 6: |
264 | self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnSetImageCoordinates) | 320 | self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnSetImageCoordinates) |
265 | else: | 321 | else: |
@@ -275,12 +331,13 @@ class NeuronavigationPanel(wx.Panel): | @@ -275,12 +331,13 @@ class NeuronavigationPanel(wx.Panel): | ||
275 | txtctrl_fre.SetBackgroundColour('WHITE') | 331 | txtctrl_fre.SetBackgroundColour('WHITE') |
276 | txtctrl_fre.SetEditable(0) | 332 | txtctrl_fre.SetEditable(0) |
277 | txtctrl_fre.SetToolTip(tooltip) | 333 | txtctrl_fre.SetToolTip(tooltip) |
334 | + self.txtctrl_fre = txtctrl_fre | ||
278 | 335 | ||
279 | # Toggle button for neuronavigation | 336 | # Toggle button for neuronavigation |
280 | tooltip = wx.ToolTip(_("Start navigation")) | 337 | tooltip = wx.ToolTip(_("Start navigation")) |
281 | btn_nav = wx.ToggleButton(self, -1, _("Navigate"), size=wx.Size(80, -1)) | 338 | btn_nav = wx.ToggleButton(self, -1, _("Navigate"), size=wx.Size(80, -1)) |
282 | btn_nav.SetToolTip(tooltip) | 339 | btn_nav.SetToolTip(tooltip) |
283 | - btn_nav.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnNavigate, btn=(btn_nav, choice_trck, choice_ref, txtctrl_fre))) | 340 | + btn_nav.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnNavigate, btn=(btn_nav, choice_trck, choice_ref))) |
284 | 341 | ||
285 | # Image and tracker coordinates number controls | 342 | # Image and tracker coordinates number controls |
286 | for m in range(0, 7): | 343 | for m in range(0, 7): |
@@ -288,7 +345,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -288,7 +345,7 @@ class NeuronavigationPanel(wx.Panel): | ||
288 | self.numctrls_coord[m].append( | 345 | self.numctrls_coord[m].append( |
289 | wx.lib.masked.numctrl.NumCtrl(parent=self, integerWidth=4, fractionWidth=1)) | 346 | wx.lib.masked.numctrl.NumCtrl(parent=self, integerWidth=4, fractionWidth=1)) |
290 | 347 | ||
291 | - # Sizers to group all GUI objects | 348 | + # Sizer to group all GUI objects |
292 | choice_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5) | 349 | choice_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5) |
293 | choice_sizer.AddMany([(choice_trck, wx.LEFT), | 350 | choice_sizer.AddMany([(choice_trck, wx.LEFT), |
294 | (choice_ref, wx.RIGHT)]) | 351 | (choice_ref, wx.RIGHT)]) |
@@ -326,8 +383,11 @@ class NeuronavigationPanel(wx.Panel): | @@ -326,8 +383,11 @@ class NeuronavigationPanel(wx.Panel): | ||
326 | def __bind_events(self): | 383 | def __bind_events(self): |
327 | Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') | 384 | Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') |
328 | Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') | 385 | Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') |
386 | + Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state') | ||
329 | Publisher.subscribe(self.UpdateImageCoordinates, 'Set ball reference position') | 387 | Publisher.subscribe(self.UpdateImageCoordinates, 'Set ball reference position') |
330 | Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker') | 388 | Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker') |
389 | + Publisher.subscribe(self.UpdateObjectRegistration, 'Update object registration') | ||
390 | + Publisher.subscribe(self.OnCloseProject, 'Close project data') | ||
331 | 391 | ||
332 | def LoadImageFiducials(self, pubsub_evt): | 392 | def LoadImageFiducials(self, pubsub_evt): |
333 | marker_id = pubsub_evt.data[0] | 393 | marker_id = pubsub_evt.data[0] |
@@ -347,26 +407,40 @@ class NeuronavigationPanel(wx.Panel): | @@ -347,26 +407,40 @@ class NeuronavigationPanel(wx.Panel): | ||
347 | for m in [0, 1, 2, 6]: | 407 | for m in [0, 1, 2, 6]: |
348 | if m == 6 and self.btns_coord[m].IsEnabled(): | 408 | if m == 6 and self.btns_coord[m].IsEnabled(): |
349 | for n in [0, 1, 2]: | 409 | for n in [0, 1, 2]: |
350 | - self.numctrls_coord[m][n].SetValue(self.current_coord[n]) | 410 | + self.numctrls_coord[m][n].SetValue(float(self.current_coord[n])) |
351 | elif m != 6 and not self.btns_coord[m].GetValue(): | 411 | elif m != 6 and not self.btns_coord[m].GetValue(): |
352 | # btn_state = self.btns_coord[m].GetValue() | 412 | # btn_state = self.btns_coord[m].GetValue() |
353 | # if not btn_state: | 413 | # if not btn_state: |
354 | for n in [0, 1, 2]: | 414 | for n in [0, 1, 2]: |
355 | - self.numctrls_coord[m][n].SetValue(self.current_coord[n]) | 415 | + self.numctrls_coord[m][n].SetValue(float(self.current_coord[n])) |
416 | + | ||
417 | + def UpdateObjectRegistration(self, pubsub_evt): | ||
418 | + if pubsub_evt.data: | ||
419 | + self.obj_reg = pubsub_evt.data | ||
420 | + self.obj_reg_status = True | ||
421 | + else: | ||
422 | + self.obj_reg = None | ||
423 | + self.obj_reg_status = False | ||
356 | 424 | ||
357 | - def UpdateTriggerState (self, pubsub_evt): | 425 | + def UpdateTrackObjectState(self, pubsub_evt): |
426 | + if pubsub_evt.data[0]: | ||
427 | + self.track_obj = pubsub_evt.data[0] | ||
428 | + else: | ||
429 | + self.track_obj = False | ||
430 | + | ||
431 | + def UpdateTriggerState(self, pubsub_evt): | ||
358 | self.trigger_state = pubsub_evt.data | 432 | self.trigger_state = pubsub_evt.data |
359 | 433 | ||
360 | def OnDisconnectTracker(self, pubsub_evt): | 434 | def OnDisconnectTracker(self, pubsub_evt): |
361 | if self.tracker_id: | 435 | if self.tracker_id: |
362 | - dt.TrackerConnection(self.tracker_id, 'disconnect') | 436 | + dt.TrackerConnection(self.tracker_id, self.trk_init[0], 'disconnect') |
363 | 437 | ||
364 | def OnChoiceTracker(self, evt, ctrl): | 438 | def OnChoiceTracker(self, evt, ctrl): |
365 | Publisher.sendMessage('Update status text in GUI', _("Configuring tracker ...")) | 439 | Publisher.sendMessage('Update status text in GUI', _("Configuring tracker ...")) |
366 | - if evt: | 440 | + if hasattr(evt, 'GetSelection'): |
367 | choice = evt.GetSelection() | 441 | choice = evt.GetSelection() |
368 | else: | 442 | else: |
369 | - choice = self.tracker_id | 443 | + choice = 6 |
370 | 444 | ||
371 | if self.trk_init: | 445 | if self.trk_init: |
372 | trck = self.trk_init[0] | 446 | trck = self.trk_init[0] |
@@ -377,26 +451,35 @@ class NeuronavigationPanel(wx.Panel): | @@ -377,26 +451,35 @@ class NeuronavigationPanel(wx.Panel): | ||
377 | # has been initialized before | 451 | # has been initialized before |
378 | if trck and choice != 6: | 452 | if trck and choice != 6: |
379 | self.ResetTrackerFiducials() | 453 | self.ResetTrackerFiducials() |
380 | - self.trk_init = dt.TrackerConnection(self.tracker_id, 'disconnect') | 454 | + Publisher.sendMessage('Update status text in GUI', _("Disconnecting tracker...")) |
455 | + Publisher.sendMessage('Remove sensors ID') | ||
456 | + self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect') | ||
381 | self.tracker_id = choice | 457 | self.tracker_id = choice |
382 | - if not self.trk_init[0]: | ||
383 | - self.trk_init = dt.TrackerConnection(self.tracker_id, 'connect') | 458 | + if not self.trk_init[0] and choice: |
459 | + Publisher.sendMessage('Update status text in GUI', _("Tracker disconnected successfully")) | ||
460 | + self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') | ||
384 | if not self.trk_init[0]: | 461 | if not self.trk_init[0]: |
385 | dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) | 462 | dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) |
386 | ctrl.SetSelection(0) | 463 | ctrl.SetSelection(0) |
387 | print "Tracker not connected!" | 464 | print "Tracker not connected!" |
388 | else: | 465 | else: |
466 | + Publisher.sendMessage('Update status text in GUI', _("Ready")) | ||
389 | ctrl.SetSelection(self.tracker_id) | 467 | ctrl.SetSelection(self.tracker_id) |
390 | print "Tracker connected!" | 468 | print "Tracker connected!" |
391 | elif choice == 6: | 469 | elif choice == 6: |
392 | if trck: | 470 | if trck: |
393 | - self.trk_init = dt.TrackerConnection(self.tracker_id, 'disconnect') | 471 | + Publisher.sendMessage('Update status text in GUI', _("Disconnecting tracker ...")) |
472 | + Publisher.sendMessage('Remove sensors ID') | ||
473 | + self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect') | ||
394 | if not self.trk_init[0]: | 474 | if not self.trk_init[0]: |
395 | - dlg.NavigationTrackerWarning(self.tracker_id, 'disconnect') | 475 | + if evt is not False: |
476 | + dlg.NavigationTrackerWarning(self.tracker_id, 'disconnect') | ||
396 | self.tracker_id = 0 | 477 | self.tracker_id = 0 |
397 | ctrl.SetSelection(self.tracker_id) | 478 | ctrl.SetSelection(self.tracker_id) |
479 | + Publisher.sendMessage('Update status text in GUI', _("Tracker disconnected")) | ||
398 | print "Tracker disconnected!" | 480 | print "Tracker disconnected!" |
399 | else: | 481 | else: |
482 | + Publisher.sendMessage('Update status text in GUI', _("Tracker still connected")) | ||
400 | print "Tracker still connected!" | 483 | print "Tracker still connected!" |
401 | else: | 484 | else: |
402 | ctrl.SetSelection(self.tracker_id) | 485 | ctrl.SetSelection(self.tracker_id) |
@@ -405,34 +488,38 @@ class NeuronavigationPanel(wx.Panel): | @@ -405,34 +488,38 @@ class NeuronavigationPanel(wx.Panel): | ||
405 | # If trk_init is None try to connect. If doesn't succeed show dialog. | 488 | # If trk_init is None try to connect. If doesn't succeed show dialog. |
406 | if choice: | 489 | if choice: |
407 | self.tracker_id = choice | 490 | self.tracker_id = choice |
408 | - self.trk_init = dt.TrackerConnection(self.tracker_id, 'connect') | 491 | + self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') |
409 | if not self.trk_init[0]: | 492 | if not self.trk_init[0]: |
410 | dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) | 493 | dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) |
411 | self.tracker_id = 0 | 494 | self.tracker_id = 0 |
412 | ctrl.SetSelection(self.tracker_id) | 495 | ctrl.SetSelection(self.tracker_id) |
413 | - Publisher.sendMessage('Update status text in GUI', _("Ready")) | 496 | + else: |
497 | + Publisher.sendMessage('Update status text in GUI', _("Ready")) | ||
498 | + | ||
499 | + Publisher.sendMessage('Update tracker initializer', (self.tracker_id, self.trk_init, self.ref_mode_id)) | ||
414 | 500 | ||
415 | def OnChoiceRefMode(self, evt, ctrl): | 501 | def OnChoiceRefMode(self, evt, ctrl): |
416 | - # When ref mode is changed the tracker coords are set to zero | 502 | + # When ref mode is changed the tracker coordinates are set to zero |
417 | self.ref_mode_id = evt.GetSelection() | 503 | self.ref_mode_id = evt.GetSelection() |
418 | self.ResetTrackerFiducials() | 504 | self.ResetTrackerFiducials() |
419 | # Some trackers do not accept restarting within this time window | 505 | # Some trackers do not accept restarting within this time window |
420 | # TODO: Improve the restarting of trackers after changing reference mode | 506 | # TODO: Improve the restarting of trackers after changing reference mode |
421 | # self.OnChoiceTracker(None, ctrl) | 507 | # self.OnChoiceTracker(None, ctrl) |
508 | + Publisher.sendMessage('Update tracker initializer', (self.tracker_id, self.trk_init, self.ref_mode_id)) | ||
422 | print "Reference mode changed!" | 509 | print "Reference mode changed!" |
423 | 510 | ||
424 | def OnSetImageCoordinates(self, evt): | 511 | def OnSetImageCoordinates(self, evt): |
425 | # FIXME: Cross does not update in last clicked slice, only on the other two | 512 | # FIXME: Cross does not update in last clicked slice, only on the other two |
426 | btn_id = const.BTNS_TRK[evt.GetId()].keys()[0] | 513 | btn_id = const.BTNS_TRK[evt.GetId()].keys()[0] |
427 | 514 | ||
428 | - wx, wy, wz = self.numctrls_coord[btn_id][0].GetValue(), \ | ||
429 | - self.numctrls_coord[btn_id][1].GetValue(), \ | 515 | + ux, uy, uz = self.numctrls_coord[btn_id][0].GetValue(),\ |
516 | + self.numctrls_coord[btn_id][1].GetValue(),\ | ||
430 | self.numctrls_coord[btn_id][2].GetValue() | 517 | self.numctrls_coord[btn_id][2].GetValue() |
431 | 518 | ||
432 | - Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) | ||
433 | - Publisher.sendMessage('Set camera in volume', (wx, wy, wz)) | ||
434 | - Publisher.sendMessage('Co-registered points', (wx, wy, wz)) | ||
435 | - Publisher.sendMessage('Update cross position', (wx, wy, wz)) | 519 | + Publisher.sendMessage('Set ball reference position', (ux, uy, uz)) |
520 | + # Publisher.sendMessage('Set camera in volume', (ux, uy, uz)) | ||
521 | + Publisher.sendMessage('Co-registered points', (ux, uy, uz, 0., 0., 0.)) | ||
522 | + Publisher.sendMessage('Update cross position', (ux, uy, uz)) | ||
436 | 523 | ||
437 | def OnImageFiducials(self, evt): | 524 | def OnImageFiducials(self, evt): |
438 | btn_id = const.BTNS_IMG_MKS[evt.GetId()].keys()[0] | 525 | btn_id = const.BTNS_IMG_MKS[evt.GetId()].keys()[0] |
@@ -441,13 +528,13 @@ class NeuronavigationPanel(wx.Panel): | @@ -441,13 +528,13 @@ class NeuronavigationPanel(wx.Panel): | ||
441 | if self.btns_coord[btn_id].GetValue(): | 528 | if self.btns_coord[btn_id].GetValue(): |
442 | coord = self.numctrls_coord[btn_id][0].GetValue(),\ | 529 | coord = self.numctrls_coord[btn_id][0].GetValue(),\ |
443 | self.numctrls_coord[btn_id][1].GetValue(),\ | 530 | self.numctrls_coord[btn_id][1].GetValue(),\ |
444 | - self.numctrls_coord[btn_id][2].GetValue() | 531 | + self.numctrls_coord[btn_id][2].GetValue(), 0, 0, 0 |
445 | 532 | ||
446 | self.fiducials[btn_id, :] = coord[0:3] | 533 | self.fiducials[btn_id, :] = coord[0:3] |
447 | Publisher.sendMessage('Create marker', (coord, marker_id)) | 534 | Publisher.sendMessage('Create marker', (coord, marker_id)) |
448 | else: | 535 | else: |
449 | for n in [0, 1, 2]: | 536 | for n in [0, 1, 2]: |
450 | - self.numctrls_coord[btn_id][n].SetValue(self.current_coord[n]) | 537 | + self.numctrls_coord[btn_id][n].SetValue(float(self.current_coord[n])) |
451 | 538 | ||
452 | self.fiducials[btn_id, :] = np.nan | 539 | self.fiducials[btn_id, :] = np.nan |
453 | Publisher.sendMessage('Delete fiducial marker', marker_id) | 540 | Publisher.sendMessage('Delete fiducial marker', marker_id) |
@@ -457,7 +544,13 @@ class NeuronavigationPanel(wx.Panel): | @@ -457,7 +544,13 @@ class NeuronavigationPanel(wx.Panel): | ||
457 | coord = None | 544 | coord = None |
458 | 545 | ||
459 | if self.trk_init and self.tracker_id: | 546 | if self.trk_init and self.tracker_id: |
460 | - coord = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) | 547 | + coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) |
548 | + if self.ref_mode_id: | ||
549 | + coord = dco.dynamic_reference_m(coord_raw[0, :], coord_raw[1, :]) | ||
550 | + else: | ||
551 | + coord = coord_raw[0, :] | ||
552 | + coord[2] = -coord[2] | ||
553 | + | ||
461 | else: | 554 | else: |
462 | dlg.NavigationTrackerWarning(0, 'choose') | 555 | dlg.NavigationTrackerWarning(0, 'choose') |
463 | 556 | ||
@@ -471,7 +564,6 @@ class NeuronavigationPanel(wx.Panel): | @@ -471,7 +564,6 @@ class NeuronavigationPanel(wx.Panel): | ||
471 | btn_nav = btn[0] | 564 | btn_nav = btn[0] |
472 | choice_trck = btn[1] | 565 | choice_trck = btn[1] |
473 | choice_ref = btn[2] | 566 | choice_ref = btn[2] |
474 | - txtctrl_fre = btn[3] | ||
475 | 567 | ||
476 | nav_id = btn_nav.GetValue() | 568 | nav_id = btn_nav.GetValue() |
477 | if nav_id: | 569 | if nav_id: |
@@ -479,6 +571,9 @@ class NeuronavigationPanel(wx.Panel): | @@ -479,6 +571,9 @@ class NeuronavigationPanel(wx.Panel): | ||
479 | dlg.InvalidFiducials() | 571 | dlg.InvalidFiducials() |
480 | btn_nav.SetValue(False) | 572 | btn_nav.SetValue(False) |
481 | 573 | ||
574 | + elif not self.trk_init[0]: | ||
575 | + dlg.NavigationTrackerWarning(0, 'choose') | ||
576 | + | ||
482 | else: | 577 | else: |
483 | tooltip = wx.ToolTip(_("Stop neuronavigation")) | 578 | tooltip = wx.ToolTip(_("Stop neuronavigation")) |
484 | btn_nav.SetToolTip(tooltip) | 579 | btn_nav.SetToolTip(tooltip) |
@@ -489,28 +584,75 @@ class NeuronavigationPanel(wx.Panel): | @@ -489,28 +584,75 @@ class NeuronavigationPanel(wx.Panel): | ||
489 | for btn_c in self.btns_coord: | 584 | for btn_c in self.btns_coord: |
490 | btn_c.Enable(False) | 585 | btn_c.Enable(False) |
491 | 586 | ||
492 | - m, q1, minv = db.base_creation(self.fiducials[0:3, :]) | ||
493 | - n, q2, ninv = db.base_creation(self.fiducials[3::, :]) | 587 | + # fids_head_img = np.zeros([3, 3]) |
588 | + # for ic in range(0, 3): | ||
589 | + # fids_head_img[ic, :] = np.asarray(db.flip_x_m(self.fiducials[ic, :])) | ||
590 | + # | ||
591 | + # m_head_aux, q_head_aux, m_inv_head_aux = db.base_creation(fids_head_img) | ||
592 | + # m_head = np.asmatrix(np.identity(4)) | ||
593 | + # m_head[:3, :3] = m_head_aux[:3, :3] | ||
594 | + | ||
595 | + m, q1, minv = db.base_creation_old(self.fiducials[:3, :]) | ||
596 | + n, q2, ninv = db.base_creation_old(self.fiducials[3:, :]) | ||
597 | + | ||
598 | + m_change = tr.affine_matrix_from_points(self.fiducials[3:, :].T, self.fiducials[:3, :].T, | ||
599 | + shear=False, scale=False) | ||
600 | + | ||
601 | + # coreg_data = [m_change, m_head] | ||
494 | 602 | ||
495 | tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id | 603 | tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id |
496 | # FIXME: FRE is taking long to calculate so it updates on GUI delayed to navigation - I think its fixed | 604 | # FIXME: FRE is taking long to calculate so it updates on GUI delayed to navigation - I think its fixed |
497 | # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok | 605 | # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok |
498 | fre = db.calculate_fre(self.fiducials, minv, n, q1, q2) | 606 | fre = db.calculate_fre(self.fiducials, minv, n, q1, q2) |
499 | 607 | ||
500 | - txtctrl_fre.SetValue(str(round(fre, 2))) | 608 | + self.txtctrl_fre.SetValue(str(round(fre, 2))) |
501 | if fre <= 3: | 609 | if fre <= 3: |
502 | - txtctrl_fre.SetBackgroundColour('GREEN') | 610 | + self.txtctrl_fre.SetBackgroundColour('GREEN') |
503 | else: | 611 | else: |
504 | - txtctrl_fre.SetBackgroundColour('RED') | 612 | + self.txtctrl_fre.SetBackgroundColour('RED') |
505 | 613 | ||
506 | if self.trigger_state: | 614 | if self.trigger_state: |
507 | self.trigger = trig.Trigger(nav_id) | 615 | self.trigger = trig.Trigger(nav_id) |
508 | 616 | ||
509 | - Publisher.sendMessage("Navigation Status", True) | 617 | + Publisher.sendMessage("Navigation status", True) |
510 | Publisher.sendMessage("Toggle Cross", const.SLICE_STATE_CROSS) | 618 | Publisher.sendMessage("Toggle Cross", const.SLICE_STATE_CROSS) |
511 | Publisher.sendMessage("Hide current mask") | 619 | Publisher.sendMessage("Hide current mask") |
512 | 620 | ||
513 | - self.correg = dcr.Coregistration((minv, n, q1, q2), nav_id, tracker_mode) | 621 | + if self.track_obj: |
622 | + if self.obj_reg_status: | ||
623 | + # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix | ||
624 | + obj_fiducials, obj_orients, obj_ref_mode, obj_name = self.obj_reg | ||
625 | + | ||
626 | + if self.trk_init and self.tracker_id: | ||
627 | + | ||
628 | + coreg_data = [m_change, obj_ref_mode] | ||
629 | + | ||
630 | + if self.ref_mode_id: | ||
631 | + coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) | ||
632 | + obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | ||
633 | + coreg_data.extend(obj_data) | ||
634 | + | ||
635 | + self.correg = dcr.CoregistrationObjectDynamic(coreg_data, nav_id, tracker_mode) | ||
636 | + else: | ||
637 | + coord_raw = np.array([None]) | ||
638 | + obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | ||
639 | + coreg_data.extend(obj_data) | ||
640 | + | ||
641 | + self.correg = dcr.CoregistrationObjectStatic(coreg_data, nav_id, tracker_mode) | ||
642 | + | ||
643 | + else: | ||
644 | + dlg.NavigationTrackerWarning(0, 'choose') | ||
645 | + | ||
646 | + else: | ||
647 | + dlg.InvalidObjectRegistration() | ||
648 | + | ||
649 | + else: | ||
650 | + coreg_data = [m_change, 0] | ||
651 | + if self.ref_mode_id: | ||
652 | + # self.correg = dcr.CoregistrationDynamic_old(bases_coreg, nav_id, tracker_mode) | ||
653 | + self.correg = dcr.CoregistrationDynamic(coreg_data, nav_id, tracker_mode) | ||
654 | + else: | ||
655 | + self.correg = dcr.CoregistrationStatic(coreg_data, nav_id, tracker_mode) | ||
514 | 656 | ||
515 | else: | 657 | else: |
516 | tooltip = wx.ToolTip(_("Start neuronavigation")) | 658 | tooltip = wx.ToolTip(_("Start neuronavigation")) |
@@ -527,13 +669,272 @@ class NeuronavigationPanel(wx.Panel): | @@ -527,13 +669,272 @@ class NeuronavigationPanel(wx.Panel): | ||
527 | 669 | ||
528 | self.correg.stop() | 670 | self.correg.stop() |
529 | 671 | ||
530 | - Publisher.sendMessage("Navigation Status", False) | 672 | + Publisher.sendMessage("Navigation status", False) |
673 | + | ||
674 | + def ResetImageFiducials(self): | ||
675 | + for m in range(0, 3): | ||
676 | + self.btns_coord[m].SetValue(False) | ||
677 | + self.fiducials[m, :] = [np.nan, np.nan, np.nan] | ||
678 | + for n in range(0, 3): | ||
679 | + self.numctrls_coord[m][n].SetValue(0.0) | ||
680 | + | ||
681 | + for n in range(0, 3): | ||
682 | + self.numctrls_coord[6][n].SetValue(0.0) | ||
531 | 683 | ||
532 | def ResetTrackerFiducials(self): | 684 | def ResetTrackerFiducials(self): |
533 | for m in range(3, 6): | 685 | for m in range(3, 6): |
686 | + self.fiducials[m, :] = [np.nan, np.nan, np.nan] | ||
534 | for n in range(0, 3): | 687 | for n in range(0, 3): |
535 | self.numctrls_coord[m][n].SetValue(0.0) | 688 | self.numctrls_coord[m][n].SetValue(0.0) |
536 | 689 | ||
690 | + self.txtctrl_fre.SetValue('') | ||
691 | + self.txtctrl_fre.SetBackgroundColour('WHITE') | ||
692 | + | ||
693 | + def OnCloseProject(self, pubsub_evt): | ||
694 | + self.ResetTrackerFiducials() | ||
695 | + self.ResetImageFiducials() | ||
696 | + self.OnChoiceTracker(False, self.choice_trck) | ||
697 | + Publisher.sendMessage('Update object registration', False) | ||
698 | + Publisher.sendMessage('Update track object state', (False, False)) | ||
699 | + Publisher.sendMessage('Delete all markers', 'close') | ||
700 | + # TODO: Reset camera initial focus | ||
701 | + Publisher.sendMessage('Reset cam clipping range') | ||
702 | + | ||
703 | + | ||
704 | +class ObjectRegistrationPanel(wx.Panel): | ||
705 | + def __init__(self, parent): | ||
706 | + wx.Panel.__init__(self, parent) | ||
707 | + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) | ||
708 | + self.SetBackgroundColour(default_colour) | ||
709 | + | ||
710 | + self.coil_list = const.COIL | ||
711 | + | ||
712 | + self.nav_prop = None | ||
713 | + self.obj_fiducials = None | ||
714 | + self.obj_orients = None | ||
715 | + self.obj_ref_mode = None | ||
716 | + self.obj_name = None | ||
717 | + self.timestamp = const.TIMESTAMP | ||
718 | + | ||
719 | + self.SetAutoLayout(1) | ||
720 | + self.__bind_events() | ||
721 | + | ||
722 | + # Button for creating new coil | ||
723 | + tooltip = wx.ToolTip(_("Create new coil")) | ||
724 | + btn_new = wx.Button(self, -1, _("New"), size=wx.Size(65, 23)) | ||
725 | + btn_new.SetToolTip(tooltip) | ||
726 | + btn_new.Enable(1) | ||
727 | + btn_new.Bind(wx.EVT_BUTTON, self.OnLinkCreate) | ||
728 | + self.btn_new = btn_new | ||
729 | + | ||
730 | + # Button for import config coil file | ||
731 | + tooltip = wx.ToolTip(_("Load coil configuration file")) | ||
732 | + btn_load = wx.Button(self, -1, _("Load"), size=wx.Size(65, 23)) | ||
733 | + btn_load.SetToolTip(tooltip) | ||
734 | + btn_load.Enable(1) | ||
735 | + btn_load.Bind(wx.EVT_BUTTON, self.OnLinkLoad) | ||
736 | + self.btn_load = btn_load | ||
737 | + | ||
738 | + # Save button for object registration | ||
739 | + tooltip = wx.ToolTip(_(u"Save object registration file")) | ||
740 | + btn_save = wx.Button(self, -1, _(u"Save"), size=wx.Size(65, 23)) | ||
741 | + btn_save.SetToolTip(tooltip) | ||
742 | + btn_save.Enable(1) | ||
743 | + btn_save.Bind(wx.EVT_BUTTON, self.ShowSaveObjectDialog) | ||
744 | + self.btn_save = btn_save | ||
745 | + | ||
746 | + # Create a horizontal sizer to represent button save | ||
747 | + line_save = wx.BoxSizer(wx.HORIZONTAL) | ||
748 | + line_save.Add(btn_new, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4) | ||
749 | + line_save.Add(btn_load, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4) | ||
750 | + line_save.Add(btn_save, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4) | ||
751 | + | ||
752 | + # Change angles threshold | ||
753 | + text_angles = wx.StaticText(self, -1, _("Angle threshold [degrees]:")) | ||
754 | + spin_size_angles = wx.SpinCtrl(self, -1, "", size=wx.Size(50, 23)) | ||
755 | + spin_size_angles.SetRange(1, 99) | ||
756 | + spin_size_angles.SetValue(const.COIL_ANGLES_THRESHOLD) | ||
757 | + spin_size_angles.Bind(wx.EVT_TEXT, partial(self.OnSelectAngleThreshold, ctrl=spin_size_angles)) | ||
758 | + spin_size_angles.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectAngleThreshold, ctrl=spin_size_angles)) | ||
759 | + | ||
760 | + # Change dist threshold | ||
761 | + text_dist = wx.StaticText(self, -1, _("Distance threshold [mm]:")) | ||
762 | + spin_size_dist = wx.SpinCtrl(self, -1, "", size=wx.Size(50, 23)) | ||
763 | + spin_size_dist.SetRange(1, 99) | ||
764 | + spin_size_dist.SetValue(const.COIL_ANGLES_THRESHOLD) | ||
765 | + spin_size_dist.Bind(wx.EVT_TEXT, partial(self.OnSelectDistThreshold, ctrl=spin_size_dist)) | ||
766 | + spin_size_dist.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectDistThreshold, ctrl=spin_size_dist)) | ||
767 | + | ||
768 | + # Change timestamp interval | ||
769 | + text_timestamp = wx.StaticText(self, -1, _("Timestamp interval [s]:")) | ||
770 | + spin_timestamp_dist = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23), inc = 0.1) | ||
771 | + spin_timestamp_dist.SetRange(0.5, 60.0) | ||
772 | + spin_timestamp_dist.SetValue(self.timestamp) | ||
773 | + spin_timestamp_dist.Bind(wx.EVT_TEXT, partial(self.OnSelectTimestamp, ctrl=spin_timestamp_dist)) | ||
774 | + spin_timestamp_dist.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectTimestamp, ctrl=spin_timestamp_dist)) | ||
775 | + self.spin_timestamp_dist = spin_timestamp_dist | ||
776 | + | ||
777 | + # Create a horizontal sizer to threshold configs | ||
778 | + line_angle_threshold = wx.BoxSizer(wx.HORIZONTAL) | ||
779 | + line_angle_threshold.AddMany([(text_angles, 1, wx.EXPAND | wx.GROW | wx.TOP| wx.RIGHT | wx.LEFT, 5), | ||
780 | + (spin_size_angles, 0, wx.ALL | wx.EXPAND | wx.GROW, 5)]) | ||
781 | + | ||
782 | + line_dist_threshold = wx.BoxSizer(wx.HORIZONTAL) | ||
783 | + line_dist_threshold.AddMany([(text_dist, 1, wx.EXPAND | wx.GROW | wx.TOP| wx.RIGHT | wx.LEFT, 5), | ||
784 | + (spin_size_dist, 0, wx.ALL | wx.EXPAND | wx.GROW, 5)]) | ||
785 | + | ||
786 | + line_timestamp = wx.BoxSizer(wx.HORIZONTAL) | ||
787 | + line_timestamp.AddMany([(text_timestamp, 1, wx.EXPAND | wx.GROW | wx.TOP| wx.RIGHT | wx.LEFT, 5), | ||
788 | + (spin_timestamp_dist, 0, wx.ALL | wx.EXPAND | wx.GROW, 5)]) | ||
789 | + | ||
790 | + # Check box for trigger monitoring to create markers from serial port | ||
791 | + checkrecordcoords = wx.CheckBox(self, -1, _('Record coordinates')) | ||
792 | + checkrecordcoords.SetValue(False) | ||
793 | + checkrecordcoords.Enable(0) | ||
794 | + checkrecordcoords.Bind(wx.EVT_CHECKBOX, partial(self.OnRecordCoords, ctrl=checkrecordcoords)) | ||
795 | + self.checkrecordcoords = checkrecordcoords | ||
796 | + | ||
797 | + # Check box to track object or simply the stylus | ||
798 | + checktrack = wx.CheckBox(self, -1, _('Track object')) | ||
799 | + checktrack.SetValue(False) | ||
800 | + checktrack.Enable(0) | ||
801 | + checktrack.Bind(wx.EVT_CHECKBOX, partial(self.OnTrackObject, ctrl=checktrack)) | ||
802 | + self.checktrack = checktrack | ||
803 | + | ||
804 | + line_checks = wx.BoxSizer(wx.HORIZONTAL) | ||
805 | + line_checks.Add(checkrecordcoords, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5) | ||
806 | + line_checks.Add(checktrack, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5) | ||
807 | + | ||
808 | + # Add line sizers into main sizer | ||
809 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
810 | + main_sizer.Add(line_save, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, 5) | ||
811 | + main_sizer.Add(line_angle_threshold, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5) | ||
812 | + main_sizer.Add(line_dist_threshold, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5) | ||
813 | + main_sizer.Add(line_timestamp, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5) | ||
814 | + main_sizer.Add(line_checks, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 10) | ||
815 | + main_sizer.Fit(self) | ||
816 | + | ||
817 | + self.SetSizer(main_sizer) | ||
818 | + self.Update() | ||
819 | + | ||
820 | + def __bind_events(self): | ||
821 | + Publisher.subscribe(self.UpdateTrackerInit, 'Update tracker initializer') | ||
822 | + Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') | ||
823 | + Publisher.subscribe(self.OnCloseProject, 'Close project data') | ||
824 | + | ||
825 | + def UpdateTrackerInit(self, pubsub_evt): | ||
826 | + self.nav_prop = pubsub_evt.data | ||
827 | + | ||
828 | + def UpdateNavigationStatus(self, pubsub_evt): | ||
829 | + nav_status = pubsub_evt.data | ||
830 | + if nav_status: | ||
831 | + self.checkrecordcoords.Enable(1) | ||
832 | + self.checktrack.Enable(0) | ||
833 | + self.btn_save.Enable(0) | ||
834 | + self.btn_new.Enable(0) | ||
835 | + self.btn_load.Enable(0) | ||
836 | + else: | ||
837 | + self.OnRecordCoords(nav_status, self.checkrecordcoords) | ||
838 | + self.checkrecordcoords.SetValue(False) | ||
839 | + self.checkrecordcoords.Enable(0) | ||
840 | + self.btn_save.Enable(1) | ||
841 | + self.btn_new.Enable(1) | ||
842 | + self.btn_load.Enable(1) | ||
843 | + if self.obj_fiducials is not None: | ||
844 | + self.checktrack.Enable(1) | ||
845 | + Publisher.sendMessage('Enable target button', True) | ||
846 | + | ||
847 | + def OnSelectAngleThreshold(self, evt, ctrl): | ||
848 | + Publisher.sendMessage('Update angle threshold', ctrl.GetValue()) | ||
849 | + | ||
850 | + def OnSelectDistThreshold(self, evt, ctrl): | ||
851 | + Publisher.sendMessage('Update dist threshold', ctrl.GetValue()) | ||
852 | + | ||
853 | + def OnSelectTimestamp(self, evt, ctrl): | ||
854 | + self.timestamp = ctrl.GetValue() | ||
855 | + | ||
856 | + def OnRecordCoords(self, evt, ctrl): | ||
857 | + if ctrl.GetValue() and evt: | ||
858 | + self.spin_timestamp_dist.Enable(0) | ||
859 | + self.thr_record = rec.Record(ctrl.GetValue(), self.timestamp) | ||
860 | + elif (not ctrl.GetValue() and evt) or (ctrl.GetValue() and not evt) : | ||
861 | + self.spin_timestamp_dist.Enable(1) | ||
862 | + self.thr_record.stop() | ||
863 | + elif not ctrl.GetValue() and not evt: | ||
864 | + None | ||
865 | + | ||
866 | + def OnTrackObject(self, evt, ctrl): | ||
867 | + Publisher.sendMessage('Update track object state', (evt.GetSelection(), self.obj_name)) | ||
868 | + | ||
869 | + def OnComboCoil(self, evt): | ||
870 | + # coil_name = evt.GetString() | ||
871 | + coil_index = evt.GetSelection() | ||
872 | + Publisher.sendMessage('Change selected coil', self.coil_list[coil_index][1]) | ||
873 | + | ||
874 | + def OnLinkCreate(self, event=None): | ||
875 | + | ||
876 | + if self.nav_prop: | ||
877 | + dialog = dlg.ObjectCalibrationDialog(self.nav_prop) | ||
878 | + try: | ||
879 | + if dialog.ShowModal() == wx.ID_OK: | ||
880 | + self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name = dialog.GetValue() | ||
881 | + if np.isfinite(self.obj_fiducials).all() and np.isfinite(self.obj_orients).all(): | ||
882 | + self.checktrack.Enable(1) | ||
883 | + Publisher.sendMessage('Update object registration', | ||
884 | + (self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name)) | ||
885 | + Publisher.sendMessage('Update status text in GUI', _("Ready")) | ||
886 | + | ||
887 | + except wx._core.PyAssertionError: # TODO FIX: win64 | ||
888 | + pass | ||
889 | + | ||
890 | + else: | ||
891 | + dlg.NavigationTrackerWarning(0, 'choose') | ||
892 | + | ||
893 | + def OnLinkLoad(self, event=None): | ||
894 | + filename = dlg.ShowLoadRegistrationDialog() | ||
895 | + | ||
896 | + if filename: | ||
897 | + data = np.loadtxt(filename, delimiter='\t') | ||
898 | + self.obj_fiducials = data[:, :3] | ||
899 | + self.obj_orients = data[:, 3:] | ||
900 | + | ||
901 | + text_file = open(filename, "r") | ||
902 | + header = text_file.readline().split('\t') | ||
903 | + text_file.close() | ||
904 | + | ||
905 | + self.obj_name = header[1] | ||
906 | + self.obj_ref_mode = int(header[-1]) | ||
907 | + | ||
908 | + self.checktrack.Enable(1) | ||
909 | + Publisher.sendMessage('Update object registration', | ||
910 | + (self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name)) | ||
911 | + Publisher.sendMessage('Update status text in GUI', _("Ready")) | ||
912 | + wx.MessageBox(_("Object file successfully loaded"), _("Load")) | ||
913 | + | ||
914 | + def ShowSaveObjectDialog(self, evt): | ||
915 | + if np.isnan(self.obj_fiducials).any() or np.isnan(self.obj_orients).any(): | ||
916 | + wx.MessageBox(_("Digitize all object fiducials before saving"), _("Save error")) | ||
917 | + else: | ||
918 | + filename = dlg.ShowSaveRegistrationDialog("object_registration.obr") | ||
919 | + if filename: | ||
920 | + hdr = 'Object' + "\t" + self.obj_name + "\t" + 'Reference' + "\t" + str('%d' % self.obj_ref_mode) | ||
921 | + data = np.hstack([self.obj_fiducials, self.obj_orients]) | ||
922 | + np.savetxt(filename, data, fmt='%.4f', delimiter='\t', newline='\n', header=hdr) | ||
923 | + wx.MessageBox(_("Object file successfully saved"), _("Save")) | ||
924 | + | ||
925 | + def OnCloseProject(self, pubsub_evt): | ||
926 | + self.checkrecordcoords.SetValue(False) | ||
927 | + self.checkrecordcoords.Enable(0) | ||
928 | + self.checktrack.SetValue(False) | ||
929 | + self.checktrack.Enable(0) | ||
930 | + | ||
931 | + self.nav_prop = None | ||
932 | + self.obj_fiducials = None | ||
933 | + self.obj_orients = None | ||
934 | + self.obj_ref_mode = None | ||
935 | + self.obj_name = None | ||
936 | + self.timestamp = const.TIMESTAMP | ||
937 | + | ||
537 | 938 | ||
538 | class MarkersPanel(wx.Panel): | 939 | class MarkersPanel(wx.Panel): |
539 | def __init__(self, parent): | 940 | def __init__(self, parent): |
@@ -545,9 +946,12 @@ class MarkersPanel(wx.Panel): | @@ -545,9 +946,12 @@ class MarkersPanel(wx.Panel): | ||
545 | 946 | ||
546 | self.__bind_events() | 947 | self.__bind_events() |
547 | 948 | ||
548 | - self.current_coord = 0, 0, 0 | 949 | + self.current_coord = 0, 0, 0, 0, 0, 0 |
950 | + self.current_angle = 0, 0, 0 | ||
549 | self.list_coord = [] | 951 | self.list_coord = [] |
550 | self.marker_ind = 0 | 952 | self.marker_ind = 0 |
953 | + self.tgt_flag = self.tgt_index = None | ||
954 | + self.nav_status = False | ||
551 | 955 | ||
552 | self.marker_colour = (0.0, 0.0, 1.) | 956 | self.marker_colour = (0.0, 0.0, 1.) |
553 | self.marker_size = 4 | 957 | self.marker_size = 4 |
@@ -608,8 +1012,8 @@ class MarkersPanel(wx.Panel): | @@ -608,8 +1012,8 @@ class MarkersPanel(wx.Panel): | ||
608 | self.lc.SetColumnWidth(1, 50) | 1012 | self.lc.SetColumnWidth(1, 50) |
609 | self.lc.SetColumnWidth(2, 50) | 1013 | self.lc.SetColumnWidth(2, 50) |
610 | self.lc.SetColumnWidth(3, 50) | 1014 | self.lc.SetColumnWidth(3, 50) |
611 | - self.lc.SetColumnWidth(4, 50) | ||
612 | - self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnListEditMarkerId) | 1015 | + self.lc.SetColumnWidth(4, 60) |
1016 | + self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnMouseRightDown) | ||
613 | self.lc.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemBlink) | 1017 | self.lc.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemBlink) |
614 | self.lc.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnStopItemBlink) | 1018 | self.lc.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnStopItemBlink) |
615 | 1019 | ||
@@ -625,17 +1029,34 @@ class MarkersPanel(wx.Panel): | @@ -625,17 +1029,34 @@ class MarkersPanel(wx.Panel): | ||
625 | self.Update() | 1029 | self.Update() |
626 | 1030 | ||
627 | def __bind_events(self): | 1031 | def __bind_events(self): |
628 | - Publisher.subscribe(self.UpdateCurrentCoord, 'Set ball reference position') | 1032 | + Publisher.subscribe(self.UpdateCurrentCoord, 'Co-registered points') |
629 | Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker') | 1033 | Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker') |
1034 | + Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers') | ||
630 | Publisher.subscribe(self.OnCreateMarker, 'Create marker') | 1035 | Publisher.subscribe(self.OnCreateMarker, 'Create marker') |
1036 | + Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') | ||
631 | 1037 | ||
632 | def UpdateCurrentCoord(self, pubsub_evt): | 1038 | def UpdateCurrentCoord(self, pubsub_evt): |
633 | - self.current_coord = pubsub_evt.data | 1039 | + self.current_coord = pubsub_evt.data[1][:] |
1040 | + #self.current_angle = pubsub_evt.data[1][3:] | ||
1041 | + | ||
1042 | + def UpdateNavigationStatus(self, pubsub_evt): | ||
1043 | + if pubsub_evt.data is False: | ||
1044 | + sleep(0.5) | ||
1045 | + #self.current_coord[3:] = 0, 0, 0 | ||
1046 | + self.nav_status = False | ||
1047 | + else: | ||
1048 | + self.nav_status = True | ||
1049 | + | ||
1050 | + def OnMouseRightDown(self, evt): | ||
1051 | + self.OnListEditMarkerId(self.nav_status) | ||
634 | 1052 | ||
635 | - def OnListEditMarkerId(self, evt): | 1053 | + def OnListEditMarkerId(self, status): |
636 | menu_id = wx.Menu() | 1054 | menu_id = wx.Menu() |
637 | - menu_id.Append(-1, _('Edit ID')) | ||
638 | - menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerId) | 1055 | + edit_id = menu_id.Append(0, _('Edit ID')) |
1056 | + menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerId, edit_id) | ||
1057 | + target_menu = menu_id.Append(1, _('Set as target')) | ||
1058 | + menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) | ||
1059 | + target_menu.Enable(status) | ||
639 | self.PopupMenu(menu_id) | 1060 | self.PopupMenu(menu_id) |
640 | menu_id.Destroy() | 1061 | menu_id.Destroy() |
641 | 1062 | ||
@@ -646,20 +1067,64 @@ class MarkersPanel(wx.Panel): | @@ -646,20 +1067,64 @@ class MarkersPanel(wx.Panel): | ||
646 | Publisher.sendMessage('Stop Blink Marker') | 1067 | Publisher.sendMessage('Stop Blink Marker') |
647 | 1068 | ||
648 | def OnMenuEditMarkerId(self, evt): | 1069 | def OnMenuEditMarkerId(self, evt): |
649 | - id_label = dlg.EnterMarkerID(self.lc.GetItemText(self.lc.GetFocusedItem(), 4)) | ||
650 | list_index = self.lc.GetFocusedItem() | 1070 | list_index = self.lc.GetFocusedItem() |
1071 | + if evt == 'TARGET': | ||
1072 | + id_label = evt | ||
1073 | + else: | ||
1074 | + id_label = dlg.EnterMarkerID(self.lc.GetItemText(list_index, 4)) | ||
1075 | + if id_label == 'TARGET': | ||
1076 | + id_label = '' | ||
1077 | + dlg.InvalidTargetID() | ||
651 | self.lc.SetStringItem(list_index, 4, id_label) | 1078 | self.lc.SetStringItem(list_index, 4, id_label) |
652 | # Add the new ID to exported list | 1079 | # Add the new ID to exported list |
653 | - self.list_coord[list_index][7] = str(id_label) | 1080 | + if len(self.list_coord[list_index]) > 8: |
1081 | + self.list_coord[list_index][10] = str(id_label) | ||
1082 | + else: | ||
1083 | + self.list_coord[list_index][7] = str(id_label) | ||
1084 | + | ||
1085 | + def OnMenuSetTarget(self, evt): | ||
1086 | + if isinstance(evt, int): | ||
1087 | + self.lc.Focus(evt) | ||
1088 | + | ||
1089 | + if self.tgt_flag: | ||
1090 | + self.lc.SetItemBackgroundColour(self.tgt_index, 'white') | ||
1091 | + Publisher.sendMessage('Set target transparency', [False, self.tgt_index]) | ||
1092 | + self.lc.SetStringItem(self.tgt_index, 4, '') | ||
1093 | + # Add the new ID to exported list | ||
1094 | + if len(self.list_coord[self.tgt_index]) > 8: | ||
1095 | + self.list_coord[self.tgt_index][10] = str('') | ||
1096 | + else: | ||
1097 | + self.list_coord[self.tgt_index][7] = str('') | ||
1098 | + | ||
1099 | + self.tgt_index = self.lc.GetFocusedItem() | ||
1100 | + self.lc.SetItemBackgroundColour(self.tgt_index, 'RED') | ||
1101 | + | ||
1102 | + Publisher.sendMessage('Update target', self.list_coord[self.tgt_index]) | ||
1103 | + Publisher.sendMessage('Set target transparency', [True, self.tgt_index]) | ||
1104 | + Publisher.sendMessage('Disable or enable coil tracker', True) | ||
1105 | + self.OnMenuEditMarkerId('TARGET') | ||
1106 | + self.tgt_flag = True | ||
1107 | + dlg.NewTarget() | ||
1108 | + | ||
1109 | + def OnDeleteAllMarkers(self, evt): | ||
1110 | + if self.list_coord: | ||
1111 | + if hasattr(evt, 'data'): | ||
1112 | + result = wx.ID_OK | ||
1113 | + else: | ||
1114 | + result = dlg.DeleteAllMarkers() | ||
1115 | + | ||
1116 | + if result == wx.ID_OK: | ||
1117 | + self.list_coord = [] | ||
1118 | + self.marker_ind = 0 | ||
1119 | + Publisher.sendMessage('Remove all markers', self.lc.GetItemCount()) | ||
1120 | + self.lc.DeleteAllItems() | ||
1121 | + Publisher.sendMessage('Stop Blink Marker', 'DeleteAll') | ||
654 | 1122 | ||
655 | - def OnDeleteAllMarkers(self, pubsub_evt): | ||
656 | - result = dlg.DeleteAllMarkers() | ||
657 | - if result == wx.ID_OK: | ||
658 | - self.list_coord = [] | ||
659 | - self.marker_ind = 0 | ||
660 | - Publisher.sendMessage('Remove all markers', self.lc.GetItemCount()) | ||
661 | - self.lc.DeleteAllItems() | ||
662 | - Publisher.sendMessage('Stop Blink Marker', 'DeleteAll') | 1123 | + if self.tgt_flag: |
1124 | + self.tgt_flag = self.tgt_index = None | ||
1125 | + Publisher.sendMessage('Disable or enable coil tracker', False) | ||
1126 | + if not hasattr(evt, 'data'): | ||
1127 | + dlg.DeleteTarget() | ||
663 | 1128 | ||
664 | def OnDeleteSingleMarker(self, evt): | 1129 | def OnDeleteSingleMarker(self, evt): |
665 | # OnDeleteSingleMarker is used for both pubsub and button click events | 1130 | # OnDeleteSingleMarker is used for both pubsub and button click events |
@@ -682,6 +1147,10 @@ class MarkersPanel(wx.Panel): | @@ -682,6 +1147,10 @@ class MarkersPanel(wx.Panel): | ||
682 | index = None | 1147 | index = None |
683 | 1148 | ||
684 | if index: | 1149 | if index: |
1150 | + if self.tgt_flag and self.tgt_index == index[0]: | ||
1151 | + self.tgt_flag = self.tgt_index = None | ||
1152 | + Publisher.sendMessage('Disable or enable coil tracker', False) | ||
1153 | + dlg.DeleteTarget() | ||
685 | self.DeleteMarker(index) | 1154 | self.DeleteMarker(index) |
686 | else: | 1155 | else: |
687 | dlg.NoMarkerSelected() | 1156 | dlg.NoMarkerSelected() |
@@ -711,20 +1180,42 @@ class MarkersPanel(wx.Panel): | @@ -711,20 +1180,42 @@ class MarkersPanel(wx.Panel): | ||
711 | 1180 | ||
712 | if filepath: | 1181 | if filepath: |
713 | try: | 1182 | try: |
1183 | + count_line = self.lc.GetItemCount() | ||
714 | content = [s.rstrip() for s in open(filepath)] | 1184 | content = [s.rstrip() for s in open(filepath)] |
715 | for data in content: | 1185 | for data in content: |
1186 | + target = None | ||
716 | line = [s for s in data.split()] | 1187 | line = [s for s in data.split()] |
717 | - coord = float(line[0]), float(line[1]), float(line[2]) | ||
718 | - colour = float(line[3]), float(line[4]), float(line[5]) | ||
719 | - size = float(line[6]) | 1188 | + if len(line) > 8: |
1189 | + coord = float(line[0]), float(line[1]), float(line[2]), float(line[3]), float(line[4]), float(line[5]) | ||
1190 | + colour = float(line[6]), float(line[7]), float(line[8]) | ||
1191 | + size = float(line[9]) | ||
1192 | + | ||
1193 | + if len(line) == 11: | ||
1194 | + for i in const.BTNS_IMG_MKS: | ||
1195 | + if line[10] in const.BTNS_IMG_MKS[i].values()[0]: | ||
1196 | + Publisher.sendMessage('Load image fiducials', (line[10], coord)) | ||
1197 | + elif line[10] == 'TARGET': | ||
1198 | + target = count_line | ||
1199 | + else: | ||
1200 | + line.append("") | ||
1201 | + | ||
1202 | + self.CreateMarker(coord, colour, size, line[10]) | ||
1203 | + if target is not None: | ||
1204 | + self.OnMenuSetTarget(target) | ||
720 | 1205 | ||
721 | - if len(line) == 8: | ||
722 | - for i in const.BTNS_IMG_MKS: | ||
723 | - if line[7] in const.BTNS_IMG_MKS[i].values()[0]: | ||
724 | - Publisher.sendMessage('Load image fiducials', (line[7], coord)) | ||
725 | else: | 1206 | else: |
726 | - line.append("") | ||
727 | - self.CreateMarker(coord, colour, size, line[7]) | 1207 | + coord = float(line[0]), float(line[1]), float(line[2]), 0, 0, 0 |
1208 | + colour = float(line[3]), float(line[4]), float(line[5]) | ||
1209 | + size = float(line[6]) | ||
1210 | + | ||
1211 | + if len(line) == 8: | ||
1212 | + for i in const.BTNS_IMG_MKS: | ||
1213 | + if line[7] in const.BTNS_IMG_MKS[i].values()[0]: | ||
1214 | + Publisher.sendMessage('Load image fiducials', (line[7], coord)) | ||
1215 | + else: | ||
1216 | + line.append("") | ||
1217 | + self.CreateMarker(coord, colour, size, line[7]) | ||
1218 | + count_line += 1 | ||
728 | except: | 1219 | except: |
729 | dlg.InvalidMarkersFile() | 1220 | dlg.InvalidMarkersFile() |
730 | 1221 | ||
@@ -744,14 +1235,16 @@ class MarkersPanel(wx.Panel): | @@ -744,14 +1235,16 @@ class MarkersPanel(wx.Panel): | ||
744 | text_file = open(filename, "w") | 1235 | text_file = open(filename, "w") |
745 | list_slice1 = self.list_coord[0] | 1236 | list_slice1 = self.list_coord[0] |
746 | coord = str('%.3f' %self.list_coord[0][0]) + "\t" + str('%.3f' %self.list_coord[0][1]) + "\t" + str('%.3f' %self.list_coord[0][2]) | 1237 | coord = str('%.3f' %self.list_coord[0][0]) + "\t" + str('%.3f' %self.list_coord[0][1]) + "\t" + str('%.3f' %self.list_coord[0][2]) |
747 | - properties = str('%.3f' %list_slice1[3]) + "\t" + str('%.3f' %list_slice1[4]) + "\t" + str('%.3f' %list_slice1[5]) + "\t" + str('%.1f' %list_slice1[6]) + "\t" + list_slice1[7] | ||
748 | - line = coord + "\t" + properties + "\n" | 1238 | + angles = str('%.3f' %self.list_coord[0][3]) + "\t" + str('%.3f' %self.list_coord[0][4]) + "\t" + str('%.3f' %self.list_coord[0][5]) |
1239 | + properties = str('%.3f' %list_slice1[6]) + "\t" + str('%.3f' %list_slice1[7]) + "\t" + str('%.3f' %list_slice1[8]) + "\t" + str('%.1f' %list_slice1[9]) + "\t" + list_slice1[10] | ||
1240 | + line = coord + "\t" + angles + "\t" + properties + "\n" | ||
749 | list_slice = self.list_coord[1:] | 1241 | list_slice = self.list_coord[1:] |
750 | 1242 | ||
751 | for value in list_slice: | 1243 | for value in list_slice: |
752 | coord = str('%.3f' %value[0]) + "\t" + str('%.3f' %value[1]) + "\t" + str('%.3f' %value[2]) | 1244 | coord = str('%.3f' %value[0]) + "\t" + str('%.3f' %value[1]) + "\t" + str('%.3f' %value[2]) |
753 | - properties = str('%.3f' %value[3]) + "\t" + str('%.3f' %value[4]) + "\t" + str('%.3f' %value[5]) + "\t" + str('%.1f' %value[6]) + "\t" + value[7] | ||
754 | - line = line + coord + "\t" + properties + "\n" | 1245 | + angles = str('%.3f' % value[3]) + "\t" + str('%.3f' % value[4]) + "\t" + str('%.3f' % value[5]) |
1246 | + properties = str('%.3f' %value[6]) + "\t" + str('%.3f' %value[7]) + "\t" + str('%.3f' %value[8]) + "\t" + str('%.1f' %value[9]) + "\t" + value[10] | ||
1247 | + line = line + coord + "\t" + angles + "\t" +properties + "\n" | ||
755 | 1248 | ||
756 | text_file.writelines(line) | 1249 | text_file.writelines(line) |
757 | text_file.close() | 1250 | text_file.close() |
@@ -766,12 +1259,13 @@ class MarkersPanel(wx.Panel): | @@ -766,12 +1259,13 @@ class MarkersPanel(wx.Panel): | ||
766 | # TODO: Use matrix coordinates and not world coordinates as current method. | 1259 | # TODO: Use matrix coordinates and not world coordinates as current method. |
767 | # This makes easier for inter-software comprehension. | 1260 | # This makes easier for inter-software comprehension. |
768 | 1261 | ||
769 | - Publisher.sendMessage('Add marker', (self.marker_ind, size, colour, coord)) | 1262 | + Publisher.sendMessage('Add marker', (self.marker_ind, size, colour, coord[0:3])) |
770 | 1263 | ||
771 | self.marker_ind += 1 | 1264 | self.marker_ind += 1 |
772 | 1265 | ||
773 | # List of lists with coordinates and properties of a marker | 1266 | # List of lists with coordinates and properties of a marker |
774 | - line = [coord[0], coord[1], coord[2], colour[0], colour[1], colour[2], self.marker_size, marker_id] | 1267 | + |
1268 | + line = [coord[0], coord[1], coord[2], coord[3], coord[4], coord[5], colour[0], colour[1], colour[2], size, marker_id] | ||
775 | 1269 | ||
776 | # Adding current line to a list of all markers already created | 1270 | # Adding current line to a list of all markers already created |
777 | if not self.list_coord: | 1271 | if not self.list_coord: |
No preview for this file type
No preview for this file type
No preview for this file type