Commit 3a9ab9cd0df30d332aba58994620b8b4b83e280a
Committed by
GitHub
1 parent
f9bfc475
Exists in
master
Decouple robot control (#404)
* INIT testing.. * REM: Decouple robot * FIX: trck_init gets the first value * INIT testing.. * REM: Decouple robot * FIX: trck_init gets the first value * FIX: append tracker init robot * FIX: transform robot coordinates to tracker coordinates system * ENH: robot matrix estimation outside invesalius * ENH: remove robot coordinates and target matrix from invesalius * FIX: disconnected * FIX: tracker connection * ENH: Cleaning * CLN: remove unused imports * ENH: checks with target angle is zero/none to enable send target to robot * more cleaning * FIX: destroys robot dialog if not possible to connect to the robot * ENH: deal with robot resetting * ADD: function to estimate transformation matrix -navigation starts after ICP box * FIX: deals with tracker devices not connected * FIX: transformation matrix to list * ADD: send target to robot from image -need tests * FIX: image_to_tracker transformation * ENH: apply icp matrix to target * ENH: Show robot menu item only with tracker is robot * FIX: typo and comments * FIX: exception for BadNamespaceError * ADD: set metaclass to navigation and icp -allows access for plugins * CLEAN: remove unused imports
Showing
14 changed files
with
197 additions
and
1041 deletions
Show diff stats
invesalius/constants.py
| @@ -832,7 +832,6 @@ SEED_RADIUS = 1.5 | @@ -832,7 +832,6 @@ SEED_RADIUS = 1.5 | ||
| 832 | # Increased the default sleep parameter from 0.1 to 0.15 to decrease CPU load during navigation. | 832 | # Increased the default sleep parameter from 0.1 to 0.15 to decrease CPU load during navigation. |
| 833 | SLEEP_NAVIGATION = 0.2 | 833 | SLEEP_NAVIGATION = 0.2 |
| 834 | SLEEP_COORDINATES = 0.05 | 834 | SLEEP_COORDINATES = 0.05 |
| 835 | -SLEEP_ROBOT = 0.01 | ||
| 836 | 835 | ||
| 837 | BRAIN_OPACITY = 0.6 | 836 | BRAIN_OPACITY = 0.6 |
| 838 | N_CPU = psutil.cpu_count() | 837 | N_CPU = psutil.cpu_count() |
| @@ -864,14 +863,3 @@ PULSE_DURATION_IN_MILLISECONDS = 0.2 | @@ -864,14 +863,3 @@ PULSE_DURATION_IN_MILLISECONDS = 0.2 | ||
| 864 | 863 | ||
| 865 | #Robot | 864 | #Robot |
| 866 | ROBOT_ElFIN_IP = ['143.107.220.251', '169.254.153.251', '127.0.0.1'] | 865 | ROBOT_ElFIN_IP = ['143.107.220.251', '169.254.153.251', '127.0.0.1'] |
| 867 | -ROBOT_ElFIN_PORT = 10003 | ||
| 868 | -ROBOT_MOTIONS = {"normal": 0, "linear out": 1, "arc": 2} | ||
| 869 | -ROBOT_HEAD_VELOCITY_THRESHOLD = 10 #mm/s | ||
| 870 | -ROBOT_ARC_THRESHOLD_DISTANCE = 100 #mm | ||
| 871 | -ROBOT_VERSOR_SCALE_FACTOR = 70 | ||
| 872 | -#Robot Working Space is defined as 800mm in Elfin manual. For safety, the value is reduced by 5%. | ||
| 873 | -ROBOT_WORKING_SPACE = 760 #mm | ||
| 874 | -ROBOT_MOVE_STATE = {"free to move": 0, | ||
| 875 | - "in motion": 1009, | ||
| 876 | - "waiting for execution": 1013, | ||
| 877 | - "error": 1025} |
invesalius/data/bases.py
| @@ -163,6 +163,12 @@ def transform_icp(m_img, m_icp): | @@ -163,6 +163,12 @@ def transform_icp(m_img, m_icp): | ||
| 163 | 163 | ||
| 164 | return m_img | 164 | return m_img |
| 165 | 165 | ||
| 166 | +def inverse_transform_icp(m_img, m_icp): | ||
| 167 | + coord_img = [m_img[0, -1], -m_img[1, -1], m_img[2, -1], 1] | ||
| 168 | + m_img[0, -1], m_img[1, -1], m_img[2, -1], _ = np.linalg.inv(m_icp) @ coord_img | ||
| 169 | + m_img[0, -1], m_img[1, -1], m_img[2, -1] = m_img[0, -1], -m_img[1, -1], m_img[2, -1] | ||
| 170 | + | ||
| 171 | + return m_img | ||
| 166 | 172 | ||
| 167 | def object_registration(fiducials, orients, coord_raw, m_change): | 173 | def object_registration(fiducials, orients, coord_raw, m_change): |
| 168 | """ | 174 | """ |
| @@ -244,47 +250,3 @@ def object_registration(fiducials, orients, coord_raw, m_change): | @@ -244,47 +250,3 @@ def object_registration(fiducials, orients, coord_raw, m_change): | ||
| 244 | ) | 250 | ) |
| 245 | 251 | ||
| 246 | return t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img | 252 | return t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img |
| 247 | - | ||
| 248 | -def compute_robot_to_head_matrix(raw_target_robot): | ||
| 249 | - """ | ||
| 250 | - :param head: nx6 array of head coordinates from tracking device in robot space | ||
| 251 | - :param robot: nx6 array of robot coordinates | ||
| 252 | - | ||
| 253 | - :return: target_robot_matrix: 3x3 array representing change of basis from robot to head in robot system | ||
| 254 | - """ | ||
| 255 | - head, robot = raw_target_robot | ||
| 256 | - # compute head target matrix | ||
| 257 | - m_head_target = dco.coordinates_to_transformation_matrix( | ||
| 258 | - position=head[:3], | ||
| 259 | - orientation=head[3:], | ||
| 260 | - axes='rzyx', | ||
| 261 | - ) | ||
| 262 | - | ||
| 263 | - # compute robot target matrix | ||
| 264 | - m_robot_target = dco.coordinates_to_transformation_matrix( | ||
| 265 | - position=robot[:3], | ||
| 266 | - orientation=robot[3:], | ||
| 267 | - axes='rzyx', | ||
| 268 | - ) | ||
| 269 | - robot_to_head_matrix = np.linalg.inv(m_head_target) @ m_robot_target | ||
| 270 | - | ||
| 271 | - return robot_to_head_matrix | ||
| 272 | - | ||
| 273 | - | ||
| 274 | -class transform_tracker_to_robot(object): | ||
| 275 | - M_tracker_to_robot = np.array([]) | ||
| 276 | - def transformation_tracker_to_robot(self, tracker_coord): | ||
| 277 | - if not transform_tracker_to_robot.M_tracker_to_robot.any(): | ||
| 278 | - return None | ||
| 279 | - | ||
| 280 | - M_tracker = dco.coordinates_to_transformation_matrix( | ||
| 281 | - position=tracker_coord[:3], | ||
| 282 | - orientation=tracker_coord[3:6], | ||
| 283 | - axes='rzyx', | ||
| 284 | - ) | ||
| 285 | - M_tracker_in_robot = transform_tracker_to_robot.M_tracker_to_robot @ M_tracker | ||
| 286 | - | ||
| 287 | - translation, angles_as_deg = dco.transformation_matrix_to_coordinates(M_tracker_in_robot, axes='rzyx') | ||
| 288 | - tracker_in_robot = list(translation) + list(angles_as_deg) | ||
| 289 | - | ||
| 290 | - return tracker_in_robot |
invesalius/data/coordinates.py
| @@ -22,7 +22,6 @@ import numpy as np | @@ -22,7 +22,6 @@ import numpy as np | ||
| 22 | import threading | 22 | import threading |
| 23 | import wx | 23 | import wx |
| 24 | 24 | ||
| 25 | -import invesalius.data.bases as db | ||
| 26 | import invesalius.data.transformations as tr | 25 | import invesalius.data.transformations as tr |
| 27 | import invesalius.constants as const | 26 | import invesalius.constants as const |
| 28 | 27 | ||
| @@ -47,14 +46,20 @@ class TrackerCoordinates(): | @@ -47,14 +46,20 @@ class TrackerCoordinates(): | ||
| 47 | def SetCoordinates(self, coord, markers_flag): | 46 | def SetCoordinates(self, coord, markers_flag): |
| 48 | self.coord = coord | 47 | self.coord = coord |
| 49 | self.markers_flag = markers_flag | 48 | self.markers_flag = markers_flag |
| 50 | - if self.previous_markers_flag != self.markers_flag and not self.nav_status: | ||
| 51 | - wx.CallAfter(Publisher.sendMessage, 'Sensors ID', markers_flag=self.markers_flag) | ||
| 52 | - self.previous_markers_flag = self.markers_flag | 49 | + if not self.nav_status: |
| 50 | + wx.CallAfter(Publisher.sendMessage, 'Update tracker coordinates', | ||
| 51 | + coord=self.coord.tolist(), markers_flag=self.markers_flag) | ||
| 52 | + if self.previous_markers_flag != self.markers_flag: | ||
| 53 | + wx.CallAfter(Publisher.sendMessage, 'Sensors ID', markers_flag=self.markers_flag) | ||
| 54 | + self.previous_markers_flag = self.markers_flag | ||
| 53 | 55 | ||
| 54 | def GetCoordinates(self): | 56 | def GetCoordinates(self): |
| 55 | - if self.previous_markers_flag != self.markers_flag and self.nav_status: | ||
| 56 | - wx.CallAfter(Publisher.sendMessage, 'Sensors ID', markers_flag=self.markers_flag) | ||
| 57 | - self.previous_markers_flag = self.markers_flag | 57 | + if self.nav_status: |
| 58 | + wx.CallAfter(Publisher.sendMessage, 'Update tracker coordinates', | ||
| 59 | + coord=self.coord.tolist(), markers_flag=self.markers_flag) | ||
| 60 | + if self.previous_markers_flag != self.markers_flag: | ||
| 61 | + wx.CallAfter(Publisher.sendMessage, 'Sensors ID', markers_flag=self.markers_flag) | ||
| 62 | + self.previous_markers_flag = self.markers_flag | ||
| 58 | 63 | ||
| 59 | return self.coord, self.markers_flag | 64 | return self.coord, self.markers_flag |
| 60 | 65 | ||
| @@ -195,16 +200,6 @@ def PolarisCoord(trck_init, trck_id, ref_mode): | @@ -195,16 +200,6 @@ def PolarisCoord(trck_init, trck_id, ref_mode): | ||
| 195 | 200 | ||
| 196 | return coord, [trck.probeID, trck.refID, trck.objID] | 201 | return coord, [trck.probeID, trck.refID, trck.objID] |
| 197 | 202 | ||
| 198 | -def ElfinCoord(trck_init): | ||
| 199 | - if len(trck_init) > 2: | ||
| 200 | - robotcoordinates = trck_init[-1] | ||
| 201 | - coord = robotcoordinates.GetRobotCoordinates() | ||
| 202 | - if coord is None: | ||
| 203 | - coord = np.array([0, 0, 0, 0, 0, 0]) | ||
| 204 | - else: | ||
| 205 | - coord = np.array([0, 0, 0, 0, 0, 0]) | ||
| 206 | - | ||
| 207 | - return coord | ||
| 208 | 203 | ||
| 209 | def CameraCoord(trck_init, trck_id, ref_mode): | 204 | def CameraCoord(trck_init, trck_id, ref_mode): |
| 210 | trck = trck_init[0] | 205 | trck = trck_init[0] |
| @@ -346,6 +341,13 @@ def PolhemusSerialCoord(trck_init, trck_id, ref_mode): | @@ -346,6 +341,13 @@ def PolhemusSerialCoord(trck_init, trck_id, ref_mode): | ||
| 346 | 341 | ||
| 347 | return coord | 342 | return coord |
| 348 | 343 | ||
| 344 | +def RobotCoord(trk_init, trck_id, ref_mode): | ||
| 345 | + #trck_id is the tracker related to the robot ID. To get the tracker ID, combined with the robot, | ||
| 346 | + # it is required to get trk_init[1] | ||
| 347 | + tracker_id = trk_init[1] | ||
| 348 | + coord_tracker, markers_flag = GetCoordinatesForThread(trk_init[0], tracker_id, ref_mode) | ||
| 349 | + | ||
| 350 | + return np.vstack([coord_tracker[0], coord_tracker[1], coord_tracker[2]]), markers_flag | ||
| 349 | 351 | ||
| 350 | def DebugCoordRandom(trk_init, trck_id, ref_mode): | 352 | def DebugCoordRandom(trk_init, trck_id, ref_mode): |
| 351 | """ | 353 | """ |
| @@ -497,22 +499,6 @@ def dynamic_reference_m(probe, reference): | @@ -497,22 +499,6 @@ def dynamic_reference_m(probe, reference): | ||
| 497 | return coord_rot | 499 | return coord_rot |
| 498 | 500 | ||
| 499 | 501 | ||
| 500 | -def RobotCoord(trk_init, trck_id, ref_mode): | ||
| 501 | - coord_tracker, markers_flag = GetCoordinatesForThread(trk_init[0][0], trk_init[1], ref_mode) | ||
| 502 | - coord_robot = ElfinCoord([trk_init[0][1]]+trk_init[1:]) | ||
| 503 | - | ||
| 504 | - probe_tracker_in_robot = db.transform_tracker_to_robot().transformation_tracker_to_robot(coord_tracker[0]) | ||
| 505 | - ref_tracker_in_robot = db.transform_tracker_to_robot().transformation_tracker_to_robot(coord_tracker[1]) | ||
| 506 | - obj_tracker_in_robot = db.transform_tracker_to_robot().transformation_tracker_to_robot(coord_tracker[2]) | ||
| 507 | - | ||
| 508 | - if probe_tracker_in_robot is None: | ||
| 509 | - probe_tracker_in_robot = coord_tracker[0] | ||
| 510 | - ref_tracker_in_robot = coord_tracker[1] | ||
| 511 | - obj_tracker_in_robot = coord_tracker[2] | ||
| 512 | - | ||
| 513 | - return np.vstack([probe_tracker_in_robot, ref_tracker_in_robot, coord_robot, obj_tracker_in_robot]), markers_flag | ||
| 514 | - | ||
| 515 | - | ||
| 516 | def dynamic_reference_m2(probe, reference): | 502 | def dynamic_reference_m2(probe, reference): |
| 517 | """ | 503 | """ |
| 518 | Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama | 504 | Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama |
| @@ -590,4 +576,4 @@ class ReceiveCoordinates(threading.Thread): | @@ -590,4 +576,4 @@ class ReceiveCoordinates(threading.Thread): | ||
| 590 | while not self.event.is_set(): | 576 | while not self.event.is_set(): |
| 591 | coord_raw, markers_flag = GetCoordinatesForThread(self.trck_init, self.trck_id, const.DEFAULT_REF_MODE) | 577 | coord_raw, markers_flag = GetCoordinatesForThread(self.trck_init, self.trck_id, const.DEFAULT_REF_MODE) |
| 592 | self.TrackerCoordinates.SetCoordinates(coord_raw, markers_flag) | 578 | self.TrackerCoordinates.SetCoordinates(coord_raw, markers_flag) |
| 593 | - sleep(const.SLEEP_COORDINATES) | ||
| 594 | \ No newline at end of file | 579 | \ No newline at end of file |
| 580 | + sleep(const.SLEEP_COORDINATES) |
invesalius/data/coregistration.py
| @@ -101,6 +101,32 @@ def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | @@ -101,6 +101,32 @@ def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | ||
| 101 | m_img[:3, :3] = r_obj[:3, :3] | 101 | m_img[:3, :3] = r_obj[:3, :3] |
| 102 | return m_img | 102 | return m_img |
| 103 | 103 | ||
| 104 | +def image_to_tracker(m_change, target, icp): | ||
| 105 | + """Compute the transformation matrix to the tracker coordinate system | ||
| 106 | + | ||
| 107 | + :param m_change: Corregistration transformation obtained from fiducials | ||
| 108 | + :type m_change: numpy.ndarray | ||
| 109 | + :param target: Target in invesalius coordinate system | ||
| 110 | + :type target: numpy.ndarray | ||
| 111 | + :param icp: ICP transformation matrix | ||
| 112 | + :type icp: numpy.ndarray | ||
| 113 | + | ||
| 114 | + :return: 4 x 4 numpy double array | ||
| 115 | + :rtype: numpy.ndarray | ||
| 116 | + """ | ||
| 117 | + m_target_in_image = dco.coordinates_to_transformation_matrix( | ||
| 118 | + position=target[:3], | ||
| 119 | + orientation=[0, 0, 0], | ||
| 120 | + axes='sxyz', | ||
| 121 | + ) | ||
| 122 | + if icp.use_icp: | ||
| 123 | + m_target_in_image = bases.inverse_transform_icp(m_target_in_image, icp.m_icp) | ||
| 124 | + m_target_in_tracker = np.linalg.inv(m_change) @ m_target_in_image | ||
| 125 | + | ||
| 126 | + # invert y coordinate | ||
| 127 | + m_target_in_tracker[2, -1] = -m_target_in_tracker[2, -1] | ||
| 128 | + | ||
| 129 | + return m_target_in_tracker | ||
| 104 | 130 | ||
| 105 | def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id, icp): | 131 | def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id, icp): |
| 106 | 132 | ||
| @@ -193,7 +219,6 @@ class CoordinateCorregistrate(threading.Thread): | @@ -193,7 +219,6 @@ class CoordinateCorregistrate(threading.Thread): | ||
| 193 | self.object_at_target_queue = queues[3] | 219 | self.object_at_target_queue = queues[3] |
| 194 | self.icp = None | 220 | self.icp = None |
| 195 | self.m_icp = None | 221 | self.m_icp = None |
| 196 | - self.robot_tracker_flag = None | ||
| 197 | self.last_coord = None | 222 | self.last_coord = None |
| 198 | self.tracker_id = tracker_id | 223 | self.tracker_id = tracker_id |
| 199 | self.target = target | 224 | self.target = target |
| @@ -254,7 +279,7 @@ class CoordinateCorregistrate(threading.Thread): | @@ -254,7 +279,7 @@ class CoordinateCorregistrate(threading.Thread): | ||
| 254 | if self.icp: | 279 | if self.icp: |
| 255 | m_img = bases.transform_icp(m_img, self.m_icp) | 280 | m_img = bases.transform_icp(m_img, self.m_icp) |
| 256 | 281 | ||
| 257 | - self.coord_queue.put_nowait([coord, [coord_raw, markers_flag], m_img, view_obj]) | 282 | + self.coord_queue.put_nowait([coord, markers_flag, m_img, view_obj]) |
| 258 | # print('CoordCoreg: put {}'.format(count)) | 283 | # print('CoordCoreg: put {}'.format(count)) |
| 259 | # count += 1 | 284 | # count += 1 |
| 260 | 285 | ||
| @@ -307,7 +332,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | @@ -307,7 +332,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | ||
| 307 | if self.icp: | 332 | if self.icp: |
| 308 | m_img = bases.transform_icp(m_img, self.m_icp) | 333 | m_img = bases.transform_icp(m_img, self.m_icp) |
| 309 | 334 | ||
| 310 | - self.coord_queue.put_nowait([coord, [coord_raw, markers_flag], m_img, view_obj]) | 335 | + self.coord_queue.put_nowait([coord, markers_flag, m_img, view_obj]) |
| 311 | 336 | ||
| 312 | if self.view_tracts: | 337 | if self.view_tracts: |
| 313 | self.coord_tracts_queue.put_nowait(m_img_flip) | 338 | self.coord_tracts_queue.put_nowait(m_img_flip) |
invesalius/data/elfin.py
| @@ -1,252 +0,0 @@ | @@ -1,252 +0,0 @@ | ||
| 1 | -#!/usr/bin/env python3 | ||
| 2 | - | ||
| 3 | -import sys | ||
| 4 | -from time import sleep | ||
| 5 | -from socket import socket, AF_INET, SOCK_STREAM | ||
| 6 | -import invesalius.constants as const | ||
| 7 | - | ||
| 8 | -class Elfin_Server(): | ||
| 9 | - """ | ||
| 10 | - This class is similar to tracker devices wrappers. | ||
| 11 | - It follows the same functions as the others (Initialize, Run and Close) | ||
| 12 | - """ | ||
| 13 | - def __init__(self, server_ip, port_number): | ||
| 14 | - self.server_ip = server_ip | ||
| 15 | - self.port_number = port_number | ||
| 16 | - | ||
| 17 | - def Initialize(self): | ||
| 18 | - message_size = 1024 | ||
| 19 | - robot_id = 0 | ||
| 20 | - self.cobot = Elfin() | ||
| 21 | - self.cobot.connect(self.server_ip, self.port_number, message_size, robot_id) | ||
| 22 | - print("connected!") | ||
| 23 | - | ||
| 24 | - def Run(self): | ||
| 25 | - return self.cobot.ReadPcsActualPos() | ||
| 26 | - | ||
| 27 | - def SendCoordinates(self, target, motion_type=const.ROBOT_MOTIONS["normal"]): | ||
| 28 | - """ | ||
| 29 | - It's not possible to send a move command to elfin if the robot is during a move. | ||
| 30 | - Status 1009 means robot in motion. | ||
| 31 | - """ | ||
| 32 | - status = self.cobot.ReadMoveState() | ||
| 33 | - if status == const.ROBOT_MOVE_STATE["free to move"]: | ||
| 34 | - if motion_type == const.ROBOT_MOTIONS["normal"] or motion_type == const.ROBOT_MOTIONS["linear out"]: | ||
| 35 | - self.cobot.MoveL(target) | ||
| 36 | - elif motion_type == const.ROBOT_MOTIONS["arc"]: | ||
| 37 | - self.cobot.MoveC(target) | ||
| 38 | - elif status == const.ROBOT_MOVE_STATE["error"]: | ||
| 39 | - self.StopRobot() | ||
| 40 | - | ||
| 41 | - def StopRobot(self): | ||
| 42 | - # Takes some microseconds to the robot actual stops after the command. | ||
| 43 | - # The sleep time is required to guarantee the stop | ||
| 44 | - self.cobot.GrpStop() | ||
| 45 | - sleep(0.1) | ||
| 46 | - | ||
| 47 | - def Close(self): | ||
| 48 | - self.StopRobot() | ||
| 49 | - #TODO: robot function to close? self.cobot.close() | ||
| 50 | - | ||
| 51 | -class Elfin: | ||
| 52 | - def __init__(self): | ||
| 53 | - """ | ||
| 54 | - Class to communicate with elfin robot. This class follows "HansRobot Communication Protocol Interface". | ||
| 55 | - """ | ||
| 56 | - self.end_msg = ",;" | ||
| 57 | - | ||
| 58 | - def connect(self, server_ip, port_number, message_size, robot_id): | ||
| 59 | - mySocket = socket(AF_INET, SOCK_STREAM) | ||
| 60 | - mySocket.connect((server_ip, port_number)) | ||
| 61 | - | ||
| 62 | - self.message_size = message_size | ||
| 63 | - self.robot_id = str(robot_id) | ||
| 64 | - self.mySocket = mySocket | ||
| 65 | - | ||
| 66 | - def send(self, message): | ||
| 67 | - self.mySocket.sendall(message.encode('utf-8')) | ||
| 68 | - data = self.mySocket.recv(self.message_size).decode('utf-8').split(',') | ||
| 69 | - status = self.check_status(data) | ||
| 70 | - if status and type(data) != bool: | ||
| 71 | - if len(data) > 3: | ||
| 72 | - return data[2:-1] | ||
| 73 | - return status | ||
| 74 | - | ||
| 75 | - def check_status(self, recv_message): | ||
| 76 | - status = recv_message[1] | ||
| 77 | - if status == 'OK': | ||
| 78 | - return True | ||
| 79 | - | ||
| 80 | - elif status == 'Fail': | ||
| 81 | - print("Error code: ", recv_message[2]) | ||
| 82 | - return False | ||
| 83 | - | ||
| 84 | - def Electrify(self): | ||
| 85 | - """ | ||
| 86 | - Function: Power the robot | ||
| 87 | - Notes: successful completion of power up before returning, power up time is | ||
| 88 | - about 44s. | ||
| 89 | - :return: | ||
| 90 | - if Error Return False | ||
| 91 | - if not Error Return True | ||
| 92 | - """ | ||
| 93 | - message = "Electrify" + self.end_msg | ||
| 94 | - status = self.send(message) | ||
| 95 | - return status | ||
| 96 | - | ||
| 97 | - def BlackOut(self): | ||
| 98 | - """ | ||
| 99 | - Function: Robot blackout | ||
| 100 | - Notes: successful power outage will only return, power failure time is 3s. | ||
| 101 | - :return: | ||
| 102 | - if Error Return False | ||
| 103 | - if not Error Return True | ||
| 104 | - """ | ||
| 105 | - message = "BlackOut" + self.end_msg | ||
| 106 | - status = self.send(message) | ||
| 107 | - return status | ||
| 108 | - | ||
| 109 | - def StartMaster(self): | ||
| 110 | - """ | ||
| 111 | - Function: Start master station | ||
| 112 | - Notes: the master station will not be returned until successfully started, startup | ||
| 113 | - master time is about 4s. | ||
| 114 | - :return: | ||
| 115 | - if Error Return False | ||
| 116 | - if not Error Return True | ||
| 117 | - """ | ||
| 118 | - message = "StartMaster" + self.end_msg | ||
| 119 | - status = self.send(message) | ||
| 120 | - return status | ||
| 121 | - | ||
| 122 | - def CloseMaster(self): | ||
| 123 | - """ | ||
| 124 | - Function: Close master station | ||
| 125 | - Notes: the master station will not be returned until successfully closed, shut | ||
| 126 | - down the master station time is about 2s. | ||
| 127 | - :return: | ||
| 128 | - if Error Return False | ||
| 129 | - if not Error Return True | ||
| 130 | - """ | ||
| 131 | - message = "CloseMaster" + self.end_msg | ||
| 132 | - status = self.send(message) | ||
| 133 | - return status | ||
| 134 | - | ||
| 135 | - def GrpPowerOn(self): | ||
| 136 | - """ | ||
| 137 | - Function: Robot servo on | ||
| 138 | - :return: | ||
| 139 | - if Error Return False | ||
| 140 | - if not Error Return True | ||
| 141 | - """ | ||
| 142 | - message = "GrpPowerOn," + self.robot_id + self.end_msg | ||
| 143 | - status = self.send(message) | ||
| 144 | - return status | ||
| 145 | - | ||
| 146 | - def GrpPowerOff(self): | ||
| 147 | - """ | ||
| 148 | - Function: Robot servo off | ||
| 149 | - :return: | ||
| 150 | - if Error Return False | ||
| 151 | - if not Error Return True | ||
| 152 | - """ | ||
| 153 | - message = "GrpPowerOff," + self.robot_id + self.end_msg | ||
| 154 | - status = self.send(message) | ||
| 155 | - return status | ||
| 156 | - | ||
| 157 | - def GrpStop(self): | ||
| 158 | - """ | ||
| 159 | - Function: Stop robot | ||
| 160 | - :return: | ||
| 161 | - if Error Return False | ||
| 162 | - if not Error Return True | ||
| 163 | - """ | ||
| 164 | - message = "GrpStop," + self.robot_id + self.end_msg | ||
| 165 | - status = self.send(message) | ||
| 166 | - return status | ||
| 167 | - | ||
| 168 | - def SetOverride(self, override): | ||
| 169 | - """ | ||
| 170 | - function: Set speed ratio | ||
| 171 | - :param override: | ||
| 172 | - double: set speed ratio, range of 0.01~1 | ||
| 173 | - :return: | ||
| 174 | - if Error Return False | ||
| 175 | - if not Error Return True | ||
| 176 | - """ | ||
| 177 | - | ||
| 178 | - message = "SetOverride," + self.robot_id + ',' + str(override) + self.end_msg | ||
| 179 | - status = self.send(message) | ||
| 180 | - return status | ||
| 181 | - | ||
| 182 | - def ReadPcsActualPos(self): | ||
| 183 | - """Function: Get the actual position of the space coordinate | ||
| 184 | - :return: | ||
| 185 | - if True Return x,y,z,a,b,c | ||
| 186 | - if Error Return False | ||
| 187 | - """ | ||
| 188 | - message = "ReadPcsActualPos," + self.robot_id + self.end_msg | ||
| 189 | - coord = self.send(message) | ||
| 190 | - if coord: | ||
| 191 | - return [float(s) for s in coord] | ||
| 192 | - | ||
| 193 | - return coord | ||
| 194 | - | ||
| 195 | - def MoveL(self, target): | ||
| 196 | - """ | ||
| 197 | - function: Robot moves straight to the specified space coordinates | ||
| 198 | - :param: target:[X,Y,Z,RX,RY,RZ] | ||
| 199 | - :return: | ||
| 200 | - """ | ||
| 201 | - target = [str(s) for s in target] | ||
| 202 | - target = (",".join(target)) | ||
| 203 | - message = "MoveL," + self.robot_id + ',' + target + self.end_msg | ||
| 204 | - return self.send(message) | ||
| 205 | - | ||
| 206 | - def SetToolCoordinateMotion(self, status): | ||
| 207 | - """ | ||
| 208 | - function: Function: Set tool coordinate motion | ||
| 209 | - :param: int Switch 0=close 1=open | ||
| 210 | - :return: | ||
| 211 | - if Error Return False | ||
| 212 | - if not Error Return True | ||
| 213 | - """ | ||
| 214 | - message = "SetToolCoordinateMotion," + self.robot_id + ',' + str(status) + self.end_msg | ||
| 215 | - status = self.send(message) | ||
| 216 | - return status | ||
| 217 | - | ||
| 218 | - def ReadMoveState(self): | ||
| 219 | - """ | ||
| 220 | - Function: Get the motion state of the robot | ||
| 221 | - :return: | ||
| 222 | - Current state of motion of robot: | ||
| 223 | - 0=motion completion; | ||
| 224 | - 1009=in motion; | ||
| 225 | - 1013=waiting for execution; | ||
| 226 | - 1025 =Error reporting | ||
| 227 | - """ | ||
| 228 | - message = "ReadMoveState," + self.robot_id + self.end_msg | ||
| 229 | - status = int(self.send(message)[0]) | ||
| 230 | - return status | ||
| 231 | - | ||
| 232 | - def MoveHoming(self): | ||
| 233 | - """ | ||
| 234 | - Function: Robot returns to the origin | ||
| 235 | - :return: | ||
| 236 | - if Error Return False | ||
| 237 | - if not Error Return True | ||
| 238 | - """ | ||
| 239 | - message = "MoveHoming," + self.robot_id + self.end_msg | ||
| 240 | - status = self.send(message) | ||
| 241 | - return status | ||
| 242 | - | ||
| 243 | - def MoveC(self, target): | ||
| 244 | - """ | ||
| 245 | - function: Arc motion | ||
| 246 | - :param: Through position[X,Y,Z],GoalCoord[X,Y,Z,RX,RY,RZ],Type[0 or 1],; | ||
| 247 | - :return: | ||
| 248 | - """ | ||
| 249 | - target = [str(s) for s in target] | ||
| 250 | - target = (",".join(target)) | ||
| 251 | - message = "MoveC," + self.robot_id + ',' + target + self.end_msg | ||
| 252 | - return self.send(message) |
invesalius/data/elfin_processing.py
| @@ -1,212 +0,0 @@ | @@ -1,212 +0,0 @@ | ||
| 1 | -#-------------------------------------------------------------------------- | ||
| 2 | -# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | ||
| 3 | -# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | ||
| 4 | -# Homepage: http://www.softwarepublico.gov.br | ||
| 5 | -# Contact: invesalius@cti.gov.br | ||
| 6 | -# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | ||
| 7 | -#-------------------------------------------------------------------------- | ||
| 8 | -# Este programa e software livre; voce pode redistribui-lo e/ou | ||
| 9 | -# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | ||
| 10 | -# publicada pela Free Software Foundation; de acordo com a versao 2 | ||
| 11 | -# da Licenca. | ||
| 12 | -# | ||
| 13 | -# Este programa eh distribuido na expectativa de ser util, mas SEM | ||
| 14 | -# QUALQUER GARANTIA; sem mesmo a garantia implicita de | ||
| 15 | -# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | ||
| 16 | -# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | ||
| 17 | -# detalhes. | ||
| 18 | -#-------------------------------------------------------------------------- | ||
| 19 | -import numpy as np | ||
| 20 | -import cv2 | ||
| 21 | -from time import time | ||
| 22 | - | ||
| 23 | -import invesalius.data.coregistration as dcr | ||
| 24 | -import invesalius.data.coordinates as dco | ||
| 25 | -import invesalius.constants as const | ||
| 26 | - | ||
| 27 | - | ||
| 28 | -class KalmanTracker: | ||
| 29 | - """ | ||
| 30 | - Kalman filter to avoid sudden fluctuation from tracker device. | ||
| 31 | - The filter strength can be set by the cov_process, and cov_measure parameter | ||
| 32 | - It is required to create one instance for each variable (x, y, z, a, b, g) | ||
| 33 | - """ | ||
| 34 | - def __init__(self, | ||
| 35 | - state_num=2, | ||
| 36 | - covariance_process=0.001, | ||
| 37 | - covariance_measure=0.1): | ||
| 38 | - | ||
| 39 | - self.state_num = state_num | ||
| 40 | - measure_num = 1 | ||
| 41 | - | ||
| 42 | - # The filter itself. | ||
| 43 | - self.filter = cv2.KalmanFilter(state_num, measure_num, 0) | ||
| 44 | - | ||
| 45 | - self.state = np.zeros((state_num, 1), dtype=np.float32) | ||
| 46 | - self.measurement = np.array((measure_num, 1), np.float32) | ||
| 47 | - self.prediction = np.zeros((state_num, 1), np.float32) | ||
| 48 | - | ||
| 49 | - | ||
| 50 | - self.filter.transitionMatrix = np.array([[1, 1], | ||
| 51 | - [0, 1]], np.float32) | ||
| 52 | - self.filter.measurementMatrix = np.array([[1, 1]], np.float32) | ||
| 53 | - self.filter.processNoiseCov = np.array([[1, 0], | ||
| 54 | - [0, 1]], np.float32) * covariance_process | ||
| 55 | - self.filter.measurementNoiseCov = np.array([[1]], np.float32) * covariance_measure | ||
| 56 | - | ||
| 57 | - def update_kalman(self, measurement): | ||
| 58 | - self.prediction = self.filter.predict() | ||
| 59 | - self.measurement = np.array([[np.float32(measurement[0])]]) | ||
| 60 | - | ||
| 61 | - self.filter.correct(self.measurement) | ||
| 62 | - self.state = self.filter.statePost | ||
| 63 | - | ||
| 64 | - | ||
| 65 | -class TrackerProcessing: | ||
| 66 | - def __init__(self): | ||
| 67 | - self.coord_vel = [] | ||
| 68 | - self.timestamp = [] | ||
| 69 | - self.velocity_vector = [] | ||
| 70 | - self.kalman_coord_vector = [] | ||
| 71 | - self.velocity_std = 0 | ||
| 72 | - | ||
| 73 | - self.tracker_stabilizers = [KalmanTracker( | ||
| 74 | - state_num=2, | ||
| 75 | - covariance_process=0.001, | ||
| 76 | - covariance_measure=0.1) for _ in range(6)] | ||
| 77 | - | ||
| 78 | - def kalman_filter(self, coord_tracker): | ||
| 79 | - kalman_array = [] | ||
| 80 | - pose_np = np.array((coord_tracker[:3], coord_tracker[3:])).flatten() | ||
| 81 | - for value, ps_stb in zip(pose_np, self.tracker_stabilizers): | ||
| 82 | - ps_stb.update_kalman([value]) | ||
| 83 | - kalman_array.append(ps_stb.state[0]) | ||
| 84 | - coord_kalman = np.hstack(kalman_array) | ||
| 85 | - | ||
| 86 | - self.kalman_coord_vector.append(coord_kalman[:3]) | ||
| 87 | - if len(self.kalman_coord_vector) < 20: #avoid initial fluctuations | ||
| 88 | - coord_kalman = coord_tracker | ||
| 89 | - print('initializing filter') | ||
| 90 | - else: | ||
| 91 | - del self.kalman_coord_vector[0] | ||
| 92 | - | ||
| 93 | - return coord_kalman | ||
| 94 | - | ||
| 95 | - def estimate_head_velocity(self, coord_vel, timestamp): | ||
| 96 | - coord_vel = np.vstack(np.array(coord_vel)) | ||
| 97 | - coord_init = coord_vel[:int(len(coord_vel) / 2)].mean(axis=0) | ||
| 98 | - coord_final = coord_vel[int(len(coord_vel) / 2):].mean(axis=0) | ||
| 99 | - velocity = (coord_final - coord_init)/(timestamp[-1] - timestamp[0]) | ||
| 100 | - distance = (coord_final - coord_init) | ||
| 101 | - | ||
| 102 | - return velocity, distance | ||
| 103 | - | ||
| 104 | - def compute_versors(self, init_point, final_point): | ||
| 105 | - init_point = np.array(init_point) | ||
| 106 | - final_point = np.array(final_point) | ||
| 107 | - norm = (sum((final_point - init_point) ** 2)) ** 0.5 | ||
| 108 | - versor_factor = (((final_point-init_point) / norm) * const.ROBOT_VERSOR_SCALE_FACTOR).tolist() | ||
| 109 | - | ||
| 110 | - return versor_factor | ||
| 111 | - | ||
| 112 | - | ||
| 113 | - def compute_arc_motion(self, current_robot_coordinates, head_center_coordinates, new_robot_coordinates): | ||
| 114 | - head_center = head_center_coordinates[0], head_center_coordinates[1], head_center_coordinates[2], \ | ||
| 115 | - new_robot_coordinates[3], new_robot_coordinates[4], new_robot_coordinates[5] | ||
| 116 | - | ||
| 117 | - versor_factor_move_out = self.compute_versors(head_center, current_robot_coordinates) | ||
| 118 | - init_move_out_point = current_robot_coordinates[0] + versor_factor_move_out[0], \ | ||
| 119 | - current_robot_coordinates[1] + versor_factor_move_out[1], \ | ||
| 120 | - current_robot_coordinates[2] + versor_factor_move_out[2], \ | ||
| 121 | - current_robot_coordinates[3], current_robot_coordinates[4], current_robot_coordinates[5] | ||
| 122 | - | ||
| 123 | - middle_point = ((new_robot_coordinates[0] + current_robot_coordinates[0]) / 2, | ||
| 124 | - (new_robot_coordinates[1] + current_robot_coordinates[1]) / 2, | ||
| 125 | - (new_robot_coordinates[2] + current_robot_coordinates[2]) / 2, | ||
| 126 | - 0, 0, 0) | ||
| 127 | - versor_factor_middle_arc = (np.array(self.compute_versors(head_center, middle_point))) * 2 | ||
| 128 | - middle_arc_point = middle_point[0] + versor_factor_middle_arc[0], \ | ||
| 129 | - middle_point[1] + versor_factor_middle_arc[1], \ | ||
| 130 | - middle_point[2] + versor_factor_middle_arc[2] | ||
| 131 | - | ||
| 132 | - versor_factor_arc = self.compute_versors(head_center, new_robot_coordinates) | ||
| 133 | - final_ext_arc_point = new_robot_coordinates[0] + versor_factor_arc[0], \ | ||
| 134 | - new_robot_coordinates[1] + versor_factor_arc[1], \ | ||
| 135 | - new_robot_coordinates[2] + versor_factor_arc[2], \ | ||
| 136 | - new_robot_coordinates[3], new_robot_coordinates[4], new_robot_coordinates[5], 0 | ||
| 137 | - | ||
| 138 | - target_arc = middle_arc_point + final_ext_arc_point | ||
| 139 | - | ||
| 140 | - return init_move_out_point, target_arc | ||
| 141 | - | ||
| 142 | - def compute_head_move_threshold(self, current_ref): | ||
| 143 | - """ | ||
| 144 | - Checks if the head velocity is bellow the threshold | ||
| 145 | - """ | ||
| 146 | - self.coord_vel.append(current_ref) | ||
| 147 | - self.timestamp.append(time()) | ||
| 148 | - if len(self.coord_vel) >= 10: | ||
| 149 | - head_velocity, head_distance = self.estimate_head_velocity(self.coord_vel, self.timestamp) | ||
| 150 | - self.velocity_vector.append(head_velocity) | ||
| 151 | - | ||
| 152 | - del self.coord_vel[0] | ||
| 153 | - del self.timestamp[0] | ||
| 154 | - | ||
| 155 | - if len(self.velocity_vector) >= 30: | ||
| 156 | - self.velocity_std = np.std(self.velocity_vector) | ||
| 157 | - del self.velocity_vector[0] | ||
| 158 | - | ||
| 159 | - if self.velocity_std > const.ROBOT_HEAD_VELOCITY_THRESHOLD: | ||
| 160 | - print('Velocity threshold activated') | ||
| 161 | - return False | ||
| 162 | - else: | ||
| 163 | - return True | ||
| 164 | - | ||
| 165 | - return False | ||
| 166 | - | ||
| 167 | - def compute_head_move_compensation(self, current_head, m_change_robot_to_head): | ||
| 168 | - """ | ||
| 169 | - Estimates the new robot position to reach the target | ||
| 170 | - """ | ||
| 171 | - M_current_head = dco.coordinates_to_transformation_matrix( | ||
| 172 | - position=current_head[:3], | ||
| 173 | - orientation=current_head[3:6], | ||
| 174 | - axes='rzyx', | ||
| 175 | - ) | ||
| 176 | - m_robot_new = M_current_head @ m_change_robot_to_head | ||
| 177 | - | ||
| 178 | - translation, angles_as_deg = dco.transformation_matrix_to_coordinates(m_robot_new, axes='sxyz') | ||
| 179 | - new_robot_position = list(translation) + list(angles_as_deg) | ||
| 180 | - | ||
| 181 | - return new_robot_position | ||
| 182 | - | ||
| 183 | - def estimate_head_center(self, tracker, current_head): | ||
| 184 | - """ | ||
| 185 | - Estimates the actual head center position using fiducials | ||
| 186 | - """ | ||
| 187 | - m_probe_head_left, m_probe_head_right, m_probe_head_nasion = tracker.GetMatrixTrackerFiducials() | ||
| 188 | - m_current_head = dcr.compute_marker_transformation(np.array([current_head]), 0) | ||
| 189 | - | ||
| 190 | - m_ear_left_new = m_current_head @ m_probe_head_left | ||
| 191 | - m_ear_right_new = m_current_head @ m_probe_head_right | ||
| 192 | - | ||
| 193 | - return (m_ear_left_new[:3, -1] + m_ear_right_new[:3, -1])/2 | ||
| 194 | - | ||
| 195 | - def correction_distance_calculation_target(self, coord_inv, actual_point): | ||
| 196 | - """ | ||
| 197 | - Estimates the Euclidean distance between the actual position and the target | ||
| 198 | - """ | ||
| 199 | - correction_distance_compensation = np.sqrt((coord_inv[0]-actual_point[0]) ** 2 + | ||
| 200 | - (coord_inv[1]-actual_point[1]) ** 2 + | ||
| 201 | - (coord_inv[2]-actual_point[2]) ** 2) | ||
| 202 | - | ||
| 203 | - return correction_distance_compensation | ||
| 204 | - | ||
| 205 | - def estimate_robot_target_length(self, robot_target): | ||
| 206 | - """ | ||
| 207 | - Estimates the length of the 3D vector of the robot target | ||
| 208 | - """ | ||
| 209 | - robot_target_length = np.sqrt(robot_target[0] ** 2 + robot_target[1] ** 2 + robot_target[2] ** 2) | ||
| 210 | - | ||
| 211 | - return robot_target_length | ||
| 212 | - |
invesalius/data/trackers.py
| @@ -18,6 +18,7 @@ | @@ -18,6 +18,7 @@ | ||
| 18 | #-------------------------------------------------------------------------- | 18 | #-------------------------------------------------------------------------- |
| 19 | import invesalius.constants as const | 19 | import invesalius.constants as const |
| 20 | import invesalius.gui.dialogs as dlg | 20 | import invesalius.gui.dialogs as dlg |
| 21 | +from invesalius.pubsub import pub as Publisher | ||
| 21 | # TODO: Disconnect tracker when a new one is connected | 22 | # TODO: Disconnect tracker when a new one is connected |
| 22 | # TODO: Test if there are too many prints when connection fails | 23 | # TODO: Test if there are too many prints when connection fails |
| 23 | # TODO: Redesign error messages. No point in having "Could not connect to default tracker" in all trackers | 24 | # TODO: Redesign error messages. No point in having "Could not connect to default tracker" in all trackers |
| @@ -223,42 +224,23 @@ def OptitrackTracker(tracker_id): | @@ -223,42 +224,23 @@ def OptitrackTracker(tracker_id): | ||
| 223 | print('#####') | 224 | print('#####') |
| 224 | return trck_init, lib_mode | 225 | return trck_init, lib_mode |
| 225 | 226 | ||
| 226 | -def ElfinRobot(robot_IP): | ||
| 227 | - trck_init = None | ||
| 228 | - try: | ||
| 229 | - import invesalius.data.elfin as elfin | ||
| 230 | - print("Trying to connect Robot via: ", robot_IP) | ||
| 231 | - trck_init = elfin.Elfin_Server(robot_IP, const.ROBOT_ElFIN_PORT) | ||
| 232 | - trck_init.Initialize() | ||
| 233 | - lib_mode = 'wrapper' | ||
| 234 | - print('Connect to elfin robot tracking device.') | ||
| 235 | - | ||
| 236 | - except: | ||
| 237 | - lib_mode = 'error' | ||
| 238 | - trck_init = None | ||
| 239 | - print('Could not connect to elfin robot tracker.') | ||
| 240 | - | ||
| 241 | - # return tracker initialization variable and type of connection | ||
| 242 | - return trck_init, lib_mode | ||
| 243 | - | ||
| 244 | def RobotTracker(tracker_id): | 227 | def RobotTracker(tracker_id): |
| 245 | from wx import ID_OK | 228 | from wx import ID_OK |
| 246 | - trck_init = None | ||
| 247 | - trck_init_robot = None | ||
| 248 | - tracker_id = None | 229 | + |
| 249 | dlg_device = dlg.SetTrackerDeviceToRobot() | 230 | dlg_device = dlg.SetTrackerDeviceToRobot() |
| 250 | if dlg_device.ShowModal() == ID_OK: | 231 | if dlg_device.ShowModal() == ID_OK: |
| 251 | tracker_id = dlg_device.GetValue() | 232 | tracker_id = dlg_device.GetValue() |
| 252 | if tracker_id: | 233 | if tracker_id: |
| 253 | - trck_connection = TrackerConnection(tracker_id, None, 'connect') | ||
| 254 | - if trck_connection[0]: | 234 | + trck_init = TrackerConnection(tracker_id, None, 'connect') |
| 235 | + if trck_init[0]: | ||
| 255 | dlg_ip = dlg.SetRobotIP() | 236 | dlg_ip = dlg.SetRobotIP() |
| 256 | if dlg_ip.ShowModal() == ID_OK: | 237 | if dlg_ip.ShowModal() == ID_OK: |
| 257 | robot_IP = dlg_ip.GetValue() | 238 | robot_IP = dlg_ip.GetValue() |
| 258 | - trck_init = trck_connection | ||
| 259 | - trck_init_robot = ElfinRobot(robot_IP) | 239 | + Publisher.sendMessage('Connect to robot', robot_IP=robot_IP) |
| 240 | + | ||
| 241 | + return trck_init, tracker_id | ||
| 260 | 242 | ||
| 261 | - return [(trck_init, trck_init_robot), tracker_id] | 243 | + return None, None |
| 262 | 244 | ||
| 263 | def DebugTrackerRandom(tracker_id): | 245 | def DebugTrackerRandom(tracker_id): |
| 264 | trck_init = True | 246 | trck_init = True |
| @@ -392,8 +374,9 @@ def DisconnectTracker(tracker_id, trck_init): | @@ -392,8 +374,9 @@ def DisconnectTracker(tracker_id, trck_init): | ||
| 392 | lib_mode = 'serial' | 374 | lib_mode = 'serial' |
| 393 | print('Tracker disconnected.') | 375 | print('Tracker disconnected.') |
| 394 | elif tracker_id == const.ROBOT: | 376 | elif tracker_id == const.ROBOT: |
| 395 | - trck_init[0][0].Close() | ||
| 396 | - trck_init[1][0].Close() | 377 | + Publisher.sendMessage('Reset robot', data=None) |
| 378 | + if trck_init[1] == const.DEBUGTRACKRANDOM or trck_init[1] == const.DEBUGTRACKAPPROACH: | ||
| 379 | + trck_init[0].Close() | ||
| 397 | trck_init = False | 380 | trck_init = False |
| 398 | lib_mode = 'wrapper' | 381 | lib_mode = 'wrapper' |
| 399 | print('Tracker disconnected.') | 382 | print('Tracker disconnected.') |
invesalius/gui/dialogs.py
| @@ -57,8 +57,6 @@ except ImportError: | @@ -57,8 +57,6 @@ except ImportError: | ||
| 57 | from wx import AboutDialogInfo, AboutBox | 57 | from wx import AboutDialogInfo, AboutBox |
| 58 | 58 | ||
| 59 | import invesalius.constants as const | 59 | import invesalius.constants as const |
| 60 | -import invesalius.data.coordinates as dco | ||
| 61 | -import invesalius.data.transformations as tr | ||
| 62 | import invesalius.gui.widgets.gradient as grad | 60 | import invesalius.gui.widgets.gradient as grad |
| 63 | import invesalius.session as ses | 61 | import invesalius.session as ses |
| 64 | import invesalius.utils as utils | 62 | import invesalius.utils as utils |
| @@ -3597,11 +3595,7 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3597,11 +3595,7 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
| 3597 | # and not change the function to the point of potentially breaking it.) | 3595 | # and not change the function to the point of potentially breaking it.) |
| 3598 | # | 3596 | # |
| 3599 | if self.obj_ref_id and fiducial_index == 4: | 3597 | if self.obj_ref_id and fiducial_index == 4: |
| 3600 | - if self.tracker_id == const.ROBOT: | ||
| 3601 | - trck_init_robot = self.trk_init[1][0] | ||
| 3602 | - coord = trck_init_robot.Run() | ||
| 3603 | - else: | ||
| 3604 | - coord = coord_raw[self.obj_ref_id, :] | 3598 | + coord = coord_raw[self.obj_ref_id, :] |
| 3605 | else: | 3599 | else: |
| 3606 | coord = coord_raw[0, :] | 3600 | coord = coord_raw[0, :] |
| 3607 | 3601 | ||
| @@ -4323,7 +4317,7 @@ class SetTrackerDeviceToRobot(wx.Dialog): | @@ -4323,7 +4317,7 @@ class SetTrackerDeviceToRobot(wx.Dialog): | ||
| 4323 | def _init_gui(self): | 4317 | def _init_gui(self): |
| 4324 | # ComboBox for spatial tracker device selection | 4318 | # ComboBox for spatial tracker device selection |
| 4325 | tooltip = wx.ToolTip(_("Choose the tracking device")) | 4319 | tooltip = wx.ToolTip(_("Choose the tracking device")) |
| 4326 | - trackers = const.TRACKERS | 4320 | + trackers = const.TRACKERS.copy() |
| 4327 | if not ses.Session().debug: | 4321 | if not ses.Session().debug: |
| 4328 | del trackers[-3:] | 4322 | del trackers[-3:] |
| 4329 | tracker_options = [_("Select tracker:")] + trackers | 4323 | tracker_options = [_("Select tracker:")] + trackers |
| @@ -4425,10 +4419,8 @@ class CreateTransformationMatrixRobot(wx.Dialog): | @@ -4425,10 +4419,8 @@ class CreateTransformationMatrixRobot(wx.Dialog): | ||
| 4425 | M_robot_2_tracker is created by an affine transformation. Robot TCP should be calibrated to the center of the tracker marker | 4419 | M_robot_2_tracker is created by an affine transformation. Robot TCP should be calibrated to the center of the tracker marker |
| 4426 | ''' | 4420 | ''' |
| 4427 | #TODO: make aboutbox | 4421 | #TODO: make aboutbox |
| 4428 | - self.tracker_coord = [] | ||
| 4429 | - self.tracker_angles = [] | ||
| 4430 | - self.robot_coord = [] | ||
| 4431 | - self.robot_angles = [] | 4422 | + self.matrix_tracker_to_robot = [] |
| 4423 | + self.robot_status = False | ||
| 4432 | 4424 | ||
| 4433 | self.tracker = tracker | 4425 | self.tracker = tracker |
| 4434 | 4426 | ||
| @@ -4470,6 +4462,8 @@ class CreateTransformationMatrixRobot(wx.Dialog): | @@ -4470,6 +4462,8 @@ class CreateTransformationMatrixRobot(wx.Dialog): | ||
| 4470 | 4462 | ||
| 4471 | btn_load = wx.Button(self, -1, label=_('Load'), size=wx.Size(65, 23)) | 4463 | btn_load = wx.Button(self, -1, label=_('Load'), size=wx.Size(65, 23)) |
| 4472 | btn_load.Bind(wx.EVT_BUTTON, self.OnLoadReg) | 4464 | btn_load.Bind(wx.EVT_BUTTON, self.OnLoadReg) |
| 4465 | + btn_load.Enable(False) | ||
| 4466 | + self.btn_load = btn_load | ||
| 4473 | 4467 | ||
| 4474 | # Create a horizontal sizers | 4468 | # Create a horizontal sizers |
| 4475 | border = 1 | 4469 | border = 1 |
| @@ -4524,16 +4518,17 @@ class CreateTransformationMatrixRobot(wx.Dialog): | @@ -4524,16 +4518,17 @@ class CreateTransformationMatrixRobot(wx.Dialog): | ||
| 4524 | main_sizer.Fit(self) | 4518 | main_sizer.Fit(self) |
| 4525 | 4519 | ||
| 4526 | self.CenterOnParent() | 4520 | self.CenterOnParent() |
| 4521 | + self.__bind_events() | ||
| 4527 | 4522 | ||
| 4528 | - def affine_correg(self, tracker, robot): | ||
| 4529 | - m_change = tr.affine_matrix_from_points(robot[:].T, tracker[:].T, | ||
| 4530 | - shear=False, scale=False, usesvd=False) | ||
| 4531 | - return m_change | 4523 | + def __bind_events(self): |
| 4524 | + Publisher.subscribe(self.OnUpdateTransformationMatrix, 'Update robot transformation matrix') | ||
| 4525 | + Publisher.subscribe(self.OnCoordinatesAdquired, 'Coordinates for the robot transformation matrix collected') | ||
| 4526 | + Publisher.subscribe(self.OnRobotConnectionStatus, 'Robot connection status') | ||
| 4532 | 4527 | ||
| 4533 | def OnContinuousAcquisition(self, evt=None, btn=None): | 4528 | def OnContinuousAcquisition(self, evt=None, btn=None): |
| 4534 | value = btn.GetValue() | 4529 | value = btn.GetValue() |
| 4535 | if value: | 4530 | if value: |
| 4536 | - self.timer.Start(500) | 4531 | + self.timer.Start(100) |
| 4537 | else: | 4532 | else: |
| 4538 | self.timer.Stop() | 4533 | self.timer.Stop() |
| 4539 | 4534 | ||
| @@ -4541,56 +4536,50 @@ class CreateTransformationMatrixRobot(wx.Dialog): | @@ -4541,56 +4536,50 @@ class CreateTransformationMatrixRobot(wx.Dialog): | ||
| 4541 | self.OnCreatePoint(evt=None) | 4536 | self.OnCreatePoint(evt=None) |
| 4542 | 4537 | ||
| 4543 | def OnCreatePoint(self, evt): | 4538 | def OnCreatePoint(self, evt): |
| 4544 | - coord_raw, markers_flag = self.tracker.TrackerCoordinates.GetCoordinates() | ||
| 4545 | - #robot thread is not initialized yet | ||
| 4546 | - coord_raw_robot = self.tracker.trk_init[0][1][0].Run() | ||
| 4547 | - coord_raw_tracker_obj = coord_raw[3] | ||
| 4548 | - | ||
| 4549 | - if markers_flag[2]: | ||
| 4550 | - self.tracker_coord.append(coord_raw_tracker_obj[:3]) | ||
| 4551 | - self.tracker_angles.append(coord_raw_tracker_obj[3:]) | ||
| 4552 | - self.robot_coord.append(coord_raw_robot[:3]) | ||
| 4553 | - self.robot_angles.append(coord_raw_robot[3:]) | ||
| 4554 | - self.txt_number.SetLabel(str(int(self.txt_number.GetLabel())+1)) | ||
| 4555 | - else: | ||
| 4556 | - print('Cannot detect the coil markers, pls try again') | 4539 | + Publisher.sendMessage('Collect coordinates for the robot transformation matrix', data=None) |
| 4540 | + | ||
| 4541 | + def OnCoordinatesAdquired(self): | ||
| 4542 | + self.txt_number.SetLabel(str(int(self.txt_number.GetLabel())+1)) | ||
| 4557 | 4543 | ||
| 4558 | - if len(self.tracker_coord) >= 3: | 4544 | + if self.robot_status and int(self.txt_number.GetLabel()) >= 3: |
| 4559 | self.btn_apply_reg.Enable(True) | 4545 | self.btn_apply_reg.Enable(True) |
| 4560 | 4546 | ||
| 4547 | + def OnRobotConnectionStatus(self, data): | ||
| 4548 | + self.robot_status = data | ||
| 4549 | + if self.robot_status: | ||
| 4550 | + self.btn_load.Enable(True) | ||
| 4551 | + if int(self.txt_number.GetLabel()) >= 3: | ||
| 4552 | + self.btn_apply_reg.Enable(True) | ||
| 4553 | + | ||
| 4561 | def OnReset(self, evt): | 4554 | def OnReset(self, evt): |
| 4555 | + Publisher.sendMessage('Reset coordinates collection for the robot transformation matrix', data=None) | ||
| 4562 | if self.btn_cont_point: | 4556 | if self.btn_cont_point: |
| 4563 | self.btn_cont_point.SetValue(False) | 4557 | self.btn_cont_point.SetValue(False) |
| 4564 | self.OnContinuousAcquisition(evt=None, btn=self.btn_cont_point) | 4558 | self.OnContinuousAcquisition(evt=None, btn=self.btn_cont_point) |
| 4565 | 4559 | ||
| 4566 | - self.tracker_coord = [] | ||
| 4567 | - self.tracker_angles = [] | ||
| 4568 | - self.robot_coord = [] | ||
| 4569 | - self.robot_angles = [] | ||
| 4570 | - self.M_tracker_2_robot = [] | ||
| 4571 | self.txt_number.SetLabel('0') | 4560 | self.txt_number.SetLabel('0') |
| 4572 | 4561 | ||
| 4573 | self.btn_apply_reg.Enable(False) | 4562 | self.btn_apply_reg.Enable(False) |
| 4574 | self.btn_save.Enable(False) | 4563 | self.btn_save.Enable(False) |
| 4575 | self.btn_ok.Enable(False) | 4564 | self.btn_ok.Enable(False) |
| 4576 | 4565 | ||
| 4566 | + self.matrix_tracker_to_robot = [] | ||
| 4567 | + | ||
| 4577 | def OnApply(self, evt): | 4568 | def OnApply(self, evt): |
| 4578 | if self.btn_cont_point: | 4569 | if self.btn_cont_point: |
| 4579 | self.btn_cont_point.SetValue(False) | 4570 | self.btn_cont_point.SetValue(False) |
| 4580 | self.OnContinuousAcquisition(evt=None, btn=self.btn_cont_point) | 4571 | self.OnContinuousAcquisition(evt=None, btn=self.btn_cont_point) |
| 4581 | 4572 | ||
| 4582 | - tracker_coord = np.array(self.tracker_coord) | ||
| 4583 | - robot_coord = np.array(self.robot_coord) | ||
| 4584 | - | ||
| 4585 | - M_robot_2_tracker = self.affine_correg(tracker_coord, robot_coord) | ||
| 4586 | - M_tracker_2_robot = tr.inverse_matrix(M_robot_2_tracker) | ||
| 4587 | - self.M_tracker_2_robot = M_tracker_2_robot | 4573 | + Publisher.sendMessage('Robot transformation matrix estimation', data=None) |
| 4588 | 4574 | ||
| 4589 | self.btn_save.Enable(True) | 4575 | self.btn_save.Enable(True) |
| 4590 | self.btn_ok.Enable(True) | 4576 | self.btn_ok.Enable(True) |
| 4591 | 4577 | ||
| 4592 | #TODO: make a colored circle to sinalize that the transformation was made (green) (red if not) | 4578 | #TODO: make a colored circle to sinalize that the transformation was made (green) (red if not) |
| 4593 | 4579 | ||
| 4580 | + def OnUpdateTransformationMatrix(self, data): | ||
| 4581 | + self.matrix_tracker_to_robot = np.array(data) | ||
| 4582 | + | ||
| 4594 | def OnSaveReg(self, evt): | 4583 | def OnSaveReg(self, evt): |
| 4595 | filename = ShowLoadSaveDialog(message=_(u"Save robot transformation file as..."), | 4584 | filename = ShowLoadSaveDialog(message=_(u"Save robot transformation file as..."), |
| 4596 | wildcard=_("Robot transformation files (*.rbtf)|*.rbtf"), | 4585 | wildcard=_("Robot transformation files (*.rbtf)|*.rbtf"), |
| @@ -4598,10 +4587,10 @@ class CreateTransformationMatrixRobot(wx.Dialog): | @@ -4598,10 +4587,10 @@ class CreateTransformationMatrixRobot(wx.Dialog): | ||
| 4598 | default_filename="robottransform.rbtf", save_ext="rbtf") | 4587 | default_filename="robottransform.rbtf", save_ext="rbtf") |
| 4599 | 4588 | ||
| 4600 | if filename: | 4589 | if filename: |
| 4601 | - if self.M_tracker_2_robot is not None: | 4590 | + if self.matrix_tracker_to_robot is not None: |
| 4602 | with open(filename, 'w', newline='') as file: | 4591 | with open(filename, 'w', newline='') as file: |
| 4603 | writer = csv.writer(file, delimiter='\t') | 4592 | writer = csv.writer(file, delimiter='\t') |
| 4604 | - writer.writerows(self.M_tracker_2_robot) | 4593 | + writer.writerows(np.vstack(self.matrix_tracker_to_robot).tolist()) |
| 4605 | 4594 | ||
| 4606 | def OnLoadReg(self, evt): | 4595 | def OnLoadReg(self, evt): |
| 4607 | filename = ShowLoadSaveDialog(message=_(u"Load robot transformation"), | 4596 | filename = ShowLoadSaveDialog(message=_(u"Load robot transformation"), |
| @@ -4611,12 +4600,12 @@ class CreateTransformationMatrixRobot(wx.Dialog): | @@ -4611,12 +4600,12 @@ class CreateTransformationMatrixRobot(wx.Dialog): | ||
| 4611 | reader = csv.reader(file, delimiter='\t') | 4600 | reader = csv.reader(file, delimiter='\t') |
| 4612 | content = [row for row in reader] | 4601 | content = [row for row in reader] |
| 4613 | 4602 | ||
| 4614 | - self.M_tracker_2_robot = np.vstack(list(np.float_(content))) | ||
| 4615 | - print("Matrix tracker to robot:", self.M_tracker_2_robot) | ||
| 4616 | - self.btn_ok.Enable(True) | 4603 | + self.matrix_tracker_to_robot = np.vstack(list(np.float_(content))) |
| 4604 | + print("Matrix tracker to robot:", self.matrix_tracker_to_robot) | ||
| 4605 | + Publisher.sendMessage('Load robot transformation matrix', data=self.matrix_tracker_to_robot.tolist()) | ||
| 4606 | + if self.robot_status: | ||
| 4607 | + self.btn_ok.Enable(True) | ||
| 4617 | 4608 | ||
| 4618 | - def GetValue(self): | ||
| 4619 | - return self.M_tracker_2_robot | ||
| 4620 | 4609 | ||
| 4621 | class SetNDIconfigs(wx.Dialog): | 4610 | class SetNDIconfigs(wx.Dialog): |
| 4622 | def __init__(self, title=_("Setting NDI polaris configs:")): | 4611 | def __init__(self, title=_("Setting NDI polaris configs:")): |
invesalius/gui/task_navigator.py
| @@ -20,10 +20,7 @@ | @@ -20,10 +20,7 @@ | ||
| 20 | import dataclasses | 20 | import dataclasses |
| 21 | from functools import partial | 21 | from functools import partial |
| 22 | import itertools | 22 | import itertools |
| 23 | -import csv | ||
| 24 | -import queue | ||
| 25 | import time | 23 | import time |
| 26 | -import threading | ||
| 27 | 24 | ||
| 28 | import nibabel as nb | 25 | import nibabel as nb |
| 29 | import numpy as np | 26 | import numpy as np |
| @@ -41,7 +38,6 @@ except ImportError: | @@ -41,7 +38,6 @@ except ImportError: | ||
| 41 | has_robot = False | 38 | has_robot = False |
| 42 | 39 | ||
| 43 | import wx | 40 | import wx |
| 44 | -import vtk | ||
| 45 | 41 | ||
| 46 | try: | 42 | try: |
| 47 | import wx.lib.agw.foldpanelbar as fpb | 43 | import wx.lib.agw.foldpanelbar as fpb |
| @@ -51,7 +47,6 @@ except ImportError: | @@ -51,7 +47,6 @@ except ImportError: | ||
| 51 | import wx.lib.colourselect as csel | 47 | import wx.lib.colourselect as csel |
| 52 | import wx.lib.masked.numctrl | 48 | import wx.lib.masked.numctrl |
| 53 | from invesalius.pubsub import pub as Publisher | 49 | from invesalius.pubsub import pub as Publisher |
| 54 | -from time import sleep | ||
| 55 | 50 | ||
| 56 | import invesalius.constants as const | 51 | import invesalius.constants as const |
| 57 | 52 | ||
| @@ -64,6 +59,7 @@ import invesalius.data.tractography as dti | @@ -64,6 +59,7 @@ import invesalius.data.tractography as dti | ||
| 64 | import invesalius.data.record_coords as rec | 59 | import invesalius.data.record_coords as rec |
| 65 | import invesalius.data.vtk_utils as vtk_utils | 60 | import invesalius.data.vtk_utils as vtk_utils |
| 66 | import invesalius.data.bases as db | 61 | import invesalius.data.bases as db |
| 62 | +import invesalius.data.coregistration as dcr | ||
| 67 | import invesalius.gui.dialogs as dlg | 63 | import invesalius.gui.dialogs as dlg |
| 68 | import invesalius.project as prj | 64 | import invesalius.project as prj |
| 69 | import invesalius.session as ses | 65 | import invesalius.session as ses |
| @@ -73,7 +69,6 @@ from invesalius.gui import utils as gui_utils | @@ -73,7 +69,6 @@ from invesalius.gui import utils as gui_utils | ||
| 73 | from invesalius.navigation.icp import ICP | 69 | from invesalius.navigation.icp import ICP |
| 74 | from invesalius.navigation.navigation import Navigation | 70 | from invesalius.navigation.navigation import Navigation |
| 75 | from invesalius.navigation.tracker import Tracker | 71 | from invesalius.navigation.tracker import Tracker |
| 76 | -from invesalius.navigation.robot import Robot | ||
| 77 | from invesalius.data.converters import to_vtk | 72 | from invesalius.data.converters import to_vtk |
| 78 | 73 | ||
| 79 | from invesalius.net.neuronavigation_api import NeuronavigationApi | 74 | from invesalius.net.neuronavigation_api import NeuronavigationApi |
| @@ -176,7 +171,7 @@ class InnerFoldPanel(wx.Panel): | @@ -176,7 +171,7 @@ class InnerFoldPanel(wx.Panel): | ||
| 176 | # | 171 | # |
| 177 | tracker = Tracker() | 172 | tracker = Tracker() |
| 178 | pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None | 173 | pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None |
| 179 | - | 174 | + icp = ICP() |
| 180 | neuronavigation_api = NeuronavigationApi() | 175 | neuronavigation_api = NeuronavigationApi() |
| 181 | navigation = Navigation( | 176 | navigation = Navigation( |
| 182 | pedal_connection=pedal_connection, | 177 | pedal_connection=pedal_connection, |
| @@ -194,6 +189,7 @@ class InnerFoldPanel(wx.Panel): | @@ -194,6 +189,7 @@ class InnerFoldPanel(wx.Panel): | ||
| 194 | parent=item, | 189 | parent=item, |
| 195 | navigation=navigation, | 190 | navigation=navigation, |
| 196 | tracker=tracker, | 191 | tracker=tracker, |
| 192 | + icp=icp, | ||
| 197 | pedal_connection=pedal_connection, | 193 | pedal_connection=pedal_connection, |
| 198 | neuronavigation_api=neuronavigation_api, | 194 | neuronavigation_api=neuronavigation_api, |
| 199 | ) | 195 | ) |
| @@ -213,7 +209,7 @@ class InnerFoldPanel(wx.Panel): | @@ -213,7 +209,7 @@ class InnerFoldPanel(wx.Panel): | ||
| 213 | 209 | ||
| 214 | # Fold 3 - Markers panel | 210 | # Fold 3 - Markers panel |
| 215 | item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) | 211 | item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) |
| 216 | - mtw = MarkersPanel(item, navigation, tracker) | 212 | + mtw = MarkersPanel(item, navigation, tracker, icp) |
| 217 | 213 | ||
| 218 | fold_panel.ApplyCaptionStyle(item, style) | 214 | fold_panel.ApplyCaptionStyle(item, style) |
| 219 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, | 215 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, |
| @@ -352,7 +348,7 @@ class InnerFoldPanel(wx.Panel): | @@ -352,7 +348,7 @@ class InnerFoldPanel(wx.Panel): | ||
| 352 | 348 | ||
| 353 | 349 | ||
| 354 | class NeuronavigationPanel(wx.Panel): | 350 | class NeuronavigationPanel(wx.Panel): |
| 355 | - def __init__(self, parent, navigation, tracker, pedal_connection, neuronavigation_api): | 351 | + def __init__(self, parent, navigation, tracker, icp, pedal_connection, neuronavigation_api): |
| 356 | wx.Panel.__init__(self, parent) | 352 | wx.Panel.__init__(self, parent) |
| 357 | try: | 353 | try: |
| 358 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) | 354 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) |
| @@ -369,9 +365,8 @@ class NeuronavigationPanel(wx.Panel): | @@ -369,9 +365,8 @@ class NeuronavigationPanel(wx.Panel): | ||
| 369 | self.neuronavigation_api = neuronavigation_api | 365 | self.neuronavigation_api = neuronavigation_api |
| 370 | 366 | ||
| 371 | self.navigation = navigation | 367 | self.navigation = navigation |
| 372 | - self.icp = ICP() | 368 | + self.icp = icp |
| 373 | self.tracker = tracker | 369 | self.tracker = tracker |
| 374 | - self.robot = Robot(tracker) | ||
| 375 | 370 | ||
| 376 | self.nav_status = False | 371 | self.nav_status = False |
| 377 | self.tracker_fiducial_being_set = None | 372 | self.tracker_fiducial_being_set = None |
| @@ -558,6 +553,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -558,6 +553,7 @@ class NeuronavigationPanel(wx.Panel): | ||
| 558 | Publisher.subscribe(self.UpdateTarget, 'Update target') | 553 | Publisher.subscribe(self.UpdateTarget, 'Update target') |
| 559 | Publisher.subscribe(self.OnStartNavigation, 'Start navigation') | 554 | Publisher.subscribe(self.OnStartNavigation, 'Start navigation') |
| 560 | Publisher.subscribe(self.OnStopNavigation, 'Stop navigation') | 555 | Publisher.subscribe(self.OnStopNavigation, 'Stop navigation') |
| 556 | + Publisher.subscribe(self.OnDialogRobotDestroy, 'Dialog robot destroy') | ||
| 561 | 557 | ||
| 562 | def LoadImageFiducials(self, label, coord): | 558 | def LoadImageFiducials(self, label, coord): |
| 563 | fiducial = self.GetFiducialByAttribute(const.IMAGE_FIDUCIALS, 'label', label) | 559 | fiducial = self.GetFiducialByAttribute(const.IMAGE_FIDUCIALS, 'label', label) |
| @@ -671,8 +667,6 @@ class NeuronavigationPanel(wx.Panel): | @@ -671,8 +667,6 @@ class NeuronavigationPanel(wx.Panel): | ||
| 671 | self.checkbox_icp.SetValue(False) | 667 | self.checkbox_icp.SetValue(False) |
| 672 | 668 | ||
| 673 | def OnDisconnectTracker(self): | 669 | def OnDisconnectTracker(self): |
| 674 | - if self.tracker.tracker_id == const.ROBOT: | ||
| 675 | - self.robot.StopRobotThreadNavigation() | ||
| 676 | self.tracker.DisconnectTracker() | 670 | self.tracker.DisconnectTracker() |
| 677 | self.ResetICP() | 671 | self.ResetICP() |
| 678 | self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_fiducial[3:6], self.txtctrl_fre) | 672 | self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_fiducial[3:6], self.txtctrl_fre) |
| @@ -691,7 +685,12 @@ class NeuronavigationPanel(wx.Panel): | @@ -691,7 +685,12 @@ class NeuronavigationPanel(wx.Panel): | ||
| 691 | 685 | ||
| 692 | self.tracker.SetTracker(choice) | 686 | self.tracker.SetTracker(choice) |
| 693 | if self.tracker.tracker_id == const.ROBOT: | 687 | if self.tracker.tracker_id == const.ROBOT: |
| 694 | - self.tracker.ConnectToRobot(self.navigation, self.tracker, self.robot) | 688 | + self.dlg_correg_robot = dlg.CreateTransformationMatrixRobot(self.tracker) |
| 689 | + if self.dlg_correg_robot.ShowModal() == wx.ID_OK: | ||
| 690 | + Publisher.sendMessage('Robot navigation mode', robot_mode=True) | ||
| 691 | + else: | ||
| 692 | + Publisher.sendMessage('Disconnect tracker') | ||
| 693 | + wx.MessageBox(_("Not possible to connect to the robot."), _("InVesalius 3")) | ||
| 695 | 694 | ||
| 696 | self.ResetICP() | 695 | self.ResetICP() |
| 697 | self.tracker.UpdateUI(ctrl, self.numctrls_fiducial[3:6], self.txtctrl_fre) | 696 | self.tracker.UpdateUI(ctrl, self.numctrls_fiducial[3:6], self.txtctrl_fre) |
| @@ -776,8 +775,8 @@ class NeuronavigationPanel(wx.Panel): | @@ -776,8 +775,8 @@ class NeuronavigationPanel(wx.Panel): | ||
| 776 | 775 | ||
| 777 | self.navigation.StopNavigation() | 776 | self.navigation.StopNavigation() |
| 778 | if self.tracker.tracker_id == const.ROBOT: | 777 | if self.tracker.tracker_id == const.ROBOT: |
| 779 | - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False, | ||
| 780 | - m_change_robot_to_head=None) | 778 | + Publisher.sendMessage('Update robot target', robot_tracker_flag=False, |
| 779 | + target_index=None, target=None) | ||
| 781 | 780 | ||
| 782 | # Enable all navigation buttons | 781 | # Enable all navigation buttons |
| 783 | choice_ref.Enable(True) | 782 | choice_ref.Enable(True) |
| @@ -786,6 +785,10 @@ class NeuronavigationPanel(wx.Panel): | @@ -786,6 +785,10 @@ class NeuronavigationPanel(wx.Panel): | ||
| 786 | for btn_c in self.btns_set_fiducial: | 785 | for btn_c in self.btns_set_fiducial: |
| 787 | btn_c.Enable(True) | 786 | btn_c.Enable(True) |
| 788 | 787 | ||
| 788 | + def OnDialogRobotDestroy(self): | ||
| 789 | + if self.dlg_correg_robot: | ||
| 790 | + self.dlg_correg_robot.Destroy() | ||
| 791 | + | ||
| 789 | def CheckFiducialRegistrationError(self): | 792 | def CheckFiducialRegistrationError(self): |
| 790 | self.navigation.UpdateFiducialRegistrationError(self.tracker) | 793 | self.navigation.UpdateFiducialRegistrationError(self.tracker) |
| 791 | fre, fre_ok = self.navigation.GetFiducialRegistrationError(self.icp) | 794 | fre, fre_ok = self.navigation.GetFiducialRegistrationError(self.icp) |
| @@ -820,7 +823,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -820,7 +823,7 @@ class NeuronavigationPanel(wx.Panel): | ||
| 820 | for btn_c in self.btns_set_fiducial: | 823 | for btn_c in self.btns_set_fiducial: |
| 821 | btn_c.Enable(False) | 824 | btn_c.Enable(False) |
| 822 | 825 | ||
| 823 | - self.navigation.StartNavigation(self.tracker) | 826 | + self.navigation.EstimateTrackerToInVTransformationMatrix(self.tracker) |
| 824 | 827 | ||
| 825 | if not self.CheckFiducialRegistrationError(): | 828 | if not self.CheckFiducialRegistrationError(): |
| 826 | # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok | 829 | # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok |
| @@ -830,9 +833,11 @@ class NeuronavigationPanel(wx.Panel): | @@ -830,9 +833,11 @@ class NeuronavigationPanel(wx.Panel): | ||
| 830 | if self.icp.use_icp: | 833 | if self.icp.use_icp: |
| 831 | self.checkbox_icp.Enable(True) | 834 | self.checkbox_icp.Enable(True) |
| 832 | self.checkbox_icp.SetValue(True) | 835 | self.checkbox_icp.SetValue(True) |
| 833 | - # Update FRE once more after starting the navigation, due to the optional use of ICP, | ||
| 834 | - # which improves FRE. | ||
| 835 | - self.CheckFiducialRegistrationError() | 836 | + # Update FRE once more after starting the navigation, due to the optional use of ICP, |
| 837 | + # which improves FRE. | ||
| 838 | + self.CheckFiducialRegistrationError() | ||
| 839 | + | ||
| 840 | + self.navigation.StartNavigation(self.tracker) | ||
| 836 | 841 | ||
| 837 | def OnNavigate(self, evt, btn_nav): | 842 | def OnNavigate(self, evt, btn_nav): |
| 838 | select_tracker_elem = self.select_tracker_elem | 843 | select_tracker_elem = self.select_tracker_elem |
| @@ -881,7 +886,6 @@ class NeuronavigationPanel(wx.Panel): | @@ -881,7 +886,6 @@ class NeuronavigationPanel(wx.Panel): | ||
| 881 | ) | 886 | ) |
| 882 | self.tracker.__init__() | 887 | self.tracker.__init__() |
| 883 | self.icp.__init__() | 888 | self.icp.__init__() |
| 884 | - self.robot.__init__(self.tracker) | ||
| 885 | 889 | ||
| 886 | 890 | ||
| 887 | class ObjectRegistrationPanel(wx.Panel): | 891 | class ObjectRegistrationPanel(wx.Panel): |
| @@ -1248,20 +1252,7 @@ class MarkersPanel(wx.Panel): | @@ -1248,20 +1252,7 @@ class MarkersPanel(wx.Panel): | ||
| 1248 | if field.type is bool: | 1252 | if field.type is bool: |
| 1249 | setattr(self, field.name, str_val=='True') | 1253 | setattr(self, field.name, str_val=='True') |
| 1250 | 1254 | ||
| 1251 | - @dataclasses.dataclass | ||
| 1252 | - class Robot_Marker: | ||
| 1253 | - """Class for storing robot target.""" | ||
| 1254 | - m_robot_target : list = None | ||
| 1255 | - | ||
| 1256 | - @property | ||
| 1257 | - def robot_target_matrix(self): | ||
| 1258 | - return self.m_robot_target | ||
| 1259 | - | ||
| 1260 | - @robot_target_matrix.setter | ||
| 1261 | - def robot_target_matrix(self, new_m_robot_target): | ||
| 1262 | - self.m_robot_target = new_m_robot_target | ||
| 1263 | - | ||
| 1264 | - def __init__(self, parent, navigation, tracker): | 1255 | + def __init__(self, parent, navigation, tracker, icp): |
| 1265 | wx.Panel.__init__(self, parent) | 1256 | wx.Panel.__init__(self, parent) |
| 1266 | try: | 1257 | try: |
| 1267 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) | 1258 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) |
| @@ -1273,6 +1264,7 @@ class MarkersPanel(wx.Panel): | @@ -1273,6 +1264,7 @@ class MarkersPanel(wx.Panel): | ||
| 1273 | 1264 | ||
| 1274 | self.navigation = navigation | 1265 | self.navigation = navigation |
| 1275 | self.tracker = tracker | 1266 | self.tracker = tracker |
| 1267 | + self.icp = icp | ||
| 1276 | 1268 | ||
| 1277 | self.__bind_events() | 1269 | self.__bind_events() |
| 1278 | 1270 | ||
| @@ -1280,11 +1272,9 @@ class MarkersPanel(wx.Panel): | @@ -1280,11 +1272,9 @@ class MarkersPanel(wx.Panel): | ||
| 1280 | 1272 | ||
| 1281 | self.current_coord = [0, 0, 0, None, None, None] | 1273 | self.current_coord = [0, 0, 0, None, None, None] |
| 1282 | self.current_seed = 0, 0, 0 | 1274 | self.current_seed = 0, 0, 0 |
| 1283 | - self.current_robot_target_matrix = [None] * 9 | 1275 | + |
| 1284 | self.markers = [] | 1276 | self.markers = [] |
| 1285 | - self.robot_markers = [] | ||
| 1286 | self.nav_status = False | 1277 | self.nav_status = False |
| 1287 | - self.raw_target_robot = None, None | ||
| 1288 | 1278 | ||
| 1289 | self.marker_colour = const.MARKER_COLOUR | 1279 | self.marker_colour = const.MARKER_COLOUR |
| 1290 | self.marker_size = const.MARKER_SIZE | 1280 | self.marker_size = const.MARKER_SIZE |
| @@ -1383,7 +1373,6 @@ class MarkersPanel(wx.Panel): | @@ -1383,7 +1373,6 @@ class MarkersPanel(wx.Panel): | ||
| 1383 | Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') | 1373 | Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') |
| 1384 | Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts') | 1374 | Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts') |
| 1385 | Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed') | 1375 | Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed') |
| 1386 | - Publisher.subscribe(self.UpdateRobotCoordinates, 'Update raw coordinates') | ||
| 1387 | 1376 | ||
| 1388 | def __find_target_marker(self): | 1377 | def __find_target_marker(self): |
| 1389 | """ | 1378 | """ |
| @@ -1417,7 +1406,6 @@ class MarkersPanel(wx.Panel): | @@ -1417,7 +1406,6 @@ class MarkersPanel(wx.Panel): | ||
| 1417 | """ | 1406 | """ |
| 1418 | for i in reversed(index): | 1407 | for i in reversed(index): |
| 1419 | del self.markers[i] | 1408 | del self.markers[i] |
| 1420 | - del self.robot_markers[i] | ||
| 1421 | self.lc.DeleteItem(i) | 1409 | self.lc.DeleteItem(i) |
| 1422 | for n in range(0, self.lc.GetItemCount()): | 1410 | for n in range(0, self.lc.GetItemCount()): |
| 1423 | self.lc.SetItem(n, 0, str(n + 1)) | 1411 | self.lc.SetItem(n, 0, str(n + 1)) |
| @@ -1470,34 +1458,37 @@ class MarkersPanel(wx.Panel): | @@ -1470,34 +1458,37 @@ class MarkersPanel(wx.Panel): | ||
| 1470 | def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0), coord_offset_w=(0, 0, 0)): | 1458 | def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0), coord_offset_w=(0, 0, 0)): |
| 1471 | self.current_seed = coord_offset_w | 1459 | self.current_seed = coord_offset_w |
| 1472 | 1460 | ||
| 1473 | - def UpdateRobotCoordinates(self, coordinates_raw, markers_flag): | ||
| 1474 | - self.raw_target_robot = coordinates_raw[1], coordinates_raw[2] | ||
| 1475 | - | ||
| 1476 | def OnMouseRightDown(self, evt): | 1461 | def OnMouseRightDown(self, evt): |
| 1477 | # TODO: Enable the "Set as target" only when target is created with registered object | 1462 | # TODO: Enable the "Set as target" only when target is created with registered object |
| 1478 | menu_id = wx.Menu() | 1463 | menu_id = wx.Menu() |
| 1479 | edit_id = menu_id.Append(0, _('Edit label')) | 1464 | edit_id = menu_id.Append(0, _('Edit label')) |
| 1480 | menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerLabel, edit_id) | 1465 | menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerLabel, edit_id) |
| 1481 | - color_id = menu_id.Append(2, _('Edit color')) | 1466 | + color_id = menu_id.Append(1, _('Edit color')) |
| 1482 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetColor, color_id) | 1467 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetColor, color_id) |
| 1483 | menu_id.AppendSeparator() | 1468 | menu_id.AppendSeparator() |
| 1484 | - target_menu = menu_id.Append(1, _('Set as target')) | 1469 | + target_menu = menu_id.Append(2, _('Set as target')) |
| 1485 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) | 1470 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) |
| 1486 | menu_id.AppendSeparator() | 1471 | menu_id.AppendSeparator() |
| 1487 | - send_target_to_robot = menu_id.Append(3, _('Send target to robot')) | ||
| 1488 | - menu_id.Bind(wx.EVT_MENU, self.OnMenuSendTargetToRobot, send_target_to_robot) | ||
| 1489 | 1472 | ||
| 1490 | - if all([elem is not None for elem in self.markers[self.lc.GetFocusedItem()].coord[3:]]): | 1473 | + check_target_angles = all([elem is not None for elem in self.markers[self.lc.GetFocusedItem()].coord[3:]]) |
| 1474 | + # Enable "Send target to robot" button only if tracker is robot, if navigation is on and if target is not none | ||
| 1475 | + if self.tracker.tracker_id == const.ROBOT: | ||
| 1476 | + send_target_to_robot_compensation = menu_id.Append(3, _('Sets target to robot head move compensation')) | ||
| 1477 | + menu_id.Bind(wx.EVT_MENU, self.OnMenuSetRobotCompensation, send_target_to_robot_compensation) | ||
| 1478 | + send_target_to_robot = menu_id.Append(4, _('Send target from InVesalius to robot')) | ||
| 1479 | + menu_id.Bind(wx.EVT_MENU, self.OnMenuSendTargetToRobot, send_target_to_robot) | ||
| 1480 | + if self.nav_status and check_target_angles: | ||
| 1481 | + send_target_to_robot_compensation.Enable(True) | ||
| 1482 | + send_target_to_robot.Enable(True) | ||
| 1483 | + else: | ||
| 1484 | + send_target_to_robot_compensation.Enable(False) | ||
| 1485 | + send_target_to_robot.Enable(False) | ||
| 1486 | + | ||
| 1487 | + if check_target_angles: | ||
| 1491 | target_menu.Enable(True) | 1488 | target_menu.Enable(True) |
| 1492 | else: | 1489 | else: |
| 1493 | target_menu.Enable(False) | 1490 | target_menu.Enable(False) |
| 1494 | 1491 | ||
| 1495 | - # Enable "Send target to robot" button only if tracker is robot, if navigation is on and if target is not none | ||
| 1496 | - m_target_robot = np.array([self.robot_markers[self.lc.GetFocusedItem()].robot_target_matrix]) | ||
| 1497 | - if self.tracker.tracker_id == const.ROBOT and self.nav_status and m_target_robot.any(): | ||
| 1498 | - send_target_to_robot.Enable(True) | ||
| 1499 | - else: | ||
| 1500 | - send_target_to_robot.Enable(False) | ||
| 1501 | # TODO: Create the remove target option so the user can disable the target without removing the marker | 1492 | # TODO: Create the remove target option so the user can disable the target without removing the marker |
| 1502 | # target_menu_rem = menu_id.Append(3, _('Remove target')) | 1493 | # target_menu_rem = menu_id.Append(3, _('Remove target')) |
| 1503 | # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem) | 1494 | # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem) |
| @@ -1547,14 +1538,33 @@ class MarkersPanel(wx.Panel): | @@ -1547,14 +1538,33 @@ class MarkersPanel(wx.Panel): | ||
| 1547 | 1538 | ||
| 1548 | Publisher.sendMessage('Set new color', index=index, color=color_new) | 1539 | Publisher.sendMessage('Set new color', index=index, color=color_new) |
| 1549 | 1540 | ||
| 1541 | + def OnMenuSetRobotCompensation(self, evt): | ||
| 1542 | + if isinstance(evt, int): | ||
| 1543 | + self.lc.Focus(evt) | ||
| 1544 | + | ||
| 1545 | + Publisher.sendMessage('Reset robot process', data=None) | ||
| 1546 | + matrix_tracker_fiducials = self.tracker.GetMatrixTrackerFiducials() | ||
| 1547 | + Publisher.sendMessage('Update tracker fiducials matrix', | ||
| 1548 | + matrix_tracker_fiducials=matrix_tracker_fiducials) | ||
| 1549 | + Publisher.sendMessage('Update robot target', robot_tracker_flag=True, target_index=self.lc.GetFocusedItem(), target=None) | ||
| 1550 | + | ||
| 1550 | def OnMenuSendTargetToRobot(self, evt): | 1551 | def OnMenuSendTargetToRobot(self, evt): |
| 1551 | if isinstance(evt, int): | 1552 | if isinstance(evt, int): |
| 1552 | self.lc.Focus(evt) | 1553 | self.lc.Focus(evt) |
| 1554 | + index = self.lc.GetFocusedItem() | ||
| 1555 | + if index == -1: | ||
| 1556 | + wx.MessageBox(_("No data selected."), _("InVesalius 3")) | ||
| 1557 | + return | ||
| 1558 | + | ||
| 1559 | + Publisher.sendMessage('Reset robot process', data=None) | ||
| 1560 | + matrix_tracker_fiducials = self.tracker.GetMatrixTrackerFiducials() | ||
| 1561 | + Publisher.sendMessage('Update tracker fiducials matrix', | ||
| 1562 | + matrix_tracker_fiducials=matrix_tracker_fiducials) | ||
| 1553 | 1563 | ||
| 1554 | - m_target_robot = self.robot_markers[self.lc.GetFocusedItem()].robot_target_matrix | 1564 | + target_coord = self.markers[index].coord[:3] |
| 1565 | + target = dcr.image_to_tracker(self.navigation.m_change, target_coord, self.icp) | ||
| 1555 | 1566 | ||
| 1556 | - Publisher.sendMessage('Reset robot process') | ||
| 1557 | - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=True, m_change_robot_to_head=m_target_robot) | 1567 | + Publisher.sendMessage('Update robot target', robot_tracker_flag=True, target_index=self.lc.GetFocusedItem(), target=target.tolist()) |
| 1558 | 1568 | ||
| 1559 | def OnDeleteAllMarkers(self, evt=None): | 1569 | def OnDeleteAllMarkers(self, evt=None): |
| 1560 | if evt is not None: | 1570 | if evt is not None: |
| @@ -1566,9 +1576,11 @@ class MarkersPanel(wx.Panel): | @@ -1566,9 +1576,11 @@ class MarkersPanel(wx.Panel): | ||
| 1566 | Publisher.sendMessage('Disable or enable coil tracker', status=False) | 1576 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
| 1567 | if evt is not None: | 1577 | if evt is not None: |
| 1568 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) | 1578 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
| 1579 | + if self.tracker.tracker_id == const.ROBOT: | ||
| 1580 | + Publisher.sendMessage('Update robot target', robot_tracker_flag=False, | ||
| 1581 | + target_index=None, target=None) | ||
| 1569 | 1582 | ||
| 1570 | self.markers = [] | 1583 | self.markers = [] |
| 1571 | - self.robot_markers = [] | ||
| 1572 | Publisher.sendMessage('Remove all markers', indexes=self.lc.GetItemCount()) | 1584 | Publisher.sendMessage('Remove all markers', indexes=self.lc.GetItemCount()) |
| 1573 | self.lc.DeleteAllItems() | 1585 | self.lc.DeleteAllItems() |
| 1574 | Publisher.sendMessage('Stop Blink Marker', index='DeleteAll') | 1586 | Publisher.sendMessage('Stop Blink Marker', index='DeleteAll') |
| @@ -1596,8 +1608,8 @@ class MarkersPanel(wx.Panel): | @@ -1596,8 +1608,8 @@ class MarkersPanel(wx.Panel): | ||
| 1596 | if self.__find_target_marker() in index: | 1608 | if self.__find_target_marker() in index: |
| 1597 | Publisher.sendMessage('Disable or enable coil tracker', status=False) | 1609 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
| 1598 | if self.tracker.tracker_id == const.ROBOT: | 1610 | if self.tracker.tracker_id == const.ROBOT: |
| 1599 | - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False, | ||
| 1600 | - m_change_robot_to_head=[]) | 1611 | + Publisher.sendMessage('Update robot target', robot_tracker_flag=False, |
| 1612 | + target_index=None, target=None) | ||
| 1601 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) | 1613 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
| 1602 | 1614 | ||
| 1603 | self.__delete_multiple_markers(index) | 1615 | self.__delete_multiple_markers(index) |
| @@ -1702,12 +1714,11 @@ class MarkersPanel(wx.Panel): | @@ -1702,12 +1714,11 @@ class MarkersPanel(wx.Panel): | ||
| 1702 | new_marker.session_id = session_id or self.current_session | 1714 | new_marker.session_id = session_id or self.current_session |
| 1703 | 1715 | ||
| 1704 | if self.tracker.tracker_id == const.ROBOT and self.nav_status: | 1716 | if self.tracker.tracker_id == const.ROBOT and self.nav_status: |
| 1705 | - self.current_robot_target_matrix = db.compute_robot_to_head_matrix(self.raw_target_robot) | 1717 | + current_head_robot_target_status = True |
| 1706 | else: | 1718 | else: |
| 1707 | - self.current_robot_target_matrix = [None] * 9 | 1719 | + current_head_robot_target_status = False |
| 1708 | 1720 | ||
| 1709 | - new_robot_marker = self.Robot_Marker() | ||
| 1710 | - new_robot_marker.robot_target_matrix = self.current_robot_target_matrix | 1721 | + Publisher.sendMessage('Add marker to robot control', data=current_head_robot_target_status) |
| 1711 | 1722 | ||
| 1712 | # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added | 1723 | # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added |
| 1713 | if all([elem is not None for elem in new_marker.coord[3:]]): | 1724 | if all([elem is not None for elem in new_marker.coord[3:]]): |
| @@ -1723,7 +1734,6 @@ class MarkersPanel(wx.Panel): | @@ -1723,7 +1734,6 @@ class MarkersPanel(wx.Panel): | ||
| 1723 | 1734 | ||
| 1724 | 1735 | ||
| 1725 | self.markers.append(new_marker) | 1736 | self.markers.append(new_marker) |
| 1726 | - self.robot_markers.append(new_robot_marker) | ||
| 1727 | 1737 | ||
| 1728 | # Add item to list control in panel | 1738 | # Add item to list control in panel |
| 1729 | num_items = self.lc.GetItemCount() | 1739 | num_items = self.lc.GetItemCount() |
invesalius/navigation/icp.py
| @@ -21,10 +21,10 @@ import wx | @@ -21,10 +21,10 @@ import wx | ||
| 21 | 21 | ||
| 22 | import invesalius.data.bases as db | 22 | import invesalius.data.bases as db |
| 23 | import invesalius.gui.dialogs as dlg | 23 | import invesalius.gui.dialogs as dlg |
| 24 | -from invesalius.pubsub import pub as Publisher | ||
| 25 | 24 | ||
| 25 | +from invesalius.utils import Singleton | ||
| 26 | 26 | ||
| 27 | -class ICP(): | 27 | +class ICP(metaclass=Singleton): |
| 28 | def __init__(self): | 28 | def __init__(self): |
| 29 | self.use_icp = False | 29 | self.use_icp = False |
| 30 | self.m_icp = None | 30 | self.m_icp = None |
| @@ -35,7 +35,6 @@ class ICP(): | @@ -35,7 +35,6 @@ class ICP(): | ||
| 35 | 35 | ||
| 36 | if not self.use_icp: | 36 | if not self.use_icp: |
| 37 | if dlg.ICPcorregistration(navigation.fre): | 37 | if dlg.ICPcorregistration(navigation.fre): |
| 38 | - Publisher.sendMessage('Stop navigation') | ||
| 39 | use_icp, self.m_icp = self.OnICP(navigation, tracker, navigation.m_change) | 38 | use_icp, self.m_icp = self.OnICP(navigation, tracker, navigation.m_change) |
| 40 | if use_icp: | 39 | if use_icp: |
| 41 | self.icp_fre = db.calculate_fre(tracker.tracker_fiducials_raw, navigation.all_fiducials, | 40 | self.icp_fre = db.calculate_fre(tracker.tracker_fiducials_raw, navigation.all_fiducials, |
| @@ -43,7 +42,6 @@ class ICP(): | @@ -43,7 +42,6 @@ class ICP(): | ||
| 43 | self.SetICP(navigation, use_icp) | 42 | self.SetICP(navigation, use_icp) |
| 44 | else: | 43 | else: |
| 45 | print("ICP canceled") | 44 | print("ICP canceled") |
| 46 | - Publisher.sendMessage('Start navigation') | ||
| 47 | 45 | ||
| 48 | def OnICP(self, navigation, tracker, m_change): | 46 | def OnICP(self, navigation, tracker, m_change): |
| 49 | ref_mode_id = navigation.GetReferenceMode() | 47 | ref_mode_id = navigation.GetReferenceMode() |
invesalius/navigation/navigation.py
| @@ -34,6 +34,7 @@ import invesalius.data.tractography as dti | @@ -34,6 +34,7 @@ import invesalius.data.tractography as dti | ||
| 34 | import invesalius.data.transformations as tr | 34 | import invesalius.data.transformations as tr |
| 35 | import invesalius.data.vtk_utils as vtk_utils | 35 | import invesalius.data.vtk_utils as vtk_utils |
| 36 | from invesalius.pubsub import pub as Publisher | 36 | from invesalius.pubsub import pub as Publisher |
| 37 | +from invesalius.utils import Singleton | ||
| 37 | 38 | ||
| 38 | 39 | ||
| 39 | class QueueCustom(queue.Queue): | 40 | class QueueCustom(queue.Queue): |
| @@ -83,7 +84,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -83,7 +84,7 @@ class UpdateNavigationScene(threading.Thread): | ||
| 83 | 84 | ||
| 84 | threading.Thread.__init__(self, name='UpdateScene') | 85 | threading.Thread.__init__(self, name='UpdateScene') |
| 85 | self.serial_port_enabled, self.view_tracts, self.peel_loaded = vis_components | 86 | self.serial_port_enabled, self.view_tracts, self.peel_loaded = vis_components |
| 86 | - self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue = vis_queues | 87 | + self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue = vis_queues |
| 87 | self.sle = sle | 88 | self.sle = sle |
| 88 | self.event = event | 89 | self.event = event |
| 89 | self.neuronavigation_api = neuronavigation_api | 90 | self.neuronavigation_api = neuronavigation_api |
| @@ -93,7 +94,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -93,7 +94,7 @@ class UpdateNavigationScene(threading.Thread): | ||
| 93 | while not self.event.is_set(): | 94 | while not self.event.is_set(): |
| 94 | got_coords = False | 95 | got_coords = False |
| 95 | try: | 96 | try: |
| 96 | - coord, [coordinates_raw, markers_flag], m_img, view_obj = self.coord_queue.get_nowait() | 97 | + coord, markers_flag, m_img, view_obj = self.coord_queue.get_nowait() |
| 97 | got_coords = True | 98 | got_coords = True |
| 98 | 99 | ||
| 99 | # print('UpdateScene: get {}'.format(count)) | 100 | # print('UpdateScene: get {}'.format(count)) |
| @@ -117,7 +118,6 @@ class UpdateNavigationScene(threading.Thread): | @@ -117,7 +118,6 @@ class UpdateNavigationScene(threading.Thread): | ||
| 117 | # see the red cross in the position of the offset marker | 118 | # see the red cross in the position of the offset marker |
| 118 | wx.CallAfter(Publisher.sendMessage, 'Update slices position', position=coord[:3]) | 119 | wx.CallAfter(Publisher.sendMessage, 'Update slices position', position=coord[:3]) |
| 119 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) | 120 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) |
| 120 | - wx.CallAfter(Publisher.sendMessage, 'Update raw coordinates', coordinates_raw=coordinates_raw, markers_flag=markers_flag) | ||
| 121 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') | 121 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') |
| 122 | wx.CallAfter(Publisher.sendMessage, 'Sensor ID', markers_flag=markers_flag) | 122 | wx.CallAfter(Publisher.sendMessage, 'Sensor ID', markers_flag=markers_flag) |
| 123 | 123 | ||
| @@ -140,7 +140,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -140,7 +140,7 @@ class UpdateNavigationScene(threading.Thread): | ||
| 140 | self.coord_queue.task_done() | 140 | self.coord_queue.task_done() |
| 141 | 141 | ||
| 142 | 142 | ||
| 143 | -class Navigation(): | 143 | +class Navigation(metaclass=Singleton): |
| 144 | def __init__(self, pedal_connection, neuronavigation_api): | 144 | def __init__(self, pedal_connection, neuronavigation_api): |
| 145 | self.pedal_connection = pedal_connection | 145 | self.pedal_connection = pedal_connection |
| 146 | self.neuronavigation_api = neuronavigation_api | 146 | self.neuronavigation_api = neuronavigation_api |
| @@ -157,7 +157,6 @@ class Navigation(): | @@ -157,7 +157,6 @@ class Navigation(): | ||
| 157 | self.coord_queue = QueueCustom(maxsize=1) | 157 | self.coord_queue = QueueCustom(maxsize=1) |
| 158 | self.icp_queue = QueueCustom(maxsize=1) | 158 | self.icp_queue = QueueCustom(maxsize=1) |
| 159 | self.object_at_target_queue = QueueCustom(maxsize=1) | 159 | self.object_at_target_queue = QueueCustom(maxsize=1) |
| 160 | - self.robot_target_queue = QueueCustom(maxsize=1) | ||
| 161 | # self.visualization_queue = QueueCustom(maxsize=1) | 160 | # self.visualization_queue = QueueCustom(maxsize=1) |
| 162 | self.serial_port_queue = QueueCustom(maxsize=1) | 161 | self.serial_port_queue = QueueCustom(maxsize=1) |
| 163 | self.coord_tracts_queue = QueueCustom(maxsize=1) | 162 | self.coord_tracts_queue = QueueCustom(maxsize=1) |
| @@ -245,9 +244,14 @@ class Navigation(): | @@ -245,9 +244,14 @@ class Navigation(): | ||
| 245 | if state and permission_to_stimulate: | 244 | if state and permission_to_stimulate: |
| 246 | self.serial_port_connection.SendPulse() | 245 | self.serial_port_connection.SendPulse() |
| 247 | 246 | ||
| 248 | - def StartNavigation(self, tracker): | 247 | + def EstimateTrackerToInVTransformationMatrix(self, tracker): |
| 249 | tracker_fiducials, tracker_fiducials_raw = tracker.GetTrackerFiducials() | 248 | tracker_fiducials, tracker_fiducials_raw = tracker.GetTrackerFiducials() |
| 249 | + self.all_fiducials = np.vstack([self.image_fiducials, tracker_fiducials]) | ||
| 250 | 250 | ||
| 251 | + self.m_change = tr.affine_matrix_from_points(self.all_fiducials[3:, :].T, self.all_fiducials[:3, :].T, | ||
| 252 | + shear=False, scale=False) | ||
| 253 | + | ||
| 254 | + def StartNavigation(self, tracker): | ||
| 251 | # initialize jobs list | 255 | # initialize jobs list |
| 252 | jobs_list = [] | 256 | jobs_list = [] |
| 253 | 257 | ||
| @@ -255,17 +259,9 @@ class Navigation(): | @@ -255,17 +259,9 @@ class Navigation(): | ||
| 255 | self.event.clear() | 259 | self.event.clear() |
| 256 | 260 | ||
| 257 | vis_components = [self.serial_port_in_use, self.view_tracts, self.peel_loaded] | 261 | vis_components = [self.serial_port_in_use, self.view_tracts, self.peel_loaded] |
| 258 | - vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue] | 262 | + vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue] |
| 259 | 263 | ||
| 260 | Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) | 264 | Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) |
| 261 | - | ||
| 262 | - self.all_fiducials = np.vstack([self.image_fiducials, tracker_fiducials]) | ||
| 263 | - | ||
| 264 | - # fiducials matrix | ||
| 265 | - m_change = tr.affine_matrix_from_points(self.all_fiducials[3:, :].T, self.all_fiducials[:3, :].T, | ||
| 266 | - shear=False, scale=False) | ||
| 267 | - self.m_change = m_change | ||
| 268 | - | ||
| 269 | errors = False | 265 | errors = False |
| 270 | 266 | ||
| 271 | if self.track_obj: | 267 | if self.track_obj: |
| @@ -279,14 +275,14 @@ class Navigation(): | @@ -279,14 +275,14 @@ class Navigation(): | ||
| 279 | # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix | 275 | # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix |
| 280 | obj_fiducials, obj_orients, obj_ref_mode, obj_name = self.obj_reg | 276 | obj_fiducials, obj_orients, obj_ref_mode, obj_name = self.obj_reg |
| 281 | 277 | ||
| 282 | - coreg_data = [m_change, obj_ref_mode] | 278 | + coreg_data = [self.m_change, obj_ref_mode] |
| 283 | 279 | ||
| 284 | if self.ref_mode_id: | 280 | if self.ref_mode_id: |
| 285 | coord_raw, markers_flag = tracker.TrackerCoordinates.GetCoordinates() | 281 | coord_raw, markers_flag = tracker.TrackerCoordinates.GetCoordinates() |
| 286 | else: | 282 | else: |
| 287 | coord_raw = np.array([None]) | 283 | coord_raw = np.array([None]) |
| 288 | 284 | ||
| 289 | - obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | 285 | + obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, self.m_change) |
| 290 | coreg_data.extend(obj_data) | 286 | coreg_data.extend(obj_data) |
| 291 | 287 | ||
| 292 | queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue, self.object_at_target_queue] | 288 | queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue, self.object_at_target_queue] |
| @@ -295,7 +291,7 @@ class Navigation(): | @@ -295,7 +291,7 @@ class Navigation(): | ||
| 295 | self.event, self.sleep_nav, tracker.tracker_id, | 291 | self.event, self.sleep_nav, tracker.tracker_id, |
| 296 | self.target)) | 292 | self.target)) |
| 297 | else: | 293 | else: |
| 298 | - coreg_data = (m_change, 0) | 294 | + coreg_data = (self.m_change, 0) |
| 299 | queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue] | 295 | queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue] |
| 300 | jobs_list.append(dcr.CoordinateCorregistrateNoObject(self.ref_mode_id, tracker, coreg_data, | 296 | jobs_list.append(dcr.CoordinateCorregistrateNoObject(self.ref_mode_id, tracker, coreg_data, |
| 301 | self.view_tracts, queues, | 297 | self.view_tracts, queues, |
invesalius/navigation/robot.py
| @@ -1,308 +0,0 @@ | @@ -1,308 +0,0 @@ | ||
| 1 | -#-------------------------------------------------------------------------- | ||
| 2 | -# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | ||
| 3 | -# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | ||
| 4 | -# Homepage: http://www.softwarepublico.gov.br | ||
| 5 | -# Contact: invesalius@cti.gov.br | ||
| 6 | -# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | ||
| 7 | -#-------------------------------------------------------------------------- | ||
| 8 | -# Este programa e software livre; voce pode redistribui-lo e/ou | ||
| 9 | -# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | ||
| 10 | -# publicada pela Free Software Foundation; de acordo com a versao 2 | ||
| 11 | -# da Licenca. | ||
| 12 | -# | ||
| 13 | -# Este programa eh distribuido na expectativa de ser util, mas SEM | ||
| 14 | -# QUALQUER GARANTIA; sem mesmo a garantia implicita de | ||
| 15 | -# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | ||
| 16 | -# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | ||
| 17 | -# detalhes. | ||
| 18 | -#-------------------------------------------------------------------------- | ||
| 19 | -import numpy as np | ||
| 20 | -import wx | ||
| 21 | -import queue | ||
| 22 | -import threading | ||
| 23 | -from time import sleep | ||
| 24 | - | ||
| 25 | -import invesalius.data.bases as db | ||
| 26 | -import invesalius.gui.dialogs as dlg | ||
| 27 | -import invesalius.constants as const | ||
| 28 | -from invesalius.pubsub import pub as Publisher | ||
| 29 | - | ||
| 30 | -try: | ||
| 31 | - import invesalius.data.elfin as elfin | ||
| 32 | - import invesalius.data.elfin_processing as elfin_process | ||
| 33 | - has_robot = True | ||
| 34 | -except ImportError: | ||
| 35 | - has_robot = False | ||
| 36 | - | ||
| 37 | -class Robot(): | ||
| 38 | - def __init__(self, tracker): | ||
| 39 | - """ | ||
| 40 | - Class to establish the connection between the robot and InVesalius | ||
| 41 | - :param tracker: tracker.py instance | ||
| 42 | - """ | ||
| 43 | - self.tracker = tracker | ||
| 44 | - self.trk_init = None | ||
| 45 | - self.robot_target_queue = None | ||
| 46 | - self.object_at_target_queue = None | ||
| 47 | - self.process_tracker = None | ||
| 48 | - | ||
| 49 | - self.thread_robot = None | ||
| 50 | - | ||
| 51 | - self.robotcoordinates = RobotCoordinates() | ||
| 52 | - | ||
| 53 | - self.__bind_events() | ||
| 54 | - | ||
| 55 | - def __bind_events(self): | ||
| 56 | - Publisher.subscribe(self.OnSendCoordinates, 'Send coord to robot') | ||
| 57 | - Publisher.subscribe(self.OnUpdateRobotTargetMatrix, 'Robot target matrix') | ||
| 58 | - Publisher.subscribe(self.OnObjectTarget, 'Coil at target') | ||
| 59 | - Publisher.subscribe(self.OnResetProcessTracker, 'Reset robot process') | ||
| 60 | - | ||
| 61 | - def OnRobotConnection(self): | ||
| 62 | - if not self.tracker.trk_init[0][0][0] or not self.tracker.trk_init[0][1][0]: | ||
| 63 | - dlg.ShowNavigationTrackerWarning(self.tracker.tracker_id, self.tracker.trk_init[1]) | ||
| 64 | - self.tracker.tracker_id = 0 | ||
| 65 | - self.tracker.tracker_connected = False | ||
| 66 | - else: | ||
| 67 | - self.tracker.trk_init.append(self.robotcoordinates) | ||
| 68 | - self.process_tracker = elfin_process.TrackerProcessing() | ||
| 69 | - dlg_correg_robot = dlg.CreateTransformationMatrixRobot(self.tracker) | ||
| 70 | - if dlg_correg_robot.ShowModal() == wx.ID_OK: | ||
| 71 | - M_tracker_to_robot = dlg_correg_robot.GetValue() | ||
| 72 | - db.transform_tracker_to_robot.M_tracker_to_robot = M_tracker_to_robot | ||
| 73 | - self.robot_server = self.tracker.trk_init[0][1][0] | ||
| 74 | - self.trk_init = self.tracker.trk_init | ||
| 75 | - else: | ||
| 76 | - dlg.ShowNavigationTrackerWarning(self.tracker.tracker_id, 'disconnect') | ||
| 77 | - self.tracker.trk_init = None | ||
| 78 | - self.tracker.tracker_id = 0 | ||
| 79 | - self.tracker.tracker_connected = False | ||
| 80 | - | ||
| 81 | - def StartRobotThreadNavigation(self, tracker, coord_queue): | ||
| 82 | - if tracker.event_robot.is_set(): | ||
| 83 | - tracker.event_robot.clear() | ||
| 84 | - self.thread_robot = ControlRobot(self.trk_init, tracker, self.robotcoordinates, | ||
| 85 | - [coord_queue, self.robot_target_queue, self.object_at_target_queue], | ||
| 86 | - self.process_tracker, tracker.event_robot) | ||
| 87 | - self.thread_robot.start() | ||
| 88 | - | ||
| 89 | - def StopRobotThreadNavigation(self): | ||
| 90 | - self.thread_robot.join() | ||
| 91 | - self.OnResetProcessTracker() | ||
| 92 | - | ||
| 93 | - def OnResetProcessTracker(self): | ||
| 94 | - self.process_tracker.__init__() | ||
| 95 | - | ||
| 96 | - def OnSendCoordinates(self, coord): | ||
| 97 | - self.robot_server.SendCoordinates(coord) | ||
| 98 | - | ||
| 99 | - def OnUpdateRobotTargetMatrix(self, robot_tracker_flag, m_change_robot_to_head): | ||
| 100 | - try: | ||
| 101 | - self.robot_target_queue.put_nowait([robot_tracker_flag, m_change_robot_to_head]) | ||
| 102 | - except queue.Full: | ||
| 103 | - pass | ||
| 104 | - | ||
| 105 | - def OnObjectTarget(self, state): | ||
| 106 | - try: | ||
| 107 | - if self.object_at_target_queue: | ||
| 108 | - self.object_at_target_queue.put_nowait(state) | ||
| 109 | - except queue.Full: | ||
| 110 | - pass | ||
| 111 | - | ||
| 112 | - def SetRobotQueues(self, queues): | ||
| 113 | - self.robot_target_queue, self.object_at_target_queue = queues | ||
| 114 | - | ||
| 115 | - | ||
| 116 | -class RobotCoordinates(): | ||
| 117 | - """ | ||
| 118 | - Class to set/get robot coordinates. Robot coordinates are acquired in ControlRobot thread. | ||
| 119 | - The class is required to avoid acquisition conflict with different threads (coordinates and navigation) | ||
| 120 | - """ | ||
| 121 | - def __init__(self): | ||
| 122 | - self.coord = None | ||
| 123 | - | ||
| 124 | - def SetRobotCoordinates(self, coord): | ||
| 125 | - self.coord = coord | ||
| 126 | - | ||
| 127 | - def GetRobotCoordinates(self): | ||
| 128 | - return self.coord | ||
| 129 | - | ||
| 130 | - | ||
| 131 | -class ControlRobot(threading.Thread): | ||
| 132 | - def __init__(self, trck_init, tracker, robotcoordinates, queues, process_tracker, event_robot): | ||
| 133 | - """Class (threading) to perform the robot control. | ||
| 134 | - A distinguish thread is required to increase perform and separate robot control from navigation thread | ||
| 135 | - (safetly layer for robot positining). | ||
| 136 | - | ||
| 137 | - There is no GUI involved, them no Sleep is required | ||
| 138 | - | ||
| 139 | - :param trck_init: Initialization variable of tracking device and connection type. See tracker.py. | ||
| 140 | - :param tracker: tracker.py instance | ||
| 141 | - :param robotcoordinates: RobotCoordinates() instance | ||
| 142 | - :param queues: Queue instance that manage coordinates and robot target | ||
| 143 | - :param process_tracker: TrackerProcessing() instance from elfin_process | ||
| 144 | - :param event_robot: Threading event to ControlRobot when tasks is done | ||
| 145 | - """ | ||
| 146 | - threading.Thread.__init__(self, name='ControlRobot') | ||
| 147 | - | ||
| 148 | - self.trck_init_robot = trck_init[0][1][0] | ||
| 149 | - self.trck_init_tracker = trck_init[0][0][0] | ||
| 150 | - self.trk_id = trck_init[1] | ||
| 151 | - self.tracker = tracker | ||
| 152 | - self.robotcoordinates = robotcoordinates | ||
| 153 | - self.robot_tracker_flag = False | ||
| 154 | - self.objattarget_flag = False | ||
| 155 | - self.target_flag = False | ||
| 156 | - self.m_change_robot_to_head = None | ||
| 157 | - self.coord_inv_old = None | ||
| 158 | - self.coord_queue = queues[0] | ||
| 159 | - self.robot_target_queue = queues[1] | ||
| 160 | - self.object_at_target_queue = queues[2] | ||
| 161 | - self.process_tracker = process_tracker | ||
| 162 | - self.event_robot = event_robot | ||
| 163 | - self.arc_motion_flag = False | ||
| 164 | - self.arc_motion_step_flag = None | ||
| 165 | - self.target_linear_out = None | ||
| 166 | - self.target_linear_in = None | ||
| 167 | - self.target_arc = None | ||
| 168 | - self.previous_robot_status = False | ||
| 169 | - | ||
| 170 | - def get_coordinates_from_tracker_devices(self): | ||
| 171 | - coord_robot_raw = self.trck_init_robot.Run() | ||
| 172 | - coord_robot = np.array(coord_robot_raw) | ||
| 173 | - coord_robot[3], coord_robot[5] = coord_robot[5], coord_robot[3] | ||
| 174 | - self.robotcoordinates.SetRobotCoordinates(coord_robot) | ||
| 175 | - | ||
| 176 | - coord_raw, markers_flag = self.tracker.TrackerCoordinates.GetCoordinates() | ||
| 177 | - | ||
| 178 | - return coord_raw, coord_robot_raw, markers_flag | ||
| 179 | - | ||
| 180 | - def robot_motion_reset(self): | ||
| 181 | - self.trck_init_robot.StopRobot() | ||
| 182 | - self.arc_motion_flag = False | ||
| 183 | - self.arc_motion_step_flag = const.ROBOT_MOTIONS["normal"] | ||
| 184 | - | ||
| 185 | - def robot_move_decision(self, distance_target, new_robot_coordinates, current_robot_coordinates, current_head_filtered): | ||
| 186 | - """ | ||
| 187 | - There are two types of robot movements. | ||
| 188 | - We can imagine in two concentric spheres of different sizes. The inside sphere is to compensate for small head movements. | ||
| 189 | - It was named "normal" moves. | ||
| 190 | - The outside sphere is for the arc motion. The arc motion is a safety feature for long robot movements. | ||
| 191 | - Even for a new target or a sudden huge head movement. | ||
| 192 | - 1) normal: | ||
| 193 | - A linear move from the actual position until the target position. | ||
| 194 | - This movement just happens when move distance is below a threshold (const.ROBOT_ARC_THRESHOLD_DISTANCE) | ||
| 195 | - 2) arc motion: | ||
| 196 | - It can be divided into three parts. | ||
| 197 | - The first one represents the movement from the inner sphere to the outer sphere. | ||
| 198 | - The robot moves back using a radial move (it use the center of the head as a reference). | ||
| 199 | - The second step is the actual arc motion (along the outer sphere). | ||
| 200 | - A middle point, between the actual position and the target, is required. | ||
| 201 | - The last step is to make a linear move until the target (goes to the inner sphere) | ||
| 202 | - | ||
| 203 | - """ | ||
| 204 | - #Check if the target is inside the working space | ||
| 205 | - if self.process_tracker.estimate_robot_target_length(new_robot_coordinates) < const.ROBOT_WORKING_SPACE: | ||
| 206 | - #Check the target distance to define the motion mode | ||
| 207 | - if distance_target < const.ROBOT_ARC_THRESHOLD_DISTANCE and not self.arc_motion_flag: | ||
| 208 | - self.trck_init_robot.SendCoordinates(new_robot_coordinates, const.ROBOT_MOTIONS["normal"]) | ||
| 209 | - | ||
| 210 | - elif distance_target >= const.ROBOT_ARC_THRESHOLD_DISTANCE or self.arc_motion_flag: | ||
| 211 | - actual_point = current_robot_coordinates | ||
| 212 | - if not self.arc_motion_flag: | ||
| 213 | - head_center_coordinates = self.process_tracker.estimate_head_center(self.tracker, | ||
| 214 | - current_head_filtered).tolist() | ||
| 215 | - | ||
| 216 | - self.target_linear_out, self.target_arc = self.process_tracker.compute_arc_motion(current_robot_coordinates, head_center_coordinates, | ||
| 217 | - new_robot_coordinates) | ||
| 218 | - self.arc_motion_flag = True | ||
| 219 | - self.arc_motion_step_flag = const.ROBOT_MOTIONS["linear out"] | ||
| 220 | - | ||
| 221 | - if self.arc_motion_flag and self.arc_motion_step_flag == const.ROBOT_MOTIONS["linear out"]: | ||
| 222 | - coord = self.target_linear_out | ||
| 223 | - if np.allclose(np.array(actual_point), np.array(self.target_linear_out), 0, 1): | ||
| 224 | - self.arc_motion_step_flag = const.ROBOT_MOTIONS["arc"] | ||
| 225 | - coord = self.target_arc | ||
| 226 | - | ||
| 227 | - elif self.arc_motion_flag and self.arc_motion_step_flag == const.ROBOT_MOTIONS["arc"]: | ||
| 228 | - head_center_coordinates = self.process_tracker.estimate_head_center(self.tracker, | ||
| 229 | - current_head_filtered).tolist() | ||
| 230 | - | ||
| 231 | - _, new_target_arc = self.process_tracker.compute_arc_motion(current_robot_coordinates, head_center_coordinates, | ||
| 232 | - new_robot_coordinates) | ||
| 233 | - if np.allclose(np.array(new_target_arc[3:-1]), np.array(self.target_arc[3:-1]), 0, 1): | ||
| 234 | - None | ||
| 235 | - else: | ||
| 236 | - if self.process_tracker.correction_distance_calculation_target(new_robot_coordinates, current_robot_coordinates) >= \ | ||
| 237 | - const.ROBOT_ARC_THRESHOLD_DISTANCE*0.8: | ||
| 238 | - self.target_arc = new_target_arc | ||
| 239 | - | ||
| 240 | - coord = self.target_arc | ||
| 241 | - | ||
| 242 | - if np.allclose(np.array(actual_point), np.array(self.target_arc[3:-1]), 0, 10): | ||
| 243 | - self.arc_motion_flag = False | ||
| 244 | - self.arc_motion_step_flag = const.ROBOT_MOTIONS["normal"] | ||
| 245 | - coord = new_robot_coordinates | ||
| 246 | - | ||
| 247 | - self.trck_init_robot.SendCoordinates(coord, self.arc_motion_step_flag) | ||
| 248 | - robot_status = True | ||
| 249 | - else: | ||
| 250 | - print("Head is too far from the robot basis") | ||
| 251 | - robot_status = False | ||
| 252 | - | ||
| 253 | - return robot_status | ||
| 254 | - | ||
| 255 | - def robot_control(self, current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag): | ||
| 256 | - coord_head_tracker_in_robot = current_tracker_coordinates_in_robot[1] | ||
| 257 | - marker_head_flag = markers_flag[1] | ||
| 258 | - coord_obj_tracker_in_robot = current_tracker_coordinates_in_robot[2] | ||
| 259 | - marker_obj_flag = markers_flag[2] | ||
| 260 | - robot_status = False | ||
| 261 | - | ||
| 262 | - if self.robot_tracker_flag: | ||
| 263 | - current_head = coord_head_tracker_in_robot | ||
| 264 | - if current_head is not None and marker_head_flag: | ||
| 265 | - current_head_filtered = self.process_tracker.kalman_filter(current_head) | ||
| 266 | - if self.process_tracker.compute_head_move_threshold(current_head_filtered): | ||
| 267 | - new_robot_coordinates = self.process_tracker.compute_head_move_compensation(current_head_filtered, | ||
| 268 | - self.m_change_robot_to_head) | ||
| 269 | - robot_status = True | ||
| 270 | - if self.coord_inv_old is None: | ||
| 271 | - self.coord_inv_old = new_robot_coordinates | ||
| 272 | - | ||
| 273 | - if np.allclose(np.array(new_robot_coordinates), np.array(current_robot_coordinates), 0, 0.01): | ||
| 274 | - #avoid small movements (0.01 mm) | ||
| 275 | - pass | ||
| 276 | - elif not np.allclose(np.array(new_robot_coordinates), np.array(self.coord_inv_old), 0, 5): | ||
| 277 | - #if the head moves (>5mm) before the robot reach the target | ||
| 278 | - self.trck_init_robot.StopRobot() | ||
| 279 | - self.coord_inv_old = new_robot_coordinates | ||
| 280 | - else: | ||
| 281 | - distance_target = self.process_tracker.correction_distance_calculation_target(new_robot_coordinates, current_robot_coordinates) | ||
| 282 | - robot_status = self.robot_move_decision(distance_target, new_robot_coordinates, current_robot_coordinates, current_head_filtered) | ||
| 283 | - self.coord_inv_old = new_robot_coordinates | ||
| 284 | - else: | ||
| 285 | - self.trck_init_robot.StopRobot() | ||
| 286 | - | ||
| 287 | - return robot_status | ||
| 288 | - | ||
| 289 | - def run(self): | ||
| 290 | - while not self.event_robot.is_set(): | ||
| 291 | - current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag = self.get_coordinates_from_tracker_devices() | ||
| 292 | - | ||
| 293 | - if not self.robot_target_queue.empty(): | ||
| 294 | - self.robot_tracker_flag, self.m_change_robot_to_head = self.robot_target_queue.get_nowait() | ||
| 295 | - self.robot_motion_reset() | ||
| 296 | - self.robot_target_queue.task_done() | ||
| 297 | - | ||
| 298 | - if not self.object_at_target_queue.empty(): | ||
| 299 | - self.target_flag = self.object_at_target_queue.get_nowait() | ||
| 300 | - self.object_at_target_queue.task_done() | ||
| 301 | - | ||
| 302 | - robot_status = self.robot_control(current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag) | ||
| 303 | - | ||
| 304 | - if self.previous_robot_status != robot_status: | ||
| 305 | - wx.CallAfter(Publisher.sendMessage, 'Update robot status', robot_status=robot_status) | ||
| 306 | - self.previous_robot_status = robot_status | ||
| 307 | - | ||
| 308 | - sleep(const.SLEEP_ROBOT) |
invesalius/navigation/tracker.py
| @@ -42,7 +42,6 @@ class Tracker(): | @@ -42,7 +42,6 @@ class Tracker(): | ||
| 42 | self.thread_coord = None | 42 | self.thread_coord = None |
| 43 | 43 | ||
| 44 | self.event_coord = threading.Event() | 44 | self.event_coord = threading.Event() |
| 45 | - self.event_robot = threading.Event() | ||
| 46 | 45 | ||
| 47 | self.TrackerCoordinates = dco.TrackerCoordinates() | 46 | self.TrackerCoordinates = dco.TrackerCoordinates() |
| 48 | 47 | ||
| @@ -78,11 +77,8 @@ class Tracker(): | @@ -78,11 +77,8 @@ class Tracker(): | ||
| 78 | 77 | ||
| 79 | if self.thread_coord: | 78 | if self.thread_coord: |
| 80 | self.event_coord.set() | 79 | self.event_coord.set() |
| 81 | - self.event_robot.set() | ||
| 82 | self.thread_coord.join() | 80 | self.thread_coord.join() |
| 83 | self.event_coord.clear() | 81 | self.event_coord.clear() |
| 84 | - self.event_robot.clear() | ||
| 85 | - | ||
| 86 | 82 | ||
| 87 | Publisher.sendMessage('Update status text in GUI', | 83 | Publisher.sendMessage('Update status text in GUI', |
| 88 | label=_("Tracker disconnected")) | 84 | label=_("Tracker disconnected")) |
| @@ -92,13 +88,6 @@ class Tracker(): | @@ -92,13 +88,6 @@ class Tracker(): | ||
| 92 | label=_("Tracker still connected")) | 88 | label=_("Tracker still connected")) |
| 93 | print("Tracker still connected!") | 89 | print("Tracker still connected!") |
| 94 | 90 | ||
| 95 | - def ConnectToRobot(self, navigation, tracker, robot): | ||
| 96 | - robot.SetRobotQueues([navigation.robot_target_queue, navigation.object_at_target_queue]) | ||
| 97 | - robot.OnRobotConnection() | ||
| 98 | - trk_init_robot = self.trk_init[0][1][0] | ||
| 99 | - if trk_init_robot: | ||
| 100 | - robot.StartRobotThreadNavigation(tracker, navigation.coord_queue) | ||
| 101 | - Publisher.sendMessage('Robot navigation mode', robot_mode=True) | ||
| 102 | 91 | ||
| 103 | def IsTrackerInitialized(self): | 92 | def IsTrackerInitialized(self): |
| 104 | return self.trk_init and self.tracker_id and self.tracker_connected | 93 | return self.trk_init and self.tracker_id and self.tracker_connected |
| @@ -158,7 +147,7 @@ class Tracker(): | @@ -158,7 +147,7 @@ class Tracker(): | ||
| 158 | m_probe_ref_right = np.linalg.inv(self.m_tracker_fiducials_raw[3]) @ self.m_tracker_fiducials_raw[2] | 147 | m_probe_ref_right = np.linalg.inv(self.m_tracker_fiducials_raw[3]) @ self.m_tracker_fiducials_raw[2] |
| 159 | m_probe_ref_nasion = np.linalg.inv(self.m_tracker_fiducials_raw[5]) @ self.m_tracker_fiducials_raw[4] | 148 | m_probe_ref_nasion = np.linalg.inv(self.m_tracker_fiducials_raw[5]) @ self.m_tracker_fiducials_raw[4] |
| 160 | 149 | ||
| 161 | - return [m_probe_ref_left, m_probe_ref_right, m_probe_ref_nasion] | 150 | + return [m_probe_ref_left.tolist(), m_probe_ref_right.tolist(), m_probe_ref_nasion.tolist()] |
| 162 | 151 | ||
| 163 | def GetTrackerInfo(self): | 152 | def GetTrackerInfo(self): |
| 164 | return self.trk_init, self.tracker_id | 153 | return self.trk_init, self.tracker_id |
invesalius/net/remote_control.py