Commit 392671f22cf2aeaca21e71a8523df5c7a071d756

Authored by Renan
1 parent c21b81c5
Exists in master

ENH: robot documentation

invesalius/data/coordinates.py
@@ -236,7 +236,6 @@ def PolhemusCoord(trck, trck_id, ref_mode): @@ -236,7 +236,6 @@ def PolhemusCoord(trck, trck_id, ref_mode):
236 236
237 237
238 def PolhemusWrapperCoord(trck, trck_id, ref_mode): 238 def PolhemusWrapperCoord(trck, trck_id, ref_mode):
239 -  
240 trck.Run() 239 trck.Run()
241 scale = 10.0 * np.array([1., 1., 1.]) 240 scale = 10.0 * np.array([1., 1., 1.])
242 241
invesalius/data/elfin.py
@@ -6,6 +6,10 @@ from socket import socket, AF_INET, SOCK_STREAM @@ -6,6 +6,10 @@ from socket import socket, AF_INET, SOCK_STREAM
6 import invesalius.constants as const 6 import invesalius.constants as const
7 7
8 class Elfin_Server(): 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 + """
9 def __init__(self, server_ip, port_number): 13 def __init__(self, server_ip, port_number):
10 self.server_ip = server_ip 14 self.server_ip = server_ip
11 self.port_number = port_number 15 self.port_number = port_number
@@ -21,6 +25,10 @@ class Elfin_Server(): @@ -21,6 +25,10 @@ class Elfin_Server():
21 return self.cobot.ReadPcsActualPos() 25 return self.cobot.ReadPcsActualPos()
22 26
23 def SendCoordinates(self, target, type=const.ROBOT_MOTIONS["normal"]): 27 def SendCoordinates(self, target, 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 + """
24 status = self.cobot.ReadMoveState() 32 status = self.cobot.ReadMoveState()
25 if status != 1009: 33 if status != 1009:
26 if type == const.ROBOT_MOTIONS["normal"] or type == const.ROBOT_MOTIONS["linear out"]: 34 if type == const.ROBOT_MOTIONS["normal"] or type == const.ROBOT_MOTIONS["linear out"]:
@@ -29,6 +37,8 @@ class Elfin_Server(): @@ -29,6 +37,8 @@ class Elfin_Server():
29 self.cobot.MoveC(target) 37 self.cobot.MoveC(target)
30 38
31 def StopRobot(self): 39 def StopRobot(self):
  40 + # Takes some microseconds to the robot actual stops after the command.
  41 + # The sleep time is required to guarantee the stop
32 self.cobot.GrpStop() 42 self.cobot.GrpStop()
33 sleep(0.1) 43 sleep(0.1)
34 44
@@ -37,6 +47,9 @@ class Elfin_Server(): @@ -37,6 +47,9 @@ class Elfin_Server():
37 47
38 class Elfin: 48 class Elfin:
39 def __init__(self): 49 def __init__(self):
  50 + """
  51 + Class to communicate with elfin robot. This class follows "HansRobot Communication Protocol Interface".
  52 + """
40 self.end_msg = ",;" 53 self.end_msg = ",;"
41 54
42 def connect(self, server_ip, port_number, message_size, robot_id): 55 def connect(self, server_ip, port_number, message_size, robot_id):
invesalius/data/elfin_processing.py
@@ -140,6 +140,9 @@ class TrackerProcessing: @@ -140,6 +140,9 @@ class TrackerProcessing:
140 return init_move_out_point, target_arc 140 return init_move_out_point, target_arc
141 141
142 def compute_head_move_threshold(self, current_ref): 142 def compute_head_move_threshold(self, current_ref):
  143 + """
  144 + Checks if the head velocity is bellow the threshold
  145 + """
