Commit af3ad6f5f9c784393dbf58dc76f264462fc26146
Exists in
master
Merge remote-tracking branch 'upstream/master'
Showing
7 changed files
with
614 additions
and
142 deletions
Show diff stats
invesalius/data/bases.py
1 | import numpy as np | 1 | import numpy as np |
2 | import invesalius.data.coordinates as dco | 2 | import invesalius.data.coordinates as dco |
3 | import invesalius.data.transformations as tr | 3 | import invesalius.data.transformations as tr |
4 | - | 4 | +import invesalius.data.coregistration as dcr |
5 | 5 | ||
6 | def angle_calculation(ap_axis, coil_axis): | 6 | def angle_calculation(ap_axis, coil_axis): |
7 | """ | 7 | """ |
@@ -100,78 +100,31 @@ def base_creation(fiducials): | @@ -100,78 +100,31 @@ def base_creation(fiducials): | ||
100 | return m, q | 100 | return m, q |
101 | 101 | ||
102 | 102 | ||
103 | -# def calculate_fre(fiducials, minv, n, q, o): | ||
104 | -# """ | ||
105 | -# Calculate the Fiducial Registration Error for neuronavigation. | ||
106 | -# | ||
107 | -# :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates | ||
108 | -# :param minv: inverse matrix given by base creation | ||
109 | -# :param n: base change matrix given by base creation | ||
110 | -# :param q: origin of first base | ||
111 | -# :param o: origin of second base | ||
112 | -# :return: float number of fiducial registration error | ||
113 | -# """ | ||
114 | -# | ||
115 | -# img = np.zeros([3, 3]) | ||
116 | -# dist = np.zeros([3, 1]) | ||
117 | -# | ||
118 | -# q1 = np.mat(q).reshape(3, 1) | ||
119 | -# q2 = np.mat(o).reshape(3, 1) | ||
120 | -# | ||
121 | -# p1 = np.mat(fiducials[3, :]).reshape(3, 1) | ||
122 | -# p2 = np.mat(fiducials[4, :]).reshape(3, 1) | ||
123 | -# p3 = np.mat(fiducials[5, :]).reshape(3, 1) | ||
124 | -# | ||
125 | -# img[0, :] = np.asarray((q1 + (minv * n) * (p1 - q2)).reshape(1, 3)) | ||
126 | -# img[1, :] = np.asarray((q1 + (minv * n) * (p2 - q2)).reshape(1, 3)) | ||
127 | -# img[2, :] = np.asarray((q1 + (minv * n) * (p3 - q2)).reshape(1, 3)) | ||
128 | -# | ||
129 | -# dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) | ||
130 | -# dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2))) | ||
131 | -# dist[2] = np.sqrt(np.sum(np.power((img[2, :] - fiducials[2, :]), 2))) | ||
132 | -# | ||
133 | -# return float(np.sqrt(np.sum(dist ** 2) / 3)) | ||
134 | - | ||
135 | - | ||
136 | -def calculate_fre_m(fiducials): | 103 | +def calculate_fre(fiducials_raw, fiducials, ref_mode_id, m_change, m_icp=None): |
137 | """ | 104 | """ |
138 | Calculate the Fiducial Registration Error for neuronavigation. | 105 | Calculate the Fiducial Registration Error for neuronavigation. |
139 | 106 | ||
107 | + :param fiducials_raw: array of 6 rows (tracker probe and reference) and 3 columns (x, y, z) with coordinates | ||
108 | + :type fiducials_raw: numpy.ndarray | ||
140 | :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates | 109 | :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates |
141 | - :param minv: inverse matrix given by base creation | ||
142 | - :param n: base change matrix given by base creation | ||
143 | - :param q: origin of first base | ||
144 | - :param o: origin of second base | 110 | + :type fiducials: numpy.ndarray |
111 | + :param ref_mode_id: Reference mode ID | ||
112 | + :type ref_mode_id: int | ||
113 | + :param m_change: 3x3 array representing change of basis from head in tracking system to vtk head system | ||
114 | + :type m_change: numpy.ndarray | ||
115 | + :param m_icp: list with icp flag and 3x3 affine array | ||
116 | + :type m_icp: list[int, numpy.ndarray] | ||
145 | :return: float number of fiducial registration error | 117 | :return: float number of fiducial registration error |
146 | """ | 118 | """ |
119 | + if m_icp is not None: | ||
120 | + icp = [True, m_icp] | ||
121 | + else: | ||
122 | + icp = [False, None] | ||
147 | 123 | ||
148 | - m, q1, minv = base_creation_old(fiducials[:3, :]) | ||
149 | - n, q2, ninv = base_creation_old(fiducials[3:, :]) | ||
150 | - | ||
151 | - # TODO: replace the old by the new base creation | ||
152 | - # the values differ greatly if FRE is computed using the old or new base_creation | ||
153 | - # check the reason for the difference, because they should be the same | ||
154 | - # m, q1 = base_creation(fiducials[:3, :]) | ||
155 | - # n, q2 = base_creation(fiducials[3:, :]) | ||
156 | - # minv = np.linalg.inv(m) | ||
157 | - | ||
158 | - img = np.zeros([3, 3]) | ||
159 | dist = np.zeros([3, 1]) | 124 | dist = np.zeros([3, 1]) |
160 | - | ||
161 | - q1 = q1.reshape(3, 1) | ||
162 | - q2 = q2.reshape(3, 1) | ||
163 | - | ||
164 | - p1 = fiducials[3, :].reshape(3, 1) | ||
165 | - p2 = fiducials[4, :].reshape(3, 1) | ||
166 | - p3 = fiducials[5, :].reshape(3, 1) | ||
167 | - | ||
168 | - img[0, :] = (q1 + (minv @ n) * (p1 - q2)).reshape(1, 3) | ||
169 | - img[1, :] = (q1 + (minv @ n) * (p2 - q2)).reshape(1, 3) | ||
170 | - img[2, :] = (q1 + (minv @ n) * (p3 - q2)).reshape(1, 3) | ||
171 | - | ||
172 | - dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) | ||
173 | - dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2))) | ||
174 | - dist[2] = np.sqrt(np.sum(np.power((img[2, :] - fiducials[2, :]), 2))) | 125 | + for i in range(0, 6, 2): |
126 | + p_m, _ = dcr.corregistrate_dynamic((m_change, 0), fiducials_raw[i:i+2], ref_mode_id, icp) | ||
127 | + dist[int(i/2)] = np.sqrt(np.sum(np.power((p_m[:3] - fiducials[int(i/2), :]), 2))) | ||
175 | 128 | ||
176 | return float(np.sqrt(np.sum(dist ** 2) / 3)) | 129 | return float(np.sqrt(np.sum(dist ** 2) / 3)) |
177 | 130 | ||
@@ -203,6 +156,13 @@ def calculate_fre_m(fiducials): | @@ -203,6 +156,13 @@ def calculate_fre_m(fiducials): | ||
203 | # | 156 | # |
204 | # return point_rot | 157 | # return point_rot |
205 | 158 | ||
159 | +def transform_icp(m_img, m_icp): | ||
160 | + coord_img = [m_img[0, -1], -m_img[1, -1], m_img[2, -1], 1] | ||
161 | + m_img[0, -1], m_img[1, -1], m_img[2, -1], _ = m_icp @ coord_img | ||
162 | + m_img[0, -1], m_img[1, -1], m_img[2, -1] = m_img[0, -1], -m_img[1, -1], m_img[2, -1] | ||
163 | + | ||
164 | + return m_img | ||
165 | + | ||
206 | 166 | ||
207 | def object_registration(fiducials, orients, coord_raw, m_change): | 167 | def object_registration(fiducials, orients, coord_raw, m_change): |
208 | """ | 168 | """ |
invesalius/data/coordinates.py
@@ -259,7 +259,7 @@ def DebugCoord(trk_init, trck_id, ref_mode): | @@ -259,7 +259,7 @@ def DebugCoord(trk_init, trck_id, ref_mode): | ||
259 | coord4 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), | 259 | coord4 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), |
260 | uniform(*dt), uniform(*dt), uniform(*dt)]) | 260 | uniform(*dt), uniform(*dt), uniform(*dt)]) |
261 | 261 | ||
262 | - sleep(0.05) | 262 | + sleep(0.15) |
263 | 263 | ||
264 | # coord1 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | 264 | # coord1 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), |
265 | # uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | 265 | # uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) |
invesalius/data/coregistration.py
@@ -24,6 +24,7 @@ from time import sleep | @@ -24,6 +24,7 @@ from time import sleep | ||
24 | 24 | ||
25 | import invesalius.data.coordinates as dco | 25 | import invesalius.data.coordinates as dco |
26 | import invesalius.data.transformations as tr | 26 | import invesalius.data.transformations as tr |
27 | +import invesalius.data.bases as bases | ||
27 | 28 | ||
28 | 29 | ||
29 | # TODO: Replace the use of degrees by radians in every part of the navigation pipeline | 30 | # TODO: Replace the use of degrees by radians in every part of the navigation pipeline |
@@ -101,7 +102,7 @@ def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | @@ -101,7 +102,7 @@ def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | ||
101 | return m_img | 102 | return m_img |
102 | 103 | ||
103 | 104 | ||
104 | -def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id): | 105 | +def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id, icp): |
105 | 106 | ||
106 | m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = inp | 107 | m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = inp |
107 | 108 | ||
@@ -116,6 +117,8 @@ def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id): | @@ -116,6 +117,8 @@ def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id): | ||
116 | m_probe_ref[2, -1] = -m_probe_ref[2, -1] | 117 | m_probe_ref[2, -1] = -m_probe_ref[2, -1] |
117 | # corregistrate from tracker to image space | 118 | # corregistrate from tracker to image space |
118 | m_img = tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn) | 119 | m_img = tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn) |
120 | + if icp[0]: | ||
121 | + m_img = bases.transform_icp(m_img, icp[1]) | ||
119 | # compute rotation angles | 122 | # compute rotation angles |
120 | _, _, angles, _, _ = tr.decompose_matrix(m_img) | 123 | _, _, angles, _, _ = tr.decompose_matrix(m_img) |
121 | # create output coordiante list | 124 | # create output coordiante list |
@@ -124,6 +127,9 @@ def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id): | @@ -124,6 +127,9 @@ def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id): | ||
124 | 127 | ||
125 | return coord, m_img | 128 | return coord, m_img |
126 | 129 | ||
130 | +def UpdateICP(self, m_icp, flag): | ||
131 | + self.m_icp = m_icp | ||
132 | + self.icp = flag | ||
127 | 133 | ||
128 | def compute_marker_transformation(coord_raw, obj_ref_mode): | 134 | def compute_marker_transformation(coord_raw, obj_ref_mode): |
129 | psi, theta, phi = np.radians(coord_raw[obj_ref_mode, 3:]) | 135 | psi, theta, phi = np.radians(coord_raw[obj_ref_mode, 3:]) |
@@ -133,7 +139,7 @@ def compute_marker_transformation(coord_raw, obj_ref_mode): | @@ -133,7 +139,7 @@ def compute_marker_transformation(coord_raw, obj_ref_mode): | ||
133 | return m_probe | 139 | return m_probe |
134 | 140 | ||
135 | 141 | ||
136 | -def corregistrate_dynamic(inp, coord_raw, ref_mode_id): | 142 | +def corregistrate_dynamic(inp, coord_raw, ref_mode_id, icp): |
137 | 143 | ||
138 | m_change, obj_ref_mode = inp | 144 | m_change, obj_ref_mode = inp |
139 | 145 | ||
@@ -150,6 +156,10 @@ def corregistrate_dynamic(inp, coord_raw, ref_mode_id): | @@ -150,6 +156,10 @@ def corregistrate_dynamic(inp, coord_raw, ref_mode_id): | ||
150 | m_probe_ref[2, -1] = -m_probe_ref[2, -1] | 156 | m_probe_ref[2, -1] = -m_probe_ref[2, -1] |
151 | # corregistrate from tracker to image space | 157 | # corregistrate from tracker to image space |
152 | m_img = m_change @ m_probe_ref | 158 | m_img = m_change @ m_probe_ref |
159 | + | ||
160 | + if icp[0]: | ||
161 | + m_img = bases.transform_icp(m_img, icp[1]) | ||
162 | + | ||
153 | # compute rotation angles | 163 | # compute rotation angles |
154 | _, _, angles, _, _ = tr.decompose_matrix(m_img) | 164 | _, _, angles, _, _ = tr.decompose_matrix(m_img) |
155 | # create output coordiante list | 165 | # create output coordiante list |
@@ -160,16 +170,19 @@ def corregistrate_dynamic(inp, coord_raw, ref_mode_id): | @@ -160,16 +170,19 @@ def corregistrate_dynamic(inp, coord_raw, ref_mode_id): | ||
160 | 170 | ||
161 | 171 | ||
162 | class CoordinateCorregistrate(threading.Thread): | 172 | class CoordinateCorregistrate(threading.Thread): |
163 | - def __init__(self, ref_mode_id, trck_info, coreg_data, coord_queue, view_tracts, coord_tracts_queue, event, sle): | 173 | + def __init__(self, ref_mode_id, trck_info, coreg_data, view_tracts, queues, event, sle): |
164 | threading.Thread.__init__(self, name='CoordCoregObject') | 174 | threading.Thread.__init__(self, name='CoordCoregObject') |
165 | self.ref_mode_id = ref_mode_id | 175 | self.ref_mode_id = ref_mode_id |
166 | self.trck_info = trck_info | 176 | self.trck_info = trck_info |
167 | self.coreg_data = coreg_data | 177 | self.coreg_data = coreg_data |
168 | - self.coord_queue = coord_queue | 178 | + self.coord_queue = queues[0] |
169 | self.view_tracts = view_tracts | 179 | self.view_tracts = view_tracts |
170 | - self.coord_tracts_queue = coord_tracts_queue | 180 | + self.coord_tracts_queue = queues[1] |
171 | self.event = event | 181 | self.event = event |
172 | self.sle = sle | 182 | self.sle = sle |
183 | + self.icp_queue = queues[2] | ||
184 | + self.icp = None | ||
185 | + self.m_icp = None | ||
173 | 186 | ||
174 | def run(self): | 187 | def run(self): |
175 | trck_info = self.trck_info | 188 | trck_info = self.trck_info |
@@ -180,19 +193,20 @@ class CoordinateCorregistrate(threading.Thread): | @@ -180,19 +193,20 @@ class CoordinateCorregistrate(threading.Thread): | ||
180 | # print('CoordCoreg: event {}'.format(self.event.is_set())) | 193 | # print('CoordCoreg: event {}'.format(self.event.is_set())) |
181 | while not self.event.is_set(): | 194 | while not self.event.is_set(): |
182 | try: | 195 | try: |
196 | + if self.icp_queue.empty(): | ||
197 | + None | ||
198 | + else: | ||
199 | + self.icp, self.m_icp = self.icp_queue.get_nowait() | ||
183 | # print(f"Set the coordinate") | 200 | # print(f"Set the coordinate") |
184 | coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | 201 | coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) |
185 | - coord, m_img = corregistrate_object_dynamic(coreg_data, coord_raw, self.ref_mode_id) | ||
186 | - # m_img = np.array([[0.38, -0.8, -0.45, 40.17], | ||
187 | - # [0.82, 0.52, -0.24, 152.28], | ||
188 | - # [0.43, -0.28, 0.86, 235.78], | ||
189 | - # [0., 0., 0., 1.]]) | ||
190 | - # angles = [-0.318, -0.441, 1.134] | ||
191 | - # coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
192 | - # np.degrees(angles[0]), np.degrees(angles[1]), np.degrees(angles[2]) | 202 | + coord, m_img = corregistrate_object_dynamic(coreg_data, coord_raw, self.ref_mode_id, [self.icp, self.m_icp]) |
193 | m_img_flip = m_img.copy() | 203 | m_img_flip = m_img.copy() |
194 | m_img_flip[1, -1] = -m_img_flip[1, -1] | 204 | m_img_flip[1, -1] = -m_img_flip[1, -1] |
195 | # self.pipeline.set_message(m_img_flip) | 205 | # self.pipeline.set_message(m_img_flip) |
206 | + | ||
207 | + if self.icp: | ||
208 | + m_img = bases.transform_icp(m_img, self.m_icp) | ||
209 | + | ||
196 | self.coord_queue.put_nowait([coord, m_img, view_obj]) | 210 | self.coord_queue.put_nowait([coord, m_img, view_obj]) |
197 | # print('CoordCoreg: put {}'.format(count)) | 211 | # print('CoordCoreg: put {}'.format(count)) |
198 | # count += 1 | 212 | # count += 1 |
@@ -200,6 +214,9 @@ class CoordinateCorregistrate(threading.Thread): | @@ -200,6 +214,9 @@ class CoordinateCorregistrate(threading.Thread): | ||
200 | if self.view_tracts: | 214 | if self.view_tracts: |
201 | self.coord_tracts_queue.put_nowait(m_img_flip) | 215 | self.coord_tracts_queue.put_nowait(m_img_flip) |
202 | 216 | ||
217 | + if not self.icp_queue.empty(): | ||
218 | + self.icp_queue.task_done() | ||
219 | + | ||
203 | # The sleep has to be in both threads | 220 | # The sleep has to be in both threads |
204 | sleep(self.sle) | 221 | sleep(self.sle) |
205 | except queue.Full: | 222 | except queue.Full: |
@@ -207,16 +224,19 @@ class CoordinateCorregistrate(threading.Thread): | @@ -207,16 +224,19 @@ class CoordinateCorregistrate(threading.Thread): | ||
207 | 224 | ||
208 | 225 | ||
209 | class CoordinateCorregistrateNoObject(threading.Thread): | 226 | class CoordinateCorregistrateNoObject(threading.Thread): |
210 | - def __init__(self, ref_mode_id, trck_info, coreg_data, coord_queue, view_tracts, coord_tracts_queue, event, sle): | 227 | + def __init__(self, ref_mode_id, trck_info, coreg_data, view_tracts, queues, event, sle): |
211 | threading.Thread.__init__(self, name='CoordCoregNoObject') | 228 | threading.Thread.__init__(self, name='CoordCoregNoObject') |
212 | self.ref_mode_id = ref_mode_id | 229 | self.ref_mode_id = ref_mode_id |
213 | self.trck_info = trck_info | 230 | self.trck_info = trck_info |
214 | self.coreg_data = coreg_data | 231 | self.coreg_data = coreg_data |
215 | - self.coord_queue = coord_queue | 232 | + self.coord_queue = queues[0] |
216 | self.view_tracts = view_tracts | 233 | self.view_tracts = view_tracts |
217 | - self.coord_tracts_queue = coord_tracts_queue | 234 | + self.coord_tracts_queue = queues[1] |
218 | self.event = event | 235 | self.event = event |
219 | self.sle = sle | 236 | self.sle = sle |
237 | + self.icp_queue = queues[2] | ||
238 | + self.icp = None | ||
239 | + self.m_icp = None | ||
220 | 240 | ||
221 | def run(self): | 241 | def run(self): |
222 | trck_info = self.trck_info | 242 | trck_info = self.trck_info |
@@ -227,17 +247,28 @@ class CoordinateCorregistrateNoObject(threading.Thread): | @@ -227,17 +247,28 @@ class CoordinateCorregistrateNoObject(threading.Thread): | ||
227 | # print('CoordCoreg: event {}'.format(self.event.is_set())) | 247 | # print('CoordCoreg: event {}'.format(self.event.is_set())) |
228 | while not self.event.is_set(): | 248 | while not self.event.is_set(): |
229 | try: | 249 | try: |
250 | + if self.icp_queue.empty(): | ||
251 | + None | ||
252 | + else: | ||
253 | + self.icp, self.m_icp = self.icp_queue.get_nowait() | ||
230 | # print(f"Set the coordinate") | 254 | # print(f"Set the coordinate") |
255 | + #print(self.icp, self.m_icp) | ||
231 | coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | 256 | coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) |
232 | - coord, m_img = corregistrate_dynamic(coreg_data, coord_raw, self.ref_mode_id) | 257 | + coord, m_img = corregistrate_dynamic(coreg_data, coord_raw, self.ref_mode_id, [self.icp, self.m_icp]) |
233 | # print("Coord: ", coord) | 258 | # print("Coord: ", coord) |
234 | m_img_flip = m_img.copy() | 259 | m_img_flip = m_img.copy() |
235 | m_img_flip[1, -1] = -m_img_flip[1, -1] | 260 | m_img_flip[1, -1] = -m_img_flip[1, -1] |
261 | + | ||
262 | + if self.icp: | ||
263 | + m_img = bases.transform_icp(m_img, self.m_icp) | ||
264 | + | ||
236 | self.coord_queue.put_nowait([coord, m_img, view_obj]) | 265 | self.coord_queue.put_nowait([coord, m_img, view_obj]) |
237 | 266 | ||
238 | if self.view_tracts: | 267 | if self.view_tracts: |
239 | self.coord_tracts_queue.put_nowait(m_img_flip) | 268 | self.coord_tracts_queue.put_nowait(m_img_flip) |
240 | 269 | ||
270 | + if not self.icp_queue.empty(): | ||
271 | + self.icp_queue.task_done() | ||
241 | # The sleep has to be in both threads | 272 | # The sleep has to be in both threads |
242 | sleep(self.sle) | 273 | sleep(self.sle) |
243 | except queue.Full: | 274 | except queue.Full: |
invesalius/data/tractography.py
@@ -230,7 +230,7 @@ def tracts_computation_branch(trk_list, alpha=255): | @@ -230,7 +230,7 @@ def tracts_computation_branch(trk_list, alpha=255): | ||
230 | 230 | ||
231 | class ComputeTractsThread(threading.Thread): | 231 | class ComputeTractsThread(threading.Thread): |
232 | 232 | ||
233 | - def __init__(self, inp, coord_tracts_queue, tracts_queue, event, sle): | 233 | + def __init__(self, inp, queues, event, sle): |
234 | """Class (threading) to compute real time tractography data for visualization. | 234 | """Class (threading) to compute real time tractography data for visualization. |
235 | 235 | ||
236 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | 236 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) |
@@ -243,10 +243,9 @@ class ComputeTractsThread(threading.Thread): | @@ -243,10 +243,9 @@ class ComputeTractsThread(threading.Thread): | ||
243 | 243 | ||
244 | :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | 244 | :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads |
245 | :type inp: list | 245 | :type inp: list |
246 | - :param coord_tracts_queue: Queue instance that manage co-registered coordinates | ||
247 | - :type coord_tracts_queue: queue.Queue | ||
248 | - :param tracts_queue: Queue instance that manage the tracts to be visualized | ||
249 | - :type tracts_queue: queue.Queue | 246 | + :param queues: Queue list with coord_tracts_queue (Queue instance that manage co-registered coordinates) and |
247 | + tracts_queue (Queue instance that manage the tracts to be visualized) | ||
248 | + :type queues: list[queue.Queue, queue.Queue] | ||
250 | :param event: Threading event to coordinate when tasks as done and allow UI release | 249 | :param event: Threading event to coordinate when tasks as done and allow UI release |
251 | :type event: threading.Event | 250 | :type event: threading.Event |
252 | :param sle: Sleep pause in seconds | 251 | :param sle: Sleep pause in seconds |
@@ -256,8 +255,8 @@ class ComputeTractsThread(threading.Thread): | @@ -256,8 +255,8 @@ class ComputeTractsThread(threading.Thread): | ||
256 | threading.Thread.__init__(self, name='ComputeTractsThread') | 255 | threading.Thread.__init__(self, name='ComputeTractsThread') |
257 | self.inp = inp | 256 | self.inp = inp |
258 | # self.coord_queue = coord_queue | 257 | # self.coord_queue = coord_queue |
259 | - self.coord_tracts_queue = coord_tracts_queue | ||
260 | - self.tracts_queue = tracts_queue | 258 | + self.coord_tracts_queue = queues[0] |
259 | + self.tracts_queue = queues[1] | ||
261 | # self.visualization_queue = visualization_queue | 260 | # self.visualization_queue = visualization_queue |
262 | self.event = event | 261 | self.event = event |
263 | self.sle = sle | 262 | self.sle = sle |
@@ -362,7 +361,7 @@ class ComputeTractsThread(threading.Thread): | @@ -362,7 +361,7 @@ class ComputeTractsThread(threading.Thread): | ||
362 | 361 | ||
363 | class ComputeTractsACTThread(threading.Thread): | 362 | class ComputeTractsACTThread(threading.Thread): |
364 | 363 | ||
365 | - def __init__(self, inp, coord_tracts_queue, tracts_queue, event, sle): | 364 | + def __init__(self, inp, queues, event, sle): |
366 | """Class (threading) to compute real time tractography data for visualization. | 365 | """Class (threading) to compute real time tractography data for visualization. |
367 | 366 | ||
368 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | 367 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) |
@@ -375,10 +374,9 @@ class ComputeTractsACTThread(threading.Thread): | @@ -375,10 +374,9 @@ class ComputeTractsACTThread(threading.Thread): | ||
375 | 374 | ||
376 | :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | 375 | :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads |
377 | :type inp: list | 376 | :type inp: list |
378 | - :param coord_tracts_queue: Queue instance that manage co-registered coordinates | ||
379 | - :type coord_tracts_queue: queue.Queue | ||
380 | - :param tracts_queue: Queue instance that manage the tracts to be visualized | ||
381 | - :type tracts_queue: queue.Queue | 377 | + :param queues: Queue list with coord_tracts_queue (Queue instance that manage co-registered coordinates) and |
378 | + tracts_queue (Queue instance that manage the tracts to be visualized) | ||
379 | + :type queues: list[queue.Queue, queue.Queue] | ||
382 | :param event: Threading event to coordinate when tasks as done and allow UI release | 380 | :param event: Threading event to coordinate when tasks as done and allow UI release |
383 | :type event: threading.Event | 381 | :type event: threading.Event |
384 | :param sle: Sleep pause in seconds | 382 | :param sle: Sleep pause in seconds |
@@ -388,8 +386,8 @@ class ComputeTractsACTThread(threading.Thread): | @@ -388,8 +386,8 @@ class ComputeTractsACTThread(threading.Thread): | ||
388 | threading.Thread.__init__(self, name='ComputeTractsThreadACT') | 386 | threading.Thread.__init__(self, name='ComputeTractsThreadACT') |
389 | self.inp = inp | 387 | self.inp = inp |
390 | # self.coord_queue = coord_queue | 388 | # self.coord_queue = coord_queue |
391 | - self.coord_tracts_queue = coord_tracts_queue | ||
392 | - self.tracts_queue = tracts_queue | 389 | + self.coord_tracts_queue = queues[0] |
390 | + self.tracts_queue = queues[1] | ||
393 | # on first pilots (january 12, 2021) used (-4, 4) | 391 | # on first pilots (january 12, 2021) used (-4, 4) |
394 | self.coord_list_w = img_utils.create_grid((-2, 2), (0, 20), inp[2]-5, 1) | 392 | self.coord_list_w = img_utils.create_grid((-2, 2), (0, 20), inp[2]-5, 1) |
395 | # self.coord_list_sph = img_utils.create_spherical_grid(10, 1) | 393 | # self.coord_list_sph = img_utils.create_spherical_grid(10, 1) |
invesalius/data/viewer_slice.py
@@ -837,8 +837,7 @@ class Viewer(wx.Panel): | @@ -837,8 +837,7 @@ class Viewer(wx.Panel): | ||
837 | # Publisher.subscribe(self.__update_cross_position, | 837 | # Publisher.subscribe(self.__update_cross_position, |
838 | # 'Update cross position %s' % self.orientation) | 838 | # 'Update cross position %s' % self.orientation) |
839 | Publisher.subscribe(self.SetCrossFocalPoint, 'Set cross focal point') | 839 | Publisher.subscribe(self.SetCrossFocalPoint, 'Set cross focal point') |
840 | - # Publisher.subscribe(self.UpdateSlicesPosition, | ||
841 | - # 'Co-registered points') | 840 | + Publisher.subscribe(self.UpdateSlicesPosition, 'Update slices position') |
842 | ### | 841 | ### |
843 | # Publisher.subscribe(self.ChangeBrushColour, | 842 | # Publisher.subscribe(self.ChangeBrushColour, |
844 | # 'Add mask') | 843 | # 'Add mask') |
@@ -849,9 +848,9 @@ class Viewer(wx.Panel): | @@ -849,9 +848,9 @@ class Viewer(wx.Panel): | ||
849 | Publisher.subscribe(self.UpdateWindowLevelText, | 848 | Publisher.subscribe(self.UpdateWindowLevelText, |
850 | 'Update window level text') | 849 | 'Update window level text') |
851 | 850 | ||
852 | - #Publisher.subscribe(self._set_cross_visibility,\ | ||
853 | - # 'Set cross visibility') | ||
854 | - ### | 851 | + Publisher.subscribe(self._set_cross_visibility, |
852 | + 'Set cross visibility') | ||
853 | + | ||
855 | Publisher.subscribe(self.__set_layout, | 854 | Publisher.subscribe(self.__set_layout, |
856 | 'Set slice viewer layout') | 855 | 'Set slice viewer layout') |
857 | 856 |
invesalius/gui/dialogs.py
@@ -23,6 +23,7 @@ import os | @@ -23,6 +23,7 @@ import os | ||
23 | import random | 23 | import random |
24 | import sys | 24 | import sys |
25 | import time | 25 | import time |
26 | +from functools import partial | ||
26 | 27 | ||
27 | from concurrent import futures | 28 | from concurrent import futures |
28 | 29 | ||
@@ -58,10 +59,12 @@ import invesalius.data.coordinates as dco | @@ -58,10 +59,12 @@ import invesalius.data.coordinates as dco | ||
58 | import invesalius.gui.widgets.gradient as grad | 59 | import invesalius.gui.widgets.gradient as grad |
59 | import invesalius.session as ses | 60 | import invesalius.session as ses |
60 | import invesalius.utils as utils | 61 | import invesalius.utils as utils |
62 | +import invesalius.data.bases as bases | ||
61 | from invesalius.gui.widgets.inv_spinctrl import InvSpinCtrl, InvFloatSpinCtrl | 63 | from invesalius.gui.widgets.inv_spinctrl import InvSpinCtrl, InvFloatSpinCtrl |
62 | from invesalius.gui.widgets import clut_imagedata | 64 | from invesalius.gui.widgets import clut_imagedata |
63 | from invesalius.gui.widgets.clut_imagedata import CLUTImageDataWidget, EVT_CLUT_NODE_CHANGED | 65 | from invesalius.gui.widgets.clut_imagedata import CLUTImageDataWidget, EVT_CLUT_NODE_CHANGED |
64 | import numpy as np | 66 | import numpy as np |
67 | +from numpy.core.umath_tests import inner1d | ||
65 | 68 | ||
66 | from invesalius import inv_paths | 69 | from invesalius import inv_paths |
67 | 70 | ||
@@ -887,6 +890,35 @@ def ShowNavigationTrackerWarning(trck_id, lib_mode): | @@ -887,6 +890,35 @@ def ShowNavigationTrackerWarning(trck_id, lib_mode): | ||
887 | dlg.ShowModal() | 890 | dlg.ShowModal() |
888 | dlg.Destroy() | 891 | dlg.Destroy() |
889 | 892 | ||
893 | +def ICPcorregistration(fre): | ||
894 | + msg = _("The fiducial registration error is: ") + str(round(fre, 2)) + '\n\n' + \ | ||
895 | + _("Would you like to improve accuracy?") | ||
896 | + if sys.platform == 'darwin': | ||
897 | + dlg = wx.MessageDialog(None, "", msg, | ||
898 | + wx.YES_NO) | ||
899 | + else: | ||
900 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3", | ||
901 | + wx.YES_NO) | ||
902 | + | ||
903 | + if dlg.ShowModal() == wx.ID_YES: | ||
904 | + flag = True | ||
905 | + else: | ||
906 | + flag = False | ||
907 | + | ||
908 | + dlg.Destroy() | ||
909 | + return flag | ||
910 | + | ||
911 | +def ReportICPerror(prev_error, final_error): | ||
912 | + msg = _("Error after refine: ") + str(round(final_error, 2)) + ' mm' + '\n\n' + \ | ||
913 | + _("Previous error: ") + str(round(prev_error, 2)) + ' mm' | ||
914 | + if sys.platform == 'darwin': | ||
915 | + dlg = wx.MessageDialog(None, "", msg, | ||
916 | + wx.OK) | ||
917 | + else: | ||
918 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3", | ||
919 | + wx.OK) | ||
920 | + dlg.ShowModal() | ||
921 | + dlg.Destroy() | ||
890 | 922 | ||
891 | def ShowEnterMarkerID(default): | 923 | def ShowEnterMarkerID(default): |
892 | msg = _("Edit marker ID") | 924 | msg = _("Edit marker ID") |
@@ -1155,7 +1187,7 @@ def ShowAboutDialog(parent): | @@ -1155,7 +1187,7 @@ def ShowAboutDialog(parent): | ||
1155 | 1187 | ||
1156 | info.SetWebSite("https://www.cti.gov.br/invesalius") | 1188 | info.SetWebSite("https://www.cti.gov.br/invesalius") |
1157 | info.SetIcon(icon) | 1189 | info.SetIcon(icon) |
1158 | - | 1190 | + |
1159 | info.License = _("GNU GPL (General Public License) version 2") | 1191 | info.License = _("GNU GPL (General Public License) version 2") |
1160 | 1192 | ||
1161 | info.Developers = [u"Paulo Henrique Junqueira Amorim", | 1193 | info.Developers = [u"Paulo Henrique Junqueira Amorim", |
@@ -1343,7 +1375,7 @@ class NewSurfaceDialog(wx.Dialog): | @@ -1343,7 +1375,7 @@ class NewSurfaceDialog(wx.Dialog): | ||
1343 | def ExportPicture(type_=""): | 1375 | def ExportPicture(type_=""): |
1344 | import invesalius.constants as const | 1376 | import invesalius.constants as const |
1345 | import invesalius.project as proj | 1377 | import invesalius.project as proj |
1346 | - | 1378 | + |
1347 | INDEX_TO_EXTENSION = {0: "bmp", 1: "jpg", 2: "png", 3: "ps", 4:"povray", 5:"tiff"} | 1379 | INDEX_TO_EXTENSION = {0: "bmp", 1: "jpg", 2: "png", 3: "ps", 4:"povray", 5:"tiff"} |
1348 | WILDCARD_SAVE_PICTURE = _("BMP image")+" (*.bmp)|*.bmp|"+\ | 1380 | WILDCARD_SAVE_PICTURE = _("BMP image")+" (*.bmp)|*.bmp|"+\ |
1349 | _("JPG image")+" (*.jpg)|*.jpg|"+\ | 1381 | _("JPG image")+" (*.jpg)|*.jpg|"+\ |
@@ -1513,7 +1545,7 @@ class SurfaceCreationOptionsPanel(wx.Panel): | @@ -1513,7 +1545,7 @@ class SurfaceCreationOptionsPanel(wx.Panel): | ||
1513 | project = prj.Project() | 1545 | project = prj.Project() |
1514 | index_list = project.mask_dict.keys() | 1546 | index_list = project.mask_dict.keys() |
1515 | self.mask_list = [project.mask_dict[index].name for index in sorted(index_list)] | 1547 | self.mask_list = [project.mask_dict[index].name for index in sorted(index_list)] |
1516 | - | 1548 | + |
1517 | active_mask = slc.Slice().current_mask.index | 1549 | active_mask = slc.Slice().current_mask.index |
1518 | #active_mask = len(self.mask_list)-1 | 1550 | #active_mask = len(self.mask_list)-1 |
1519 | 1551 | ||
@@ -2093,17 +2125,17 @@ class ImportBitmapParameters(wx.Dialog): | @@ -2093,17 +2125,17 @@ class ImportBitmapParameters(wx.Dialog): | ||
2093 | 2125 | ||
2094 | 2126 | ||
2095 | def _init_gui(self): | 2127 | def _init_gui(self): |
2096 | - | 2128 | + |
2097 | import invesalius.project as prj | 2129 | import invesalius.project as prj |
2098 | - | 2130 | + |
2099 | p = wx.Panel(self, -1, style = wx.TAB_TRAVERSAL | 2131 | p = wx.Panel(self, -1, style = wx.TAB_TRAVERSAL |
2100 | | wx.CLIP_CHILDREN | 2132 | | wx.CLIP_CHILDREN |
2101 | | wx.FULL_REPAINT_ON_RESIZE) | 2133 | | wx.FULL_REPAINT_ON_RESIZE) |
2102 | - | 2134 | + |
2103 | gbs_principal = self.gbs = wx.GridBagSizer(4,1) | 2135 | gbs_principal = self.gbs = wx.GridBagSizer(4,1) |
2104 | 2136 | ||
2105 | gbs = self.gbs = wx.GridBagSizer(5, 2) | 2137 | gbs = self.gbs = wx.GridBagSizer(5, 2) |
2106 | - | 2138 | + |
2107 | flag_labels = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | 2139 | flag_labels = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL |
2108 | 2140 | ||
2109 | stx_name = wx.StaticText(p, -1, _(u"Project name:")) | 2141 | stx_name = wx.StaticText(p, -1, _(u"Project name:")) |
@@ -2134,7 +2166,7 @@ class ImportBitmapParameters(wx.Dialog): | @@ -2134,7 +2166,7 @@ class ImportBitmapParameters(wx.Dialog): | ||
2134 | 2166 | ||
2135 | #--- spacing -------------- | 2167 | #--- spacing -------------- |
2136 | gbs_spacing = wx.GridBagSizer(2, 6) | 2168 | gbs_spacing = wx.GridBagSizer(2, 6) |
2137 | - | 2169 | + |
2138 | stx_spacing_x = stx_spacing_x = wx.StaticText(p, -1, _(u"X:")) | 2170 | stx_spacing_x = stx_spacing_x = wx.StaticText(p, -1, _(u"X:")) |
2139 | fsp_spacing_x = self.fsp_spacing_x = InvFloatSpinCtrl(p, -1, min_value=0, max_value=1000000000, | 2171 | fsp_spacing_x = self.fsp_spacing_x = InvFloatSpinCtrl(p, -1, min_value=0, max_value=1000000000, |
2140 | increment=0.25, value=1.0, digits=8) | 2172 | increment=0.25, value=1.0, digits=8) |
@@ -2151,7 +2183,7 @@ class ImportBitmapParameters(wx.Dialog): | @@ -2151,7 +2183,7 @@ class ImportBitmapParameters(wx.Dialog): | ||
2151 | 2183 | ||
2152 | try: | 2184 | try: |
2153 | proj = prj.Project() | 2185 | proj = prj.Project() |
2154 | - | 2186 | + |
2155 | sx = proj.spacing[0] | 2187 | sx = proj.spacing[0] |
2156 | sy = proj.spacing[1] | 2188 | sy = proj.spacing[1] |
2157 | sz = proj.spacing[2] | 2189 | sz = proj.spacing[2] |
@@ -2174,7 +2206,7 @@ class ImportBitmapParameters(wx.Dialog): | @@ -2174,7 +2206,7 @@ class ImportBitmapParameters(wx.Dialog): | ||
2174 | 2206 | ||
2175 | #----- buttons ------------------------ | 2207 | #----- buttons ------------------------ |
2176 | gbs_button = wx.GridBagSizer(2, 4) | 2208 | gbs_button = wx.GridBagSizer(2, 4) |
2177 | - | 2209 | + |
2178 | btn_ok = self.btn_ok= wx.Button(p, wx.ID_OK) | 2210 | btn_ok = self.btn_ok= wx.Button(p, wx.ID_OK) |
2179 | btn_ok.SetDefault() | 2211 | btn_ok.SetDefault() |
2180 | 2212 | ||
@@ -2197,14 +2229,14 @@ class ImportBitmapParameters(wx.Dialog): | @@ -2197,14 +2229,14 @@ class ImportBitmapParameters(wx.Dialog): | ||
2197 | 2229 | ||
2198 | box = wx.BoxSizer() | 2230 | box = wx.BoxSizer() |
2199 | box.Add(gbs_principal, 1, wx.ALL|wx.EXPAND, 10) | 2231 | box.Add(gbs_principal, 1, wx.ALL|wx.EXPAND, 10) |
2200 | - | 2232 | + |
2201 | p.SetSizer(box) | 2233 | p.SetSizer(box) |
2202 | box.Fit(self) | 2234 | box.Fit(self) |
2203 | self.Layout() | 2235 | self.Layout() |
2204 | 2236 | ||
2205 | def bind_evts(self): | 2237 | def bind_evts(self): |
2206 | self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) | 2238 | self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) |
2207 | - | 2239 | + |
2208 | def SetInterval(self, v): | 2240 | def SetInterval(self, v): |
2209 | self.interval = v | 2241 | self.interval = v |
2210 | 2242 | ||
@@ -2228,10 +2260,10 @@ class ImportBitmapParameters(wx.Dialog): | @@ -2228,10 +2260,10 @@ class ImportBitmapParameters(wx.Dialog): | ||
2228 | 2260 | ||
2229 | 2261 | ||
2230 | def BitmapNotSameSize(): | 2262 | def BitmapNotSameSize(): |
2231 | - | 2263 | + |
2232 | dlg = wx.MessageDialog(None,_("All bitmaps files must be the same \n width and height size."), 'Error',\ | 2264 | dlg = wx.MessageDialog(None,_("All bitmaps files must be the same \n width and height size."), 'Error',\ |
2233 | wx.OK | wx.ICON_ERROR) | 2265 | wx.OK | wx.ICON_ERROR) |
2234 | - | 2266 | + |
2235 | dlg.ShowModal() | 2267 | dlg.ShowModal() |
2236 | dlg.Destroy() | 2268 | dlg.Destroy() |
2237 | 2269 | ||
@@ -2908,7 +2940,7 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | @@ -2908,7 +2940,7 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | ||
2908 | 2940 | ||
2909 | 2941 | ||
2910 | class CropOptionsDialog(wx.Dialog): | 2942 | class CropOptionsDialog(wx.Dialog): |
2911 | - | 2943 | + |
2912 | def __init__(self, config, ID=-1, title=_(u"Crop mask"), style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP): | 2944 | def __init__(self, config, ID=-1, title=_(u"Crop mask"), style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP): |
2913 | self.config = config | 2945 | self.config = config |
2914 | wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), ID, title=title, style=style) | 2946 | wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), ID, title=title, style=style) |
@@ -3316,7 +3348,7 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3316,7 +3348,7 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
3316 | main_sizer = wx.BoxSizer(wx.VERTICAL) | 3348 | main_sizer = wx.BoxSizer(wx.VERTICAL) |
3317 | main_sizer.Add(self.interactor, 0, wx.EXPAND) | 3349 | main_sizer.Add(self.interactor, 0, wx.EXPAND) |
3318 | main_sizer.Add(group_sizer, 0, | 3350 | main_sizer.Add(group_sizer, 0, |
3319 | - wx.EXPAND|wx.GROW|wx.LEFT|wx.TOP|wx.RIGHT|wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 10) | 3351 | + wx.EXPAND|wx.GROW|wx.LEFT|wx.TOP|wx.RIGHT|wx.BOTTOM, 10) |
3320 | 3352 | ||
3321 | self.SetSizer(main_sizer) | 3353 | self.SetSizer(main_sizer) |
3322 | main_sizer.Fit(self) | 3354 | main_sizer.Fit(self) |
@@ -3493,6 +3525,363 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3493,6 +3525,363 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
3493 | def GetValue(self): | 3525 | def GetValue(self): |
3494 | return self.obj_fiducials, self.obj_orients, self.obj_ref_id, self.obj_name, self.polydata | 3526 | return self.obj_fiducials, self.obj_orients, self.obj_ref_id, self.obj_name, self.polydata |
3495 | 3527 | ||
3528 | +class ICPCorregistrationDialog(wx.Dialog): | ||
3529 | + | ||
3530 | + def __init__(self, nav_prop): | ||
3531 | + import invesalius.project as prj | ||
3532 | + | ||
3533 | + self.__bind_events() | ||
3534 | + | ||
3535 | + self.tracker_id = nav_prop[0] | ||
3536 | + self.trk_init = nav_prop[1] | ||
3537 | + self.obj_ref_id = 2 | ||
3538 | + self.obj_name = None | ||
3539 | + self.obj_actor = None | ||
3540 | + self.polydata = None | ||
3541 | + self.m_icp = None | ||
3542 | + self.initial_focus = None | ||
3543 | + self.prev_error = None | ||
3544 | + self.final_error = None | ||
3545 | + self.icp_mode = 0 | ||
3546 | + self.staticballs = [] | ||
3547 | + self.point_coord = [] | ||
3548 | + self.transformed_points = [] | ||
3549 | + | ||
3550 | + self.obj_fiducials = np.full([5, 3], np.nan) | ||
3551 | + self.obj_orients = np.full([5, 3], np.nan) | ||
3552 | + | ||
3553 | + wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, _(u"Refine Corregistration"), size=(380, 440), | ||
3554 | + style=wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP) | ||
3555 | + | ||
3556 | + self.proj = prj.Project() | ||
3557 | + | ||
3558 | + self._init_gui() | ||
3559 | + | ||
3560 | + def __bind_events(self): | ||
3561 | + Publisher.subscribe(self.UpdateCurrentCoord, 'Set cross focal point') | ||
3562 | + | ||
3563 | + def UpdateCurrentCoord(self, position): | ||
3564 | + self.current_coord = position[:] | ||
3565 | + | ||
3566 | + def _init_gui(self): | ||
3567 | + self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize()) | ||
3568 | + self.interactor.Enable(1) | ||
3569 | + self.ren = vtk.vtkRenderer() | ||
3570 | + self.interactor.GetRenderWindow().AddRenderer(self.ren) | ||
3571 | + | ||
3572 | + self.timer = wx.Timer(self) | ||
3573 | + self.Bind(wx.EVT_TIMER, self.OnUpdate, self.timer) | ||
3574 | + | ||
3575 | + txt_surface = wx.StaticText(self, -1, _('Select the surface:')) | ||
3576 | + txt_mode = wx.StaticText(self, -1, _('Registration mode:')) | ||
3577 | + | ||
3578 | + combo_surface_name = wx.ComboBox(self, -1, size=(210, 23), | ||
3579 | + style=wx.CB_DROPDOWN | wx.CB_READONLY) | ||
3580 | + # combo_surface_name.SetSelection(0) | ||
3581 | + if sys.platform != 'win32': | ||
3582 | + combo_surface_name.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | ||
3583 | + combo_surface_name.Bind(wx.EVT_COMBOBOX, self.OnComboName) | ||
3584 | + for n in range(len(self.proj.surface_dict)): | ||
3585 | + combo_surface_name.Insert(str(self.proj.surface_dict[n].name), n) | ||
3586 | + | ||
3587 | + self.combo_surface_name = combo_surface_name | ||
3588 | + | ||
3589 | + init_surface = 0 | ||
3590 | + combo_surface_name.SetSelection(init_surface) | ||
3591 | + self.surface = self.proj.surface_dict[init_surface].polydata | ||
3592 | + self.LoadActor() | ||
3593 | + | ||
3594 | + tooltip = wx.ToolTip(_("Choose the registration mode:")) | ||
3595 | + choice_icp_method = wx.ComboBox(self, -1, "", size=(100, 23), | ||
3596 | + choices=([_("Affine"), _("Similarity"), _("RigidBody")]), | ||
3597 | + style=wx.CB_DROPDOWN|wx.CB_READONLY) | ||
3598 | + choice_icp_method.SetSelection(0) | ||
3599 | + choice_icp_method.SetToolTip(tooltip) | ||
3600 | + choice_icp_method.Bind(wx.EVT_COMBOBOX, self.OnChoiceICPMethod) | ||
3601 | + | ||
3602 | + # Buttons to acquire and remove points | ||
3603 | + create_point = wx.Button(self, -1, label=_('Create point')) | ||
3604 | + create_point.Bind(wx.EVT_BUTTON, self.OnCreatePoint) | ||
3605 | + | ||
3606 | + cont_point = wx.ToggleButton(self, -1, label=_('Continuous acquisition')) | ||
3607 | + cont_point.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnContinuousAcquisition, btn=cont_point)) | ||
3608 | + self.cont_point = cont_point | ||
3609 | + | ||
3610 | + btn_reset = wx.Button(self, -1, label=_('Remove points')) | ||
3611 | + btn_reset.Bind(wx.EVT_BUTTON, self.OnReset) | ||
3612 | + | ||
3613 | + btn_apply_icp = wx.Button(self, -1, label=_('Apply registration')) | ||
3614 | + btn_apply_icp.Bind(wx.EVT_BUTTON, self.OnICP) | ||
3615 | + | ||
3616 | + # Buttons to finish or cancel object registration | ||
3617 | + tooltip = wx.ToolTip(_(u"Refine done")) | ||
3618 | + btn_ok = wx.Button(self, wx.ID_OK, _(u"Done")) | ||
3619 | + btn_ok.SetToolTip(tooltip) | ||
3620 | + | ||
3621 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | ||
3622 | + btn_cancel.SetHelpText("") | ||
3623 | + | ||
3624 | + top_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=50, vgap=5) | ||
3625 | + top_sizer.AddMany([txt_surface, txt_mode, | ||
3626 | + combo_surface_name, choice_icp_method]) | ||
3627 | + | ||
3628 | + btn_acqui_sizer = wx.FlexGridSizer(rows=1, cols=3, hgap=15, vgap=15) | ||
3629 | + btn_acqui_sizer.AddMany([create_point, cont_point, btn_reset]) | ||
3630 | + | ||
3631 | + btn_ok_sizer = wx.FlexGridSizer(rows=1, cols=3, hgap=20, vgap=20) | ||
3632 | + btn_ok_sizer.AddMany([btn_apply_icp, btn_ok, btn_cancel]) | ||
3633 | + | ||
3634 | + btn_sizer = wx.FlexGridSizer(rows=2, cols=1, hgap=50, vgap=20) | ||
3635 | + btn_sizer.AddMany([(btn_acqui_sizer, 1, wx.ALIGN_CENTER_HORIZONTAL), | ||
3636 | + (btn_ok_sizer, 1, wx.ALIGN_RIGHT)]) | ||
3637 | + | ||
3638 | + self.progress = wx.Gauge(self, -1) | ||
3639 | + | ||
3640 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
3641 | + main_sizer.Add(top_sizer, 0, wx.LEFT|wx.TOP|wx.BOTTOM, 10) | ||
3642 | + main_sizer.Add(self.interactor, 0, wx.EXPAND) | ||
3643 | + main_sizer.Add(btn_sizer, 0, | ||
3644 | + wx.EXPAND|wx.GROW|wx.LEFT|wx.TOP|wx.BOTTOM, 10) | ||
3645 | + main_sizer.Add(self.progress, 0, wx.EXPAND | wx.ALL, 5) | ||
3646 | + | ||
3647 | + self.SetSizer(main_sizer) | ||
3648 | + main_sizer.Fit(self) | ||
3649 | + | ||
3650 | + def LoadActor(self): | ||
3651 | + ''' | ||
3652 | + Load the selected actor from the project (self.surface) into the scene | ||
3653 | + :return: | ||
3654 | + ''' | ||
3655 | + mapper = vtk.vtkPolyDataMapper() | ||
3656 | + mapper.SetInputData(self.surface) | ||
3657 | + mapper.ScalarVisibilityOff() | ||
3658 | + #mapper.ImmediateModeRenderingOn() | ||
3659 | + | ||
3660 | + obj_actor = vtk.vtkActor() | ||
3661 | + obj_actor.SetMapper(mapper) | ||
3662 | + self.obj_actor = obj_actor | ||
3663 | + | ||
3664 | + self.ren.AddActor(obj_actor) | ||
3665 | + self.ren.ResetCamera() | ||
3666 | + self.interactor.Render() | ||
3667 | + | ||
3668 | + def RemoveActor(self): | ||
3669 | + #self.ren.RemoveActor(self.obj_actor) | ||
3670 | + self.ren.RemoveAllViewProps() | ||
3671 | + self.point_coord = [] | ||
3672 | + self.transformed_points = [] | ||
3673 | + self.m_icp = None | ||
3674 | + self.SetProgress(0) | ||
3675 | + self.ren.ResetCamera() | ||
3676 | + self.interactor.Render() | ||
3677 | + | ||
3678 | + def AddMarker(self, size, colour, coord): | ||
3679 | + """ | ||
3680 | + Points are rendered into the scene. These points give visual information about the registration. | ||
3681 | + :param size: value of the marker size | ||
3682 | + :type size: int | ||
3683 | + :param colour: RGB Color Code for the marker | ||
3684 | + :type colour: tuple (int(R),int(G),int(B)) | ||
3685 | + :param coord: x, y, z of the marker | ||
3686 | + :type coord: np.ndarray | ||
3687 | + """ | ||
3688 | + | ||
3689 | + x, y, z = coord[0], -coord[1], coord[2] | ||
3690 | + | ||
3691 | + ball_ref = vtk.vtkSphereSource() | ||
3692 | + ball_ref.SetRadius(size) | ||
3693 | + ball_ref.SetCenter(x, y, z) | ||
3694 | + | ||
3695 | + mapper = vtk.vtkPolyDataMapper() | ||
3696 | + mapper.SetInputConnection(ball_ref.GetOutputPort()) | ||
3697 | + | ||
3698 | + prop = vtk.vtkProperty() | ||
3699 | + prop.SetColor(colour[0:3]) | ||
3700 | + | ||
3701 | + #adding a new actor for the present ball | ||
3702 | + sphere_actor = vtk.vtkActor() | ||
3703 | + | ||
3704 | + sphere_actor.SetMapper(mapper) | ||
3705 | + sphere_actor.SetProperty(prop) | ||
3706 | + | ||
3707 | + self.ren.AddActor(sphere_actor) | ||
3708 | + self.point_coord.append([x, y, z]) | ||
3709 | + | ||
3710 | + self.Refresh() | ||
3711 | + | ||
3712 | + def SetProgress(self, progress): | ||
3713 | + self.progress.SetValue(progress * 100) | ||
3714 | + self.Refresh() | ||
3715 | + | ||
3716 | + def vtkmatrix_to_numpy(self, matrix): | ||
3717 | + """ | ||
3718 | + Copies the elements of a vtkMatrix4x4 into a numpy array. | ||
3719 | + | ||
3720 | + :param matrix: The matrix to be copied into an array. | ||
3721 | + :type matrix: vtk.vtkMatrix4x4 | ||
3722 | + :rtype: numpy.ndarray | ||
3723 | + """ | ||
3724 | + m = np.ones((4, 4)) | ||
3725 | + for i in range(4): | ||
3726 | + for j in range(4): | ||
3727 | + m[i, j] = matrix.GetElement(i, j) | ||
3728 | + return m | ||
3729 | + | ||
3730 | + def SetCameraVolume(self, position): | ||
3731 | + """ | ||
3732 | + Positioning of the camera based on the acquired point | ||
3733 | + :param position: x, y, z of the last acquired point | ||
3734 | + :return: | ||
3735 | + """ | ||
3736 | + cam_focus = np.array([position[0], -position[1], position[2]]) | ||
3737 | + cam = self.ren.GetActiveCamera() | ||
3738 | + | ||
3739 | + if self.initial_focus is None: | ||
3740 | + self.initial_focus = np.array(cam.GetFocalPoint()) | ||
3741 | + | ||
3742 | + cam_pos0 = np.array(cam.GetPosition()) | ||
3743 | + cam_focus0 = np.array(cam.GetFocalPoint()) | ||
3744 | + v0 = cam_pos0 - cam_focus0 | ||
3745 | + v0n = np.sqrt(inner1d(v0, v0)) | ||
3746 | + | ||
3747 | + v1 = (cam_focus - self.initial_focus) | ||
3748 | + | ||
3749 | + v1n = np.sqrt(inner1d(v1, v1)) | ||
3750 | + if not v1n: | ||
3751 | + v1n = 1.0 | ||
3752 | + cam_pos = (v1/v1n)*v0n + cam_focus | ||
3753 | + | ||
3754 | + cam.SetFocalPoint(cam_focus) | ||
3755 | + cam.SetPosition(cam_pos) | ||
3756 | + self.Refresh() | ||
3757 | + | ||
3758 | + def ErrorEstimation(self, surface, points): | ||
3759 | + """ | ||
3760 | + Estimation of the average squared distance between the cloud of points to the closest mesh | ||
3761 | + :param surface: Surface polydata of the scene | ||
3762 | + :type surface: vtk.polydata | ||
3763 | + :param points: Cloud of points | ||
3764 | + :type points: np.ndarray | ||
3765 | + :return: mean distance | ||
3766 | + """ | ||
3767 | + cell_locator = vtk.vtkCellLocator() | ||
3768 | + cell_locator.SetDataSet(surface) | ||
3769 | + cell_locator.BuildLocator() | ||
3770 | + | ||
3771 | + cellId = vtk.mutable(0) | ||
3772 | + c = [0.0, 0.0, 0.0] | ||
3773 | + subId = vtk.mutable(0) | ||
3774 | + d = vtk.mutable(0.0) | ||
3775 | + error = [] | ||
3776 | + for i in range(len(points)): | ||
3777 | + cell_locator.FindClosestPoint(points[i], c, cellId, subId, d) | ||
3778 | + error.append(np.sqrt(float(d))) | ||
3779 | + | ||
3780 | + return np.mean(error) | ||
3781 | + | ||
3782 | + def OnComboName(self, evt): | ||
3783 | + surface_name = evt.GetString() | ||
3784 | + surface_index = evt.GetSelection() | ||
3785 | + self.surface = self.proj.surface_dict[surface_index].polydata | ||
3786 | + if self.obj_actor: | ||
3787 | + self.RemoveActor() | ||
3788 | + self.LoadActor() | ||
3789 | + | ||
3790 | + def OnChoiceICPMethod(self, evt): | ||
3791 | + self.icp_mode = evt.GetSelection() | ||
3792 | + | ||
3793 | + def OnContinuousAcquisition(self, evt=None, btn=None): | ||
3794 | + value = btn.GetValue() | ||
3795 | + if value: | ||
3796 | + self.timer.Start(500) | ||
3797 | + else: | ||
3798 | + self.timer.Stop() | ||
3799 | + | ||
3800 | + def OnUpdate(self, evt): | ||
3801 | + self.AddMarker(3, (1, 0, 0), self.current_coord[:3]) | ||
3802 | + self.SetCameraVolume(self.current_coord[:3]) | ||
3803 | + | ||
3804 | + def OnCreatePoint(self, evt): | ||
3805 | + self.AddMarker(3,(1,0,0),self.current_coord[:3]) | ||
3806 | + self.SetCameraVolume(self.current_coord[:3]) | ||
3807 | + | ||
3808 | + def OnReset(self, evt): | ||
3809 | + self.RemoveActor() | ||
3810 | + self.LoadActor() | ||
3811 | + | ||
3812 | + def OnICP(self, evt): | ||
3813 | + self.SetProgress(0.3) | ||
3814 | + if self.cont_point: | ||
3815 | + self.cont_point.SetValue(False) | ||
3816 | + self.OnContinuousAcquisition(evt=None, btn=self.cont_point) | ||
3817 | + sourcePoints = np.array(self.point_coord) | ||
3818 | + sourcePoints_vtk = vtk.vtkPoints() | ||
3819 | + | ||
3820 | + for i in range(len(sourcePoints)): | ||
3821 | + id0 = sourcePoints_vtk.InsertNextPoint(sourcePoints[i]) | ||
3822 | + | ||
3823 | + source = vtk.vtkPolyData() | ||
3824 | + source.SetPoints(sourcePoints_vtk) | ||
3825 | + | ||
3826 | + icp = vtk.vtkIterativeClosestPointTransform() | ||
3827 | + icp.SetSource(source) | ||
3828 | + icp.SetTarget(self.surface) | ||
3829 | + | ||
3830 | + if self.icp_mode == 0: | ||
3831 | + print("Affine mode") | ||
3832 | + icp.GetLandmarkTransform().SetModeToAffine() | ||
3833 | + elif self.icp_mode == 1: | ||
3834 | + print("Similarity mode") | ||
3835 | + icp.GetLandmarkTransform().SetModeToSimilarity() | ||
3836 | + elif self.icp_mode == 2: | ||
3837 | + print("Rigid mode") | ||
3838 | + icp.GetLandmarkTransform().SetModeToRigidBody() | ||
3839 | + | ||
3840 | + #icp.DebugOn() | ||
3841 | + icp.SetMaximumNumberOfIterations(1000) | ||
3842 | + | ||
3843 | + icp.Modified() | ||
3844 | + | ||
3845 | + icp.Update() | ||
3846 | + | ||
3847 | + self.m_icp = self.vtkmatrix_to_numpy(icp.GetMatrix()) | ||
3848 | + | ||
3849 | + icpTransformFilter = vtk.vtkTransformPolyDataFilter() | ||
3850 | + icpTransformFilter.SetInputData(source) | ||
3851 | + | ||
3852 | + icpTransformFilter.SetTransform(icp) | ||
3853 | + icpTransformFilter.Update() | ||
3854 | + | ||
3855 | + transformedSource = icpTransformFilter.GetOutput() | ||
3856 | + | ||
3857 | + self.SetProgress(1) | ||
3858 | + | ||
3859 | + for i in range(transformedSource.GetNumberOfPoints()): | ||
3860 | + p = [0, 0, 0] | ||
3861 | + transformedSource.GetPoint(i, p) | ||
3862 | + self.transformed_points.append(p) | ||
3863 | + point = vtk.vtkSphereSource() | ||
3864 | + point.SetCenter(p) | ||
3865 | + point.SetRadius(3) | ||
3866 | + point.SetPhiResolution(3) | ||
3867 | + point.SetThetaResolution(3) | ||
3868 | + | ||
3869 | + mapper = vtk.vtkPolyDataMapper() | ||
3870 | + mapper.SetInputConnection(point.GetOutputPort()) | ||
3871 | + | ||
3872 | + actor = vtk.vtkActor() | ||
3873 | + actor.SetMapper(mapper) | ||
3874 | + actor.GetProperty().SetColor((0,1,0)) | ||
3875 | + | ||
3876 | + self.ren.AddActor(actor) | ||
3877 | + | ||
3878 | + self.prev_error = self.ErrorEstimation(self.surface, sourcePoints) | ||
3879 | + self.final_error = self.ErrorEstimation(self.surface, self.transformed_points) | ||
3880 | + | ||
3881 | + self.Refresh() | ||
3882 | + | ||
3883 | + def GetValue(self): | ||
3884 | + return self.m_icp, self.point_coord, self.transformed_points, self.prev_error, self.final_error | ||
3496 | 3885 | ||
3497 | class SurfaceProgressWindow(object): | 3886 | class SurfaceProgressWindow(object): |
3498 | def __init__(self): | 3887 | def __init__(self): |
@@ -3752,20 +4141,20 @@ class SetNDIconfigs(wx.Dialog): | @@ -3752,20 +4141,20 @@ class SetNDIconfigs(wx.Dialog): | ||
3752 | wildcard="Rom files (*.rom)|*.rom", message="Select probe's rom file") | 4141 | wildcard="Rom files (*.rom)|*.rom", message="Select probe's rom file") |
3753 | row_probe = wx.BoxSizer(wx.VERTICAL) | 4142 | row_probe = wx.BoxSizer(wx.VERTICAL) |
3754 | row_probe.Add(wx.StaticText(self, wx.ID_ANY, "Set probe's rom file"), 0, wx.TOP|wx.RIGHT, 5) | 4143 | row_probe.Add(wx.StaticText(self, wx.ID_ANY, "Set probe's rom file"), 0, wx.TOP|wx.RIGHT, 5) |
3755 | - row_probe.Add(self.dir_probe, 0, wx.EXPAND|wx.ALIGN_CENTER) | 4144 | + row_probe.Add(self.dir_probe, 0, wx.ALIGN_CENTER) |
3756 | 4145 | ||
3757 | self.dir_ref = wx.FilePickerCtrl(self, path=last_ndi_ref_marker, style=wx.FLP_USE_TEXTCTRL|wx.FLP_SMALL, | 4146 | self.dir_ref = wx.FilePickerCtrl(self, path=last_ndi_ref_marker, style=wx.FLP_USE_TEXTCTRL|wx.FLP_SMALL, |
3758 | wildcard="Rom files (*.rom)|*.rom", message="Select reference's rom file") | 4147 | wildcard="Rom files (*.rom)|*.rom", message="Select reference's rom file") |
3759 | row_ref = wx.BoxSizer(wx.VERTICAL) | 4148 | row_ref = wx.BoxSizer(wx.VERTICAL) |
3760 | row_ref.Add(wx.StaticText(self, wx.ID_ANY, "Set reference's rom file"), 0, wx.TOP | wx.RIGHT, 5) | 4149 | row_ref.Add(wx.StaticText(self, wx.ID_ANY, "Set reference's rom file"), 0, wx.TOP | wx.RIGHT, 5) |
3761 | - row_ref.Add(self.dir_ref, 0, wx.EXPAND|wx.ALIGN_CENTER) | 4150 | + row_ref.Add(self.dir_ref, 0, wx.ALIGN_CENTER) |
3762 | 4151 | ||
3763 | self.dir_obj = wx.FilePickerCtrl(self, path=last_ndi_obj_marker, style=wx.FLP_USE_TEXTCTRL|wx.FLP_SMALL, | 4152 | self.dir_obj = wx.FilePickerCtrl(self, path=last_ndi_obj_marker, style=wx.FLP_USE_TEXTCTRL|wx.FLP_SMALL, |
3764 | wildcard="Rom files (*.rom)|*.rom", message="Select object's rom file") | 4153 | wildcard="Rom files (*.rom)|*.rom", message="Select object's rom file") |
3765 | #self.dir_probe.Bind(wx.EVT_FILEPICKER_CHANGED, self.Selected) | 4154 | #self.dir_probe.Bind(wx.EVT_FILEPICKER_CHANGED, self.Selected) |
3766 | row_obj = wx.BoxSizer(wx.VERTICAL) | 4155 | row_obj = wx.BoxSizer(wx.VERTICAL) |
3767 | row_obj.Add(wx.StaticText(self, wx.ID_ANY, "Set object's rom file"), 0, wx.TOP|wx.RIGHT, 5) | 4156 | row_obj.Add(wx.StaticText(self, wx.ID_ANY, "Set object's rom file"), 0, wx.TOP|wx.RIGHT, 5) |
3768 | - row_obj.Add(self.dir_obj, 0, wx.EXPAND|wx.ALIGN_CENTER) | 4157 | + row_obj.Add(self.dir_obj, 0, wx.ALIGN_CENTER) |
3769 | 4158 | ||
3770 | btn_ok = wx.Button(self, wx.ID_OK) | 4159 | btn_ok = wx.Button(self, wx.ID_OK) |
3771 | btn_ok.SetHelpText("") | 4160 | btn_ok.SetHelpText("") |
invesalius/gui/task_navigator.py
@@ -309,17 +309,24 @@ class NeuronavigationPanel(wx.Panel): | @@ -309,17 +309,24 @@ class NeuronavigationPanel(wx.Panel): | ||
309 | 309 | ||
310 | # Initialize global variables | 310 | # Initialize global variables |
311 | self.fiducials = np.full([6, 3], np.nan) | 311 | self.fiducials = np.full([6, 3], np.nan) |
312 | + self.fiducials_raw = np.zeros((6, 6)) | ||
312 | self.correg = None | 313 | self.correg = None |
313 | self.current_coord = 0, 0, 0 | 314 | self.current_coord = 0, 0, 0 |
314 | self.trk_init = None | 315 | self.trk_init = None |
316 | + self.nav_status = False | ||
315 | self.trigger = None | 317 | self.trigger = None |
316 | self.trigger_state = False | 318 | self.trigger_state = False |
317 | self.obj_reg = None | 319 | self.obj_reg = None |
318 | self.obj_reg_status = False | 320 | self.obj_reg_status = False |
319 | self.track_obj = False | 321 | self.track_obj = False |
322 | + self.m_icp = None | ||
323 | + self.fre = None | ||
324 | + self.icp_fre = None | ||
325 | + self.icp = False | ||
320 | self.event = threading.Event() | 326 | self.event = threading.Event() |
321 | 327 | ||
322 | self.coord_queue = QueueCustom(maxsize=1) | 328 | self.coord_queue = QueueCustom(maxsize=1) |
329 | + self.icp_queue = QueueCustom(maxsize=1) | ||
323 | # self.visualization_queue = QueueCustom(maxsize=1) | 330 | # self.visualization_queue = QueueCustom(maxsize=1) |
324 | self.trigger_queue = QueueCustom(maxsize=1) | 331 | self.trigger_queue = QueueCustom(maxsize=1) |
325 | self.coord_tracts_queue = QueueCustom(maxsize=1) | 332 | self.coord_tracts_queue = QueueCustom(maxsize=1) |
@@ -385,6 +392,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -385,6 +392,7 @@ class NeuronavigationPanel(wx.Panel): | ||
385 | 392 | ||
386 | # TODO: Find a better allignment between FRE, text and navigate button | 393 | # TODO: Find a better allignment between FRE, text and navigate button |
387 | txt_fre = wx.StaticText(self, -1, _('FRE:')) | 394 | txt_fre = wx.StaticText(self, -1, _('FRE:')) |
395 | + txt_icp = wx.StaticText(self, -1, _('Refine:')) | ||
388 | 396 | ||
389 | # Fiducial registration error text box | 397 | # Fiducial registration error text box |
390 | tooltip = wx.ToolTip(_("Fiducial registration error")) | 398 | tooltip = wx.ToolTip(_("Fiducial registration error")) |
@@ -401,6 +409,14 @@ class NeuronavigationPanel(wx.Panel): | @@ -401,6 +409,14 @@ class NeuronavigationPanel(wx.Panel): | ||
401 | btn_nav.SetToolTip(tooltip) | 409 | btn_nav.SetToolTip(tooltip) |
402 | btn_nav.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnNavigate, btn=(btn_nav, choice_trck, choice_ref))) | 410 | btn_nav.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnNavigate, btn=(btn_nav, choice_trck, choice_ref))) |
403 | 411 | ||
412 | + tooltip = wx.ToolTip(_(u"Refine the coregistration")) | ||
413 | + checkicp = wx.CheckBox(self, -1, _(' ')) | ||
414 | + checkicp.SetValue(False) | ||
415 | + checkicp.Enable(False) | ||
416 | + checkicp.Bind(wx.EVT_CHECKBOX, partial(self.Oncheckicp, ctrl=checkicp)) | ||
417 | + checkicp.SetToolTip(tooltip) | ||
418 | + self.checkicp = checkicp | ||
419 | + | ||
404 | # Image and tracker coordinates number controls | 420 | # Image and tracker coordinates number controls |
405 | for m in range(len(self.btns_coord)): | 421 | for m in range(len(self.btns_coord)): |
406 | for n in range(3): | 422 | for n in range(3): |
@@ -421,10 +437,12 @@ class NeuronavigationPanel(wx.Panel): | @@ -421,10 +437,12 @@ class NeuronavigationPanel(wx.Panel): | ||
421 | if m in range(1, 6): | 437 | if m in range(1, 6): |
422 | self.numctrls_coord[m][n].SetEditable(False) | 438 | self.numctrls_coord[m][n].SetEditable(False) |
423 | 439 | ||
424 | - nav_sizer = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5) | 440 | + nav_sizer = wx.FlexGridSizer(rows=1, cols=5, hgap=5, vgap=5) |
425 | nav_sizer.AddMany([(txt_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), | 441 | nav_sizer.AddMany([(txt_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), |
426 | (txtctrl_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), | 442 | (txtctrl_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), |
427 | - (btn_nav, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) | 443 | + (btn_nav, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), |
444 | + (txt_icp, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), | ||
445 | + (checkicp, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) | ||
428 | 446 | ||
429 | group_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5) | 447 | group_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5) |
430 | group_sizer.AddGrowableCol(0, 1) | 448 | group_sizer.AddGrowableCol(0, 1) |
@@ -459,6 +477,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -459,6 +477,7 @@ class NeuronavigationPanel(wx.Panel): | ||
459 | Publisher.subscribe(self.UpdateTractsVisualization, 'Update tracts visualization') | 477 | Publisher.subscribe(self.UpdateTractsVisualization, 'Update tracts visualization') |
460 | Publisher.subscribe(self.EnableACT, 'Enable ACT') | 478 | Publisher.subscribe(self.EnableACT, 'Enable ACT') |
461 | Publisher.subscribe(self.UpdateACTData, 'Update ACT data') | 479 | Publisher.subscribe(self.UpdateACTData, 'Update ACT data') |
480 | + Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') | ||
462 | 481 | ||
463 | def LoadImageFiducials(self, marker_id, coord): | 482 | def LoadImageFiducials(self, marker_id, coord): |
464 | for n in const.BTNS_IMG_MKS: | 483 | for n in const.BTNS_IMG_MKS: |
@@ -470,6 +489,14 @@ class NeuronavigationPanel(wx.Panel): | @@ -470,6 +489,14 @@ class NeuronavigationPanel(wx.Panel): | ||
470 | for m in [0, 1, 2]: | 489 | for m in [0, 1, 2]: |
471 | self.numctrls_coord[btn_id][m].SetValue(coord[m]) | 490 | self.numctrls_coord[btn_id][m].SetValue(coord[m]) |
472 | 491 | ||
492 | + def UpdateNavigationStatus(self, nav_status, vis_status): | ||
493 | + self.nav_status = nav_status | ||
494 | + if nav_status and (self.m_icp is not None): | ||
495 | + self.checkicp.Enable(True) | ||
496 | + else: | ||
497 | + self.checkicp.Enable(False) | ||
498 | + #self.checkicp.SetValue(False) | ||
499 | + | ||
473 | def UpdateFRE(self, fre): | 500 | def UpdateFRE(self, fre): |
474 | # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok | 501 | # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok |
475 | self.txtctrl_fre.SetValue(str(round(fre, 2))) | 502 | self.txtctrl_fre.SetValue(str(round(fre, 2))) |
@@ -661,9 +688,57 @@ class NeuronavigationPanel(wx.Panel): | @@ -661,9 +688,57 @@ class NeuronavigationPanel(wx.Panel): | ||
661 | # Update number controls with tracker coordinates | 688 | # Update number controls with tracker coordinates |
662 | if coord is not None: | 689 | if coord is not None: |
663 | self.fiducials[btn_id, :] = coord[0:3] | 690 | self.fiducials[btn_id, :] = coord[0:3] |
691 | + if btn_id == 3: | ||
692 | + self.fiducials_raw[0, :] = coord_raw[0, :] | ||
693 | + self.fiducials_raw[1, :] = coord_raw[1, :] | ||
694 | + elif btn_id == 4: | ||
695 | + self.fiducials_raw[2, :] = coord_raw[0, :] | ||
696 | + self.fiducials_raw[3, :] = coord_raw[1, :] | ||
697 | + else: | ||
698 | + self.fiducials_raw[4, :] = coord_raw[0, :] | ||
699 | + self.fiducials_raw[5, :] = coord_raw[1, :] | ||
700 | + | ||
664 | for n in [0, 1, 2]: | 701 | for n in [0, 1, 2]: |
665 | self.numctrls_coord[btn_id][n].SetValue(float(coord[n])) | 702 | self.numctrls_coord[btn_id][n].SetValue(float(coord[n])) |
666 | 703 | ||
704 | + def OnICP(self): | ||
705 | + dialog = dlg.ICPCorregistrationDialog(nav_prop=(self.tracker_id, self.trk_init, self.ref_mode_id)) | ||
706 | + if dialog.ShowModal() == wx.ID_OK: | ||
707 | + self.m_icp, point_coord, transformed_points, prev_error, final_error = dialog.GetValue() | ||
708 | + #TODO: checkbox in the dialog to transfer the icp points to 3D viewer | ||
709 | + #create markers | ||
710 | + # for i in range(len(point_coord)): | ||
711 | + # img_coord = point_coord[i][0],-point_coord[i][1],point_coord[i][2], 0, 0, 0 | ||
712 | + # transf_coord = transformed_points[i][0],-transformed_points[i][1],transformed_points[i][2], 0, 0, 0 | ||
713 | + # Publisher.sendMessage('Create marker', coord=img_coord, marker_id=None, colour=(1,0,0)) | ||
714 | + # Publisher.sendMessage('Create marker', coord=transf_coord, marker_id=None, colour=(0,0,1)) | ||
715 | + if self.m_icp is not None: | ||
716 | + dlg.ReportICPerror(prev_error, final_error) | ||
717 | + self.checkicp.Enable(True) | ||
718 | + self.checkicp.SetValue(True) | ||
719 | + self.icp = True | ||
720 | + else: | ||
721 | + self.checkicp.Enable(False) | ||
722 | + self.checkicp.SetValue(False) | ||
723 | + self.icp = False | ||
724 | + | ||
725 | + return self.m_icp | ||
726 | + | ||
727 | + def Oncheckicp(self, evt, ctrl): | ||
728 | + if ctrl.GetValue() and evt and (self.m_icp is not None): | ||
729 | + self.icp = True | ||
730 | + else: | ||
731 | + self.icp = False | ||
732 | + self.ctrl_icp() | ||
733 | + | ||
734 | + def ctrl_icp(self): | ||
735 | + if self.icp: | ||
736 | + self.UpdateFRE(self.icp_fre) | ||
737 | + else: | ||
738 | + self.UpdateFRE(self.fre) | ||
739 | + self.icp_queue.put_nowait([self.icp, self.m_icp]) | ||
740 | + #print(self.icp, self.m_icp) | ||
741 | + | ||
667 | def OnNavigate(self, evt, btn): | 742 | def OnNavigate(self, evt, btn): |
668 | btn_nav = btn[0] | 743 | btn_nav = btn[0] |
669 | choice_trck = btn[1] | 744 | choice_trck = btn[1] |
@@ -673,7 +748,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -673,7 +748,7 @@ class NeuronavigationPanel(wx.Panel): | ||
673 | # initialize jobs list | 748 | # initialize jobs list |
674 | jobs_list = [] | 749 | jobs_list = [] |
675 | vis_components = [self.trigger_state, self.view_tracts] | 750 | vis_components = [self.trigger_state, self.view_tracts] |
676 | - vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue] | 751 | + vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue] |
677 | 752 | ||
678 | nav_id = btn_nav.GetValue() | 753 | nav_id = btn_nav.GetValue() |
679 | if not nav_id: | 754 | if not nav_id: |
@@ -752,10 +827,9 @@ class NeuronavigationPanel(wx.Panel): | @@ -752,10 +827,9 @@ class NeuronavigationPanel(wx.Panel): | ||
752 | tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id | 827 | tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id |
753 | 828 | ||
754 | # compute fiducial registration error (FRE) | 829 | # compute fiducial registration error (FRE) |
755 | - # this is the old way to compute the fre, left here to recheck if new works fine. | ||
756 | - # fre = db.calculate_fre(self.fiducials, minv, n, q1, q2) | ||
757 | - fre = db.calculate_fre_m(self.fiducials) | ||
758 | - self.UpdateFRE(fre) | 830 | + if not self.icp_fre: |
831 | + self.fre = db.calculate_fre(self.fiducials_raw, self.fiducials, self.ref_mode_id, m_change) | ||
832 | + self.UpdateFRE(self.fre) | ||
759 | 833 | ||
760 | if self.track_obj: | 834 | if self.track_obj: |
761 | # if object tracking is selected | 835 | # if object tracking is selected |
@@ -778,15 +852,15 @@ class NeuronavigationPanel(wx.Panel): | @@ -778,15 +852,15 @@ class NeuronavigationPanel(wx.Panel): | ||
778 | obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | 852 | obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) |
779 | coreg_data.extend(obj_data) | 853 | coreg_data.extend(obj_data) |
780 | 854 | ||
781 | - jobs_list.append(dcr.CoordinateCorregistrate(self.ref_mode_id, tracker_mode, coreg_data, self.coord_queue, | ||
782 | - self.view_tracts, self.coord_tracts_queue, | 855 | + queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue] |
856 | + jobs_list.append(dcr.CoordinateCorregistrate(self.ref_mode_id, tracker_mode, coreg_data, | ||
857 | + self.view_tracts, queues, | ||
783 | self.event, self.sleep_nav)) | 858 | self.event, self.sleep_nav)) |
784 | - | ||
785 | else: | 859 | else: |
786 | coreg_data = (m_change, 0) | 860 | coreg_data = (m_change, 0) |
861 | + queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue] | ||
787 | jobs_list.append(dcr.CoordinateCorregistrateNoObject(self.ref_mode_id, tracker_mode, coreg_data, | 862 | jobs_list.append(dcr.CoordinateCorregistrateNoObject(self.ref_mode_id, tracker_mode, coreg_data, |
788 | - self.coord_queue, | ||
789 | - self.view_tracts, self.coord_tracts_queue, | 863 | + self.view_tracts, queues, |
790 | self.event, self.sleep_nav)) | 864 | self.event, self.sleep_nav)) |
791 | 865 | ||
792 | if not errors: | 866 | if not errors: |
@@ -807,12 +881,11 @@ class NeuronavigationPanel(wx.Panel): | @@ -807,12 +881,11 @@ class NeuronavigationPanel(wx.Panel): | ||
807 | self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius,\ | 881 | self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius,\ |
808 | self.n_threads, self.act_data, affine_vtk, matrix_shape[1] | 882 | self.n_threads, self.act_data, affine_vtk, matrix_shape[1] |
809 | # print("Appending the tract computation thread!") | 883 | # print("Appending the tract computation thread!") |
884 | + queues = [self.coord_tracts_queue, self.tracts_queue] | ||
810 | if self.enable_act: | 885 | if self.enable_act: |
811 | - jobs_list.append(dti.ComputeTractsACTThread(self.trk_inp, self.coord_tracts_queue, | ||
812 | - self.tracts_queue, self.event, self.sleep_nav)) | 886 | + jobs_list.append(dti.ComputeTractsACTThread(self.trk_inp, queues, self.event, self.sleep_nav)) |
813 | else: | 887 | else: |
814 | - jobs_list.append(dti.ComputeTractsThread(self.trk_inp, self.coord_tracts_queue, | ||
815 | - self.tracts_queue, self.event, self.sleep_nav)) | 888 | + jobs_list.append(dti.ComputeTractsThread(self.trk_inp, queues, self.event, self.sleep_nav)) |
816 | 889 | ||
817 | jobs_list.append(UpdateNavigationScene(vis_queues, vis_components, | 890 | jobs_list.append(UpdateNavigationScene(vis_queues, vis_components, |
818 | self.event, self.sleep_nav)) | 891 | self.event, self.sleep_nav)) |
@@ -822,6 +895,13 @@ class NeuronavigationPanel(wx.Panel): | @@ -822,6 +895,13 @@ class NeuronavigationPanel(wx.Panel): | ||
822 | jobs.start() | 895 | jobs.start() |
823 | # del jobs | 896 | # del jobs |
824 | 897 | ||
898 | + if not self.checkicp.GetValue(): | ||
899 | + if dlg.ICPcorregistration(self.fre): | ||
900 | + m_icp = self.OnICP() | ||
901 | + self.icp_fre = db.calculate_fre(self.fiducials_raw, self.fiducials, self.ref_mode_id, | ||
902 | + m_change, m_icp) | ||
903 | + self.ctrl_icp() | ||
904 | + | ||
825 | def ResetImageFiducials(self): | 905 | def ResetImageFiducials(self): |
826 | for m in range(0, 3): | 906 | for m in range(0, 3): |
827 | self.btns_coord[m].SetValue(False) | 907 | self.btns_coord[m].SetValue(False) |
@@ -838,15 +918,25 @@ class NeuronavigationPanel(wx.Panel): | @@ -838,15 +918,25 @@ class NeuronavigationPanel(wx.Panel): | ||
838 | self.txtctrl_fre.SetValue('') | 918 | self.txtctrl_fre.SetValue('') |
839 | self.txtctrl_fre.SetBackgroundColour('WHITE') | 919 | self.txtctrl_fre.SetBackgroundColour('WHITE') |
840 | 920 | ||
921 | + def ResetIcp(self): | ||
922 | + self.m_icp = None | ||
923 | + self.fre = None | ||
924 | + self.icp_fre = None | ||
925 | + self.icp = False | ||
926 | + self.checkicp.Enable(False) | ||
927 | + self.checkicp.SetValue(False) | ||
928 | + | ||
841 | def OnCloseProject(self): | 929 | def OnCloseProject(self): |
842 | self.ResetTrackerFiducials() | 930 | self.ResetTrackerFiducials() |
843 | self.ResetImageFiducials() | 931 | self.ResetImageFiducials() |
932 | + self.ResetIcp() | ||
844 | self.OnChoiceTracker(False, self.choice_trck) | 933 | self.OnChoiceTracker(False, self.choice_trck) |
845 | Publisher.sendMessage('Update object registration') | 934 | Publisher.sendMessage('Update object registration') |
846 | Publisher.sendMessage('Update track object state', flag=False, obj_name=False) | 935 | Publisher.sendMessage('Update track object state', flag=False, obj_name=False) |
847 | Publisher.sendMessage('Delete all markers') | 936 | Publisher.sendMessage('Delete all markers') |
848 | Publisher.sendMessage("Update marker offset state", create=False) | 937 | Publisher.sendMessage("Update marker offset state", create=False) |
849 | Publisher.sendMessage("Remove tracts") | 938 | Publisher.sendMessage("Remove tracts") |
939 | + Publisher.sendMessage("Set cross visibility", visibility=0) | ||
850 | # TODO: Reset camera initial focus | 940 | # TODO: Reset camera initial focus |
851 | Publisher.sendMessage('Reset cam clipping range') | 941 | Publisher.sendMessage('Reset cam clipping range') |
852 | 942 | ||
@@ -1373,18 +1463,23 @@ class MarkersPanel(wx.Panel): | @@ -1373,18 +1463,23 @@ class MarkersPanel(wx.Panel): | ||
1373 | self.marker_ind -= 1 | 1463 | self.marker_ind -= 1 |
1374 | Publisher.sendMessage('Remove marker', index=index) | 1464 | Publisher.sendMessage('Remove marker', index=index) |
1375 | 1465 | ||
1376 | - def OnCreateMarker(self, evt=None, coord=None, marker_id=None): | 1466 | + def OnCreateMarker(self, evt=None, coord=None, marker_id=None, colour=None): |
1377 | # OnCreateMarker is used for both pubsub and button click events | 1467 | # OnCreateMarker is used for both pubsub and button click events |
1378 | # Pubsub is used for markers created with fiducial buttons, trigger and create marker button | 1468 | # Pubsub is used for markers created with fiducial buttons, trigger and create marker button |
1469 | + if not colour: | ||
1470 | + colour = self.marker_colour | ||
1471 | + if not coord: | ||
1472 | + coord = self.current_coord | ||
1473 | + | ||
1379 | if evt is None: | 1474 | if evt is None: |
1380 | if coord: | 1475 | if coord: |
1381 | self.CreateMarker(coord=coord, colour=(0.0, 1.0, 0.0), size=self.marker_size, | 1476 | self.CreateMarker(coord=coord, colour=(0.0, 1.0, 0.0), size=self.marker_size, |
1382 | marker_id=marker_id, seed=self.current_seed) | 1477 | marker_id=marker_id, seed=self.current_seed) |
1383 | else: | 1478 | else: |
1384 | - self.CreateMarker(coord=self.current_coord, colour=self.marker_colour, size=self.marker_size, | 1479 | + self.CreateMarker(coord=self.current_coord, colour=colour, size=self.marker_size, |
1385 | seed=self.current_seed) | 1480 | seed=self.current_seed) |
1386 | else: | 1481 | else: |
1387 | - self.CreateMarker(coord=self.current_coord, colour=self.marker_colour, size=self.marker_size, | 1482 | + self.CreateMarker(coord=self.current_coord, colour=colour, size=self.marker_size, |
1388 | seed=self.current_seed) | 1483 | seed=self.current_seed) |
1389 | 1484 | ||
1390 | def OnLoadMarkers(self, evt): | 1485 | def OnLoadMarkers(self, evt): |
@@ -1807,7 +1902,6 @@ class TractographyPanel(wx.Panel): | @@ -1807,7 +1902,6 @@ class TractographyPanel(wx.Panel): | ||
1807 | self.nav_status = nav_status | 1902 | self.nav_status = nav_status |
1808 | 1903 | ||
1809 | def OnLinkBrain(self, event=None): | 1904 | def OnLinkBrain(self, event=None): |
1810 | - | ||
1811 | Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | 1905 | Publisher.sendMessage('Update status text in GUI', label=_("Busy")) |
1812 | Publisher.sendMessage('Begin busy cursor') | 1906 | Publisher.sendMessage('Begin busy cursor') |
1813 | mask_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import brain mask")) | 1907 | mask_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import brain mask")) |
@@ -2023,7 +2117,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -2023,7 +2117,7 @@ class UpdateNavigationScene(threading.Thread): | ||
2023 | 2117 | ||
2024 | threading.Thread.__init__(self, name='UpdateScene') | 2118 | threading.Thread.__init__(self, name='UpdateScene') |
2025 | self.trigger_state, self.view_tracts = vis_components | 2119 | self.trigger_state, self.view_tracts = vis_components |
2026 | - self.coord_queue, self.trigger_queue, self.tracts_queue = vis_queues | 2120 | + self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue = vis_queues |
2027 | self.sle = sle | 2121 | self.sle = sle |
2028 | self.event = event | 2122 | self.event = event |
2029 | 2123 | ||
@@ -2055,6 +2149,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -2055,6 +2149,7 @@ class UpdateNavigationScene(threading.Thread): | ||
2055 | 2149 | ||
2056 | #TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user | 2150 | #TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user |
2057 | # see the red cross in the position of the offset marker | 2151 | # see the red cross in the position of the offset marker |
2152 | + wx.CallAfter(Publisher.sendMessage, 'Update slices position', position=coord[:3]) | ||
2058 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) | 2153 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) |
2059 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') | 2154 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') |
2060 | 2155 |