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 |