143 self.coord_vel.append(current_ref) 146 self.coord_vel.append(current_ref)
144 self.timestamp.append(time()) 147 self.timestamp.append(time())
145 if len(self.coord_vel) >= 10: 148 if len(self.coord_vel) >= 10:
@@ -161,18 +164,24 @@ class TrackerProcessing: @@ -161,18 +164,24 @@ class TrackerProcessing:
161 164
162 return False 165 return False
163 166
164 - def compute_head_move_compensation(self, current_head, M_change_robot_to_head): 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 + """
165 M_current_head = dco.coordinates_to_transformation_matrix( 171 M_current_head = dco.coordinates_to_transformation_matrix(
166 position=current_head[:3], 172 position=current_head[:3],
167 orientation=current_head[3:6], 173 orientation=current_head[3:6],
168 axes='rzyx', 174 axes='rzyx',
169 ) 175 )
170 - M_robot_new = M_current_head @ M_change_robot_to_head  
171 - angles_as_deg, translate = dco.transformation_matrix_to_coordinates(M_robot_new, axes='rzyx') 176 + m_robot_new = M_current_head @ m_change_robot_to_head
  177 + angles_as_deg, translate = dco.transformation_matrix_to_coordinates(m_robot_new, axes='rzyx')
172 #TODO: check this with robot 178 #TODO: check this with robot
173 return list(translate) + list(angles_as_deg) 179 return list(translate) + list(angles_as_deg)
174 180
175 def estimate_head_center(self, tracker, current_head): 181 def estimate_head_center(self, tracker, current_head):
  182 + """
  183 + Estimates the actual head center position using fiducials
  184 + """
176 m_probe_head_left, m_probe_head_right, m_probe_head_nasion = tracker.GetMatrixTrackerFiducials() 185 m_probe_head_left, m_probe_head_right, m_probe_head_nasion = tracker.GetMatrixTrackerFiducials()
177 m_current_head = dcr.compute_marker_transformation(np.array([current_head]), 0) 186 m_current_head = dcr.compute_marker_transformation(np.array([current_head]), 0)
178 187
@@ -182,10 +191,13 @@ class TrackerProcessing: @@ -182,10 +191,13 @@ class TrackerProcessing:
182 return (m_ear_left_new[:3, -1] + m_ear_right_new[:3, -1])/2 191 return (m_ear_left_new[:3, -1] + m_ear_right_new[:3, -1])/2
183 192
184 def correction_distance_calculation_target(self, coord_inv, actual_point): 193 def correction_distance_calculation_target(self, coord_inv, actual_point):
185 - sum = (coord_inv[0]-actual_point[0]) ** 2\  
186 - + (coord_inv[1]-actual_point[1]) ** 2\  
187 - + (coord_inv[2]-actual_point[2]) ** 2  
188 - correction_distance_compensation = pow(sum, 0.5) 194 + """
  195 + Estimates the Euclidean distance between the actual position and the target
  196 + """
  197 + square_sum = (coord_inv[0]-actual_point[0]) ** 2 +\
  198 + (coord_inv[1]-actual_point[1]) ** 2 +\
  199 + (coord_inv[2]-actual_point[2]) ** 2
  200 + correction_distance_compensation = pow(square_sum, 0.5)
189 201
190 return correction_distance_compensation 202 return correction_distance_compensation
191 203
invesalius/navigation/robot.py
@@ -35,6 +35,10 @@ except ImportError: @@ -35,6 +35,10 @@ except ImportError:
35 35
36 class Robot(): 36 class Robot():
37 def __init__(self, tracker): 37 def __init__(self, tracker):
  38 + """
  39 + Class to establish the connection between the robot and InVesalius
  40 + :param tracker: tracker.py instance
  41 + """
38 self.tracker = tracker 42 self.tracker = tracker
39 self.trk_init = None 43 self.trk_init = None
40 self.robot_target_queue = None 44 self.robot_target_queue = None
@@ -109,6 +113,10 @@ class Robot(): @@ -109,6 +113,10 @@ class Robot():
109 113
110 114
111 class RobotCoordinates(): 115 class RobotCoordinates():
  116 + """
  117 + Class to set/get robot coordinates. Robot coordinates are acquired in ControlRobot thread.
  118 + The class is required to avoid acquisition conflict with different threads (coordinates and navigation)
  119 + """
112 def __init__(self): 120 def __init__(self):
113 self.coord = None 121 self.coord = None
114 122
@@ -121,6 +129,19 @@ class RobotCoordinates(): @@ -121,6 +129,19 @@ class RobotCoordinates():
121 129
122 class ControlRobot(threading.Thread): 130 class ControlRobot(threading.Thread):
123 def __init__(self, trck_init, tracker, robotcoordinates, queues, process_tracker, event_robot): 131 def __init__(self, trck_init, tracker, robotcoordinates, queues, process_tracker, event_robot):
  132 + """Class (threading) to perform the robot control.
  133 + A distinguish thread is required to increase perform and separate robot control from navigation thread
  134 + (safetly layer for robot positining).
  135 +
  136 + There is no GUI involved, them no Sleep is required
  137 +
  138 + :param trck_init: Initialization variable of tracking device and connection type. See tracker.py.
  139 + :param tracker: tracker.py instance
  140 + :param robotcoordinates: RobotCoordinates() instance
  141 + :param queues: Queue instance that manage coordinates and robot target
  142 + :param process_tracker: TrackerProcessing() instance from elfin_process
  143 + :param event_robot: Threading event to ControlRobot when tasks is done
  144 + """
124 threading.Thread.__init__(self, name='ControlRobot') 145 threading.Thread.__init__(self, name='ControlRobot')
125 146
126 self.trck_init_robot = trck_init[1][0] 147 self.trck_init_robot = trck_init[1][0]
@@ -155,6 +176,24 @@ class ControlRobot(threading.Thread): @@ -155,6 +176,24 @@ class ControlRobot(threading.Thread):
155 return coord_raw, coord_robot_raw, markers_flag 176 return coord_raw, coord_robot_raw, markers_flag
156 177
157 def robot_move_decision(self, distance_target, new_robot_coordinates, current_robot_coordinates, current_head_filtered): 178 def robot_move_decision(self, distance_target, new_robot_coordinates, current_robot_coordinates, current_head_filtered):
  179 + """
  180 + There are two types of robot movements.
  181 + We can imagine in two concentric spheres of different sizes. The inside sphere is to compensate for small head movements.
  182 + It was named "normal" moves.
  183 + The outside sphere is for the arc motion. The arc motion is a safety feature for long robot movements.
  184 + Even for a new target or a sudden huge head movement.
  185 + 1) normal:
  186 + A linear move from the actual position until the target position.
  187 + This movement just happens when move distance is below a threshold (const.ROBOT_ARC_THRESHOLD_DISTANCE)
  188 + 2) arc motion:
  189 + It can be divided into three parts.
  190 + The first one represents the movement from the inner sphere to the outer sphere.
  191 + The robot moves back using a radial move (it use the center of the head as a reference).
  192 + The second step is the actual arc motion (along the outer sphere).
  193 + A middle point, between the actual position and the target, is required.
  194 + The last step is to make a linear move until the target (goes to the inner sphere)
  195 +
  196 + """
158 if distance_target < const.ROBOT_ARC_THRESHOLD_DISTANCE and not self.arc_motion_flag: 197 if distance_target < const.ROBOT_ARC_THRESHOLD_DISTANCE and not self.arc_motion_flag:
159 self.trck_init_robot.SendCoordinates(new_robot_coordinates, const.ROBOT_MOTIONS["normal"]) 198 self.trck_init_robot.SendCoordinates(new_robot_coordinates, const.ROBOT_MOTIONS["normal"])
160 199
@@ -214,9 +253,10 @@ class ControlRobot(threading.Thread): @@ -214,9 +253,10 @@ class ControlRobot(threading.Thread):
214 self.coord_inv_old = new_robot_coordinates 253 self.coord_inv_old = new_robot_coordinates
215 254
216 if np.allclose(np.array(new_robot_coordinates), np.array(current_robot_coordinates), 0, 0.01): 255 if np.allclose(np.array(new_robot_coordinates), np.array(current_robot_coordinates), 0, 0.01):
217 - # print("At target within range 1") 256 + #avoid small movements (0.01 mm)
218 pass 257 pass
219 elif not np.allclose(np.array(new_robot_coordinates), np.array(self.coord_inv_old), 0, 5): 258 elif not np.allclose(np.array(new_robot_coordinates), np.array(self.coord_inv_old), 0, 5):
  259 + #if the head moves (>5mm) before the robot reach the target
220 self.trck_init_robot.StopRobot() 260 self.trck_init_robot.StopRobot()
221 self.coord_inv_old = new_robot_coordinates 261 self.coord_inv_old = new_robot_coordinates
222 else: 262 else:
@@ -230,15 +270,11 @@ class ControlRobot(threading.Thread): @@ -230,15 +270,11 @@ class ControlRobot(threading.Thread):
230 while not self.event_robot.is_set(): 270 while not self.event_robot.is_set():
231 current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag = self.get_coordinates_from_tracker_devices() 271 current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag = self.get_coordinates_from_tracker_devices()
232 272
233 - if self.robot_target_queue.empty():  
234 - None  
235 - else: 273 + if not self.robot_target_queue.empty():
236 self.robot_tracker_flag, self.m_change_robot_to_head = self.robot_target_queue.get_nowait() 274 self.robot_tracker_flag, self.m_change_robot_to_head = self.robot_target_queue.get_nowait()
237 self.robot_target_queue.task_done() 275 self.robot_target_queue.task_done()
238 276
239 - if self.object_at_target_queue.empty():  
240 - None  
241 - else: 277 + if not self.object_at_target_queue.empty():
242 self.target_flag = self.object_at_target_queue.get_nowait() 278 self.target_flag = self.object_at_target_queue.get_nowait()
243 self.object_at_target_queue.task_done() 279 self.object_at_target_queue.task_done()
244 280