Compare View

switch
from
...
to
 
Commits (3)
  • * 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
    Renan
     
  • Thiago Franco de Moraes
     
  • * ADD: Send coil mesh using NeuronavigationApi
    
    * Review comments, some tweaks to save memory and speed up
    Olli-Pekka Kahilakoski
     
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/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 )
@@ -205,7 +201,12 @@ class InnerFoldPanel(wx.Panel): @@ -205,7 +201,12 @@ class InnerFoldPanel(wx.Panel):
205 201
206 # Fold 2 - Object registration panel 202 # Fold 2 - Object registration panel
207 item = fold_panel.AddFoldPanel(_("Object registration"), collapsed=True) 203 item = fold_panel.AddFoldPanel(_("Object registration"), collapsed=True)
208 - otw = ObjectRegistrationPanel(item, tracker, pedal_connection) 204 + otw = ObjectRegistrationPanel(
  205 + parent=item,
  206 + tracker=tracker,
  207 + pedal_connection=pedal_connection,
  208 + neuronavigation_api=neuronavigation_api,
  209 + )
209 210
210 fold_panel.ApplyCaptionStyle(item, style) 211 fold_panel.ApplyCaptionStyle(item, style)
211 fold_panel.AddFoldPanelWindow(item, otw, spacing=0, 212 fold_panel.AddFoldPanelWindow(item, otw, spacing=0,
@@ -213,7 +214,7 @@ class InnerFoldPanel(wx.Panel): @@ -213,7 +214,7 @@ class InnerFoldPanel(wx.Panel):
213 214
214 # Fold 3 - Markers panel 215 # Fold 3 - Markers panel
215 item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) 216 item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True)
216 - mtw = MarkersPanel(item, navigation, tracker) 217 + mtw = MarkersPanel(item, navigation, tracker, icp)
217 218
218 fold_panel.ApplyCaptionStyle(item, style) 219 fold_panel.ApplyCaptionStyle(item, style)
219 fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, 220 fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0,
@@ -352,7 +353,7 @@ class InnerFoldPanel(wx.Panel): @@ -352,7 +353,7 @@ class InnerFoldPanel(wx.Panel):
352 353
353 354
354 class NeuronavigationPanel(wx.Panel): 355 class NeuronavigationPanel(wx.Panel):
355 - def __init__(self, parent, navigation, tracker, pedal_connection, neuronavigation_api): 356 + def __init__(self, parent, navigation, tracker, icp, pedal_connection, neuronavigation_api):
356 wx.Panel.__init__(self, parent) 357 wx.Panel.__init__(self, parent)
357 try: 358 try:
358 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) 359 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR)
@@ -369,9 +370,8 @@ class NeuronavigationPanel(wx.Panel): @@ -369,9 +370,8 @@ class NeuronavigationPanel(wx.Panel):
369 self.neuronavigation_api = neuronavigation_api 370 self.neuronavigation_api = neuronavigation_api
370 371
371 self.navigation = navigation 372 self.navigation = navigation
372 - self.icp = ICP() 373 + self.icp = icp
373 self.tracker = tracker 374 self.tracker = tracker
374 - self.robot = Robot(tracker)  
375 375
376 self.nav_status = False 376 self.nav_status = False
377 self.tracker_fiducial_being_set = None 377 self.tracker_fiducial_being_set = None
@@ -558,6 +558,7 @@ class NeuronavigationPanel(wx.Panel): @@ -558,6 +558,7 @@ class NeuronavigationPanel(wx.Panel):
558 Publisher.subscribe(self.UpdateTarget, 'Update target') 558 Publisher.subscribe(self.UpdateTarget, 'Update target')
559 Publisher.subscribe(self.OnStartNavigation, 'Start navigation') 559 Publisher.subscribe(self.OnStartNavigation, 'Start navigation')
560 Publisher.subscribe(self.OnStopNavigation, 'Stop navigation') 560 Publisher.subscribe(self.OnStopNavigation, 'Stop navigation')
  561 + Publisher.subscribe(self.OnDialogRobotDestroy, 'Dialog robot destroy')
561 562
562 def LoadImageFiducials(self, label, coord): 563 def LoadImageFiducials(self, label, coord):
563 fiducial = self.GetFiducialByAttribute(const.IMAGE_FIDUCIALS, 'label', label) 564 fiducial = self.GetFiducialByAttribute(const.IMAGE_FIDUCIALS, 'label', label)
@@ -671,8 +672,6 @@ class NeuronavigationPanel(wx.Panel): @@ -671,8 +672,6 @@ class NeuronavigationPanel(wx.Panel):
671 self.checkbox_icp.SetValue(False) 672 self.checkbox_icp.SetValue(False)
672 673
673 def OnDisconnectTracker(self): 674 def OnDisconnectTracker(self):
674 - if self.tracker.tracker_id == const.ROBOT:  
675 - self.robot.StopRobotThreadNavigation()  
676 self.tracker.DisconnectTracker() 675 self.tracker.DisconnectTracker()
677 self.ResetICP() 676 self.ResetICP()
678 self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_fiducial[3:6], self.txtctrl_fre) 677 self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_fiducial[3:6], self.txtctrl_fre)
@@ -691,7 +690,12 @@ class NeuronavigationPanel(wx.Panel): @@ -691,7 +690,12 @@ class NeuronavigationPanel(wx.Panel):
691 690
692 self.tracker.SetTracker(choice) 691 self.tracker.SetTracker(choice)
693 if self.tracker.tracker_id == const.ROBOT: 692 if self.tracker.tracker_id == const.ROBOT:
694 - self.tracker.ConnectToRobot(self.navigation, self.tracker, self.robot) 693 + self.dlg_correg_robot = dlg.CreateTransformationMatrixRobot(self.tracker)
  694 + if self.dlg_correg_robot.ShowModal() == wx.ID_OK:
  695 + Publisher.sendMessage('Robot navigation mode', robot_mode=True)
  696 + else:
  697 + Publisher.sendMessage('Disconnect tracker')
  698 + wx.MessageBox(_("Not possible to connect to the robot."), _("InVesalius 3"))
695 699
696 self.ResetICP() 700 self.ResetICP()
697 self.tracker.UpdateUI(ctrl, self.numctrls_fiducial[3:6], self.txtctrl_fre) 701 self.tracker.UpdateUI(ctrl, self.numctrls_fiducial[3:6], self.txtctrl_fre)
@@ -776,8 +780,8 @@ class NeuronavigationPanel(wx.Panel): @@ -776,8 +780,8 @@ class NeuronavigationPanel(wx.Panel):
776 780
777 self.navigation.StopNavigation() 781 self.navigation.StopNavigation()
778 if self.tracker.tracker_id == const.ROBOT: 782 if self.tracker.tracker_id == const.ROBOT:
779 - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False,  
780 - m_change_robot_to_head=None) 783 + Publisher.sendMessage('Update robot target', robot_tracker_flag=False,
  784 + target_index=None, target=None)
781 785
782 # Enable all navigation buttons 786 # Enable all navigation buttons
783 choice_ref.Enable(True) 787 choice_ref.Enable(True)
@@ -786,6 +790,10 @@ class NeuronavigationPanel(wx.Panel): @@ -786,6 +790,10 @@ class NeuronavigationPanel(wx.Panel):
786 for btn_c in self.btns_set_fiducial: 790 for btn_c in self.btns_set_fiducial:
787 btn_c.Enable(True) 791 btn_c.Enable(True)
788 792
  793 + def OnDialogRobotDestroy(self):
  794 + if self.dlg_correg_robot:
  795 + self.dlg_correg_robot.Destroy()
  796 +
789 def CheckFiducialRegistrationError(self): 797 def CheckFiducialRegistrationError(self):
790 self.navigation.UpdateFiducialRegistrationError(self.tracker) 798 self.navigation.UpdateFiducialRegistrationError(self.tracker)
791 fre, fre_ok = self.navigation.GetFiducialRegistrationError(self.icp) 799 fre, fre_ok = self.navigation.GetFiducialRegistrationError(self.icp)
@@ -820,7 +828,7 @@ class NeuronavigationPanel(wx.Panel): @@ -820,7 +828,7 @@ class NeuronavigationPanel(wx.Panel):
820 for btn_c in self.btns_set_fiducial: 828 for btn_c in self.btns_set_fiducial:
821 btn_c.Enable(False) 829 btn_c.Enable(False)
822 830
823 - self.navigation.StartNavigation(self.tracker) 831 + self.navigation.EstimateTrackerToInVTransformationMatrix(self.tracker)
824 832
825 if not self.CheckFiducialRegistrationError(): 833 if not self.CheckFiducialRegistrationError():
826 # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok 834 # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok
@@ -830,9 +838,11 @@ class NeuronavigationPanel(wx.Panel): @@ -830,9 +838,11 @@ class NeuronavigationPanel(wx.Panel):
830 if self.icp.use_icp: 838 if self.icp.use_icp:
831 self.checkbox_icp.Enable(True) 839 self.checkbox_icp.Enable(True)
832 self.checkbox_icp.SetValue(True) 840 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() 841 + # Update FRE once more after starting the navigation, due to the optional use of ICP,
  842 + # which improves FRE.
  843 + self.CheckFiducialRegistrationError()
  844 +
  845 + self.navigation.StartNavigation(self.tracker)
836 846
837 def OnNavigate(self, evt, btn_nav): 847 def OnNavigate(self, evt, btn_nav):
838 select_tracker_elem = self.select_tracker_elem 848 select_tracker_elem = self.select_tracker_elem
@@ -881,11 +891,10 @@ class NeuronavigationPanel(wx.Panel): @@ -881,11 +891,10 @@ class NeuronavigationPanel(wx.Panel):
881 ) 891 )
882 self.tracker.__init__() 892 self.tracker.__init__()
883 self.icp.__init__() 893 self.icp.__init__()
884 - self.robot.__init__(self.tracker)  
885 894
886 895
887 class ObjectRegistrationPanel(wx.Panel): 896 class ObjectRegistrationPanel(wx.Panel):
888 - def __init__(self, parent, tracker, pedal_connection): 897 + def __init__(self, parent, tracker, pedal_connection, neuronavigation_api):
889 wx.Panel.__init__(self, parent) 898 wx.Panel.__init__(self, parent)
890 try: 899 try:
891 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) 900 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR)
@@ -897,6 +906,7 @@ class ObjectRegistrationPanel(wx.Panel): @@ -897,6 +906,7 @@ class ObjectRegistrationPanel(wx.Panel):
897 906
898 self.tracker = tracker 907 self.tracker = tracker
899 self.pedal_connection = pedal_connection 908 self.pedal_connection = pedal_connection
  909 + self.neuronavigation_api = neuronavigation_api
900 910
901 self.nav_prop = None 911 self.nav_prop = None
902 self.obj_fiducials = None 912 self.obj_fiducials = None
@@ -913,7 +923,7 @@ class ObjectRegistrationPanel(wx.Panel): @@ -913,7 +923,7 @@ class ObjectRegistrationPanel(wx.Panel):
913 btn_new = wx.Button(self, -1, _("New"), size=wx.Size(65, 23)) 923 btn_new = wx.Button(self, -1, _("New"), size=wx.Size(65, 23))
914 btn_new.SetToolTip(tooltip) 924 btn_new.SetToolTip(tooltip)
915 btn_new.Enable(1) 925 btn_new.Enable(1)
916 - btn_new.Bind(wx.EVT_BUTTON, self.OnLinkCreate) 926 + btn_new.Bind(wx.EVT_BUTTON, self.OnCreateNewCoil)
917 self.btn_new = btn_new 927 self.btn_new = btn_new
918 928
919 # Button for import config coil file 929 # Button for import config coil file
@@ -1056,13 +1066,16 @@ class ObjectRegistrationPanel(wx.Panel): @@ -1056,13 +1066,16 @@ class ObjectRegistrationPanel(wx.Panel):
1056 coil_index = evt.GetSelection() 1066 coil_index = evt.GetSelection()
1057 Publisher.sendMessage('Change selected coil', self.coil_list[coil_index][1]) 1067 Publisher.sendMessage('Change selected coil', self.coil_list[coil_index][1])
1058 1068
1059 - def OnLinkCreate(self, event=None): 1069 + def OnCreateNewCoil(self, event=None):
1060 1070
1061 if self.tracker.IsTrackerInitialized(): 1071 if self.tracker.IsTrackerInitialized():
1062 dialog = dlg.ObjectCalibrationDialog(self.tracker, self.pedal_connection) 1072 dialog = dlg.ObjectCalibrationDialog(self.tracker, self.pedal_connection)
1063 try: 1073 try:
1064 if dialog.ShowModal() == wx.ID_OK: 1074 if dialog.ShowModal() == wx.ID_OK:
1065 self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name, polydata, use_default_object = dialog.GetValue() 1075 self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name, polydata, use_default_object = dialog.GetValue()
  1076 +
  1077 + self.neuronavigation_api.update_coil_mesh(polydata)
  1078 +
1066 if np.isfinite(self.obj_fiducials).all() and np.isfinite(self.obj_orients).all(): 1079 if np.isfinite(self.obj_fiducials).all() and np.isfinite(self.obj_orients).all():
1067 self.checktrack.Enable(1) 1080 self.checktrack.Enable(1)
1068 Publisher.sendMessage('Update object registration', 1081 Publisher.sendMessage('Update object registration',
@@ -1248,20 +1261,7 @@ class MarkersPanel(wx.Panel): @@ -1248,20 +1261,7 @@ class MarkersPanel(wx.Panel):
1248 if field.type is bool: 1261 if field.type is bool:
1249 setattr(self, field.name, str_val=='True') 1262 setattr(self, field.name, str_val=='True')
1250 1263
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): 1264 + def __init__(self, parent, navigation, tracker, icp):
1265 wx.Panel.__init__(self, parent) 1265 wx.Panel.__init__(self, parent)
1266 try: 1266 try:
1267 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) 1267 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR)
@@ -1273,6 +1273,7 @@ class MarkersPanel(wx.Panel): @@ -1273,6 +1273,7 @@ class MarkersPanel(wx.Panel):
1273 1273
1274 self.navigation = navigation 1274 self.navigation = navigation
1275 self.tracker = tracker 1275 self.tracker = tracker
  1276 + self.icp = icp
1276 1277
1277 self.__bind_events() 1278 self.__bind_events()
1278 1279
@@ -1280,11 +1281,9 @@ class MarkersPanel(wx.Panel): @@ -1280,11 +1281,9 @@ class MarkersPanel(wx.Panel):
1280 1281
1281 self.current_coord = [0, 0, 0, None, None, None] 1282 self.current_coord = [0, 0, 0, None, None, None]
1282 self.current_seed = 0, 0, 0 1283 self.current_seed = 0, 0, 0
1283 - self.current_robot_target_matrix = [None] * 9 1284 +
1284 self.markers = [] 1285 self.markers = []
1285 - self.robot_markers = []  
1286 self.nav_status = False 1286 self.nav_status = False
1287 - self.raw_target_robot = None, None  
1288 1287
1289 self.marker_colour = const.MARKER_COLOUR 1288 self.marker_colour = const.MARKER_COLOUR
1290 self.marker_size = const.MARKER_SIZE 1289 self.marker_size = const.MARKER_SIZE
@@ -1383,7 +1382,6 @@ class MarkersPanel(wx.Panel): @@ -1383,7 +1382,6 @@ class MarkersPanel(wx.Panel):
1383 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') 1382 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status')
1384 Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts') 1383 Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts')
1385 Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed') 1384 Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed')
1386 - Publisher.subscribe(self.UpdateRobotCoordinates, 'Update raw coordinates')  
1387 1385
1388 def __find_target_marker(self): 1386 def __find_target_marker(self):
1389 """ 1387 """
@@ -1417,7 +1415,6 @@ class MarkersPanel(wx.Panel): @@ -1417,7 +1415,6 @@ class MarkersPanel(wx.Panel):
1417 """ 1415 """
1418 for i in reversed(index): 1416 for i in reversed(index):
1419 del self.markers[i] 1417 del self.markers[i]
1420 - del self.robot_markers[i]  
1421 self.lc.DeleteItem(i) 1418 self.lc.DeleteItem(i)
1422 for n in range(0, self.lc.GetItemCount()): 1419 for n in range(0, self.lc.GetItemCount()):
1423 self.lc.SetItem(n, 0, str(n + 1)) 1420 self.lc.SetItem(n, 0, str(n + 1))
@@ -1470,34 +1467,37 @@ class MarkersPanel(wx.Panel): @@ -1470,34 +1467,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)): 1467 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 1468 self.current_seed = coord_offset_w
1472 1469
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): 1470 def OnMouseRightDown(self, evt):
1477 # TODO: Enable the "Set as target" only when target is created with registered object 1471 # TODO: Enable the "Set as target" only when target is created with registered object
1478 menu_id = wx.Menu() 1472 menu_id = wx.Menu()
1479 edit_id = menu_id.Append(0, _('Edit label')) 1473 edit_id = menu_id.Append(0, _('Edit label'))
1480 menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerLabel, edit_id) 1474 menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerLabel, edit_id)
1481 - color_id = menu_id.Append(2, _('Edit color')) 1475 + color_id = menu_id.Append(1, _('Edit color'))
1482 menu_id.Bind(wx.EVT_MENU, self.OnMenuSetColor, color_id) 1476 menu_id.Bind(wx.EVT_MENU, self.OnMenuSetColor, color_id)
1483 menu_id.AppendSeparator() 1477 menu_id.AppendSeparator()
1484 - target_menu = menu_id.Append(1, _('Set as target')) 1478 + target_menu = menu_id.Append(2, _('Set as target'))
1485 menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) 1479 menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu)
1486 menu_id.AppendSeparator() 1480 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 1481
1490 - if all([elem is not None for elem in self.markers[self.lc.GetFocusedItem()].coord[3:]]): 1482 + check_target_angles = all([elem is not None for elem in self.markers[self.lc.GetFocusedItem()].coord[3:]])
  1483 + # Enable "Send target to robot" button only if tracker is robot, if navigation is on and if target is not none
  1484 + if self.tracker.tracker_id == const.ROBOT:
  1485 + send_target_to_robot_compensation = menu_id.Append(3, _('Sets target to robot head move compensation'))
  1486 + menu_id.Bind(wx.EVT_MENU, self.OnMenuSetRobotCompensation, send_target_to_robot_compensation)
  1487 + send_target_to_robot = menu_id.Append(4, _('Send target from InVesalius to robot'))
  1488 + menu_id.Bind(wx.EVT_MENU, self.OnMenuSendTargetToRobot, send_target_to_robot)
  1489 + if self.nav_status and check_target_angles:
  1490 + send_target_to_robot_compensation.Enable(True)
  1491 + send_target_to_robot.Enable(True)
  1492 + else:
  1493 + send_target_to_robot_compensation.Enable(False)
  1494 + send_target_to_robot.Enable(False)
  1495 +
  1496 + if check_target_angles:
1491 target_menu.Enable(True) 1497 target_menu.Enable(True)
1492 else: 1498 else:
1493 target_menu.Enable(False) 1499 target_menu.Enable(False)
1494 1500
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 1501 # 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')) 1502 # target_menu_rem = menu_id.Append(3, _('Remove target'))
1503 # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem) 1503 # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem)
@@ -1547,14 +1547,33 @@ class MarkersPanel(wx.Panel): @@ -1547,14 +1547,33 @@ class MarkersPanel(wx.Panel):
1547 1547
1548 Publisher.sendMessage('Set new color', index=index, color=color_new) 1548 Publisher.sendMessage('Set new color', index=index, color=color_new)
1549 1549
  1550 + def OnMenuSetRobotCompensation(self, evt):
  1551 + if isinstance(evt, int):
  1552 + self.lc.Focus(evt)
  1553 +
  1554 + Publisher.sendMessage('Reset robot process', data=None)
  1555 + matrix_tracker_fiducials = self.tracker.GetMatrixTrackerFiducials()
  1556 + Publisher.sendMessage('Update tracker fiducials matrix',
  1557 + matrix_tracker_fiducials=matrix_tracker_fiducials)
  1558 + Publisher.sendMessage('Update robot target', robot_tracker_flag=True, target_index=self.lc.GetFocusedItem(), target=None)
  1559 +
1550 def OnMenuSendTargetToRobot(self, evt): 1560 def OnMenuSendTargetToRobot(self, evt):
1551 if isinstance(evt, int): 1561 if isinstance(evt, int):
1552 self.lc.Focus(evt) 1562 self.lc.Focus(evt)
  1563 + index = self.lc.GetFocusedItem()
  1564 + if index == -1:
  1565 + wx.MessageBox(_("No data selected."), _("InVesalius 3"))
  1566 + return
  1567 +
  1568 + Publisher.sendMessage('Reset robot process', data=None)
  1569 + matrix_tracker_fiducials = self.tracker.GetMatrixTrackerFiducials()
  1570 + Publisher.sendMessage('Update tracker fiducials matrix',
  1571 + matrix_tracker_fiducials=matrix_tracker_fiducials)
1553 1572
1554 - m_target_robot = self.robot_markers[self.lc.GetFocusedItem()].robot_target_matrix 1573 + target_coord = self.markers[index].coord[:3]
  1574 + target = dcr.image_to_tracker(self.navigation.m_change, target_coord, self.icp)
1555 1575
1556 - Publisher.sendMessage('Reset robot process')  
1557 - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=True, m_change_robot_to_head=m_target_robot) 1576 + Publisher.sendMessage('Update robot target', robot_tracker_flag=True, target_index=self.lc.GetFocusedItem(), target=target.tolist())
1558 1577
1559 def OnDeleteAllMarkers(self, evt=None): 1578 def OnDeleteAllMarkers(self, evt=None):
1560 if evt is not None: 1579 if evt is not None:
@@ -1566,9 +1585,11 @@ class MarkersPanel(wx.Panel): @@ -1566,9 +1585,11 @@ class MarkersPanel(wx.Panel):
1566 Publisher.sendMessage('Disable or enable coil tracker', status=False) 1585 Publisher.sendMessage('Disable or enable coil tracker', status=False)
1567 if evt is not None: 1586 if evt is not None:
1568 wx.MessageBox(_("Target deleted."), _("InVesalius 3")) 1587 wx.MessageBox(_("Target deleted."), _("InVesalius 3"))
  1588 + if self.tracker.tracker_id == const.ROBOT:
  1589 + Publisher.sendMessage('Update robot target', robot_tracker_flag=False,
  1590 + target_index=None, target=None)
1569 1591
1570 self.markers = [] 1592 self.markers = []
1571 - self.robot_markers = []  
1572 Publisher.sendMessage('Remove all markers', indexes=self.lc.GetItemCount()) 1593 Publisher.sendMessage('Remove all markers', indexes=self.lc.GetItemCount())
1573 self.lc.DeleteAllItems() 1594 self.lc.DeleteAllItems()
1574 Publisher.sendMessage('Stop Blink Marker', index='DeleteAll') 1595 Publisher.sendMessage('Stop Blink Marker', index='DeleteAll')
@@ -1596,8 +1617,8 @@ class MarkersPanel(wx.Panel): @@ -1596,8 +1617,8 @@ class MarkersPanel(wx.Panel):
1596 if self.__find_target_marker() in index: 1617 if self.__find_target_marker() in index:
1597 Publisher.sendMessage('Disable or enable coil tracker', status=False) 1618 Publisher.sendMessage('Disable or enable coil tracker', status=False)
1598 if self.tracker.tracker_id == const.ROBOT: 1619 if self.tracker.tracker_id == const.ROBOT:
1599 - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False,  
1600 - m_change_robot_to_head=[]) 1620 + Publisher.sendMessage('Update robot target', robot_tracker_flag=False,
  1621 + target_index=None, target=None)
1601 wx.MessageBox(_("Target deleted."), _("InVesalius 3")) 1622 wx.MessageBox(_("Target deleted."), _("InVesalius 3"))
1602 1623
1603 self.__delete_multiple_markers(index) 1624 self.__delete_multiple_markers(index)
@@ -1702,12 +1723,11 @@ class MarkersPanel(wx.Panel): @@ -1702,12 +1723,11 @@ class MarkersPanel(wx.Panel):
1702 new_marker.session_id = session_id or self.current_session 1723 new_marker.session_id = session_id or self.current_session
1703 1724
1704 if self.tracker.tracker_id == const.ROBOT and self.nav_status: 1725 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) 1726 + current_head_robot_target_status = True
1706 else: 1727 else:
1707 - self.current_robot_target_matrix = [None] * 9 1728 + current_head_robot_target_status = False
1708 1729
1709 - new_robot_marker = self.Robot_Marker()  
1710 - new_robot_marker.robot_target_matrix = self.current_robot_target_matrix 1730 + Publisher.sendMessage('Add marker to robot control', data=current_head_robot_target_status)
1711 1731
1712 # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added 1732 # 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:]]): 1733 if all([elem is not None for elem in new_marker.coord[3:]]):
@@ -1723,7 +1743,6 @@ class MarkersPanel(wx.Panel): @@ -1723,7 +1743,6 @@ class MarkersPanel(wx.Panel):
1723 1743
1724 1744
1725 self.markers.append(new_marker) 1745 self.markers.append(new_marker)
1726 - self.robot_markers.append(new_robot_marker)  
1727 1746
1728 # Add item to list control in panel 1747 # Add item to list control in panel
1729 num_items = self.lc.GetItemCount() 1748 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/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/neuronavigation_api.py
@@ -20,6 +20,9 @@ @@ -20,6 +20,9 @@
20 from invesalius.pubsub import pub as Publisher 20 from invesalius.pubsub import pub as Publisher
21 from invesalius.utils import Singleton 21 from invesalius.utils import Singleton
22 22
  23 +import numpy as np
  24 +from vtk.numpy_interface import dataset_adapter
  25 +
23 class NeuronavigationApi(metaclass=Singleton): 26 class NeuronavigationApi(metaclass=Singleton):
24 """ 27 """
25 An API used internally in InVesalius to communicate with the 28 An API used internally in InVesalius to communicate with the
@@ -34,6 +37,8 @@ class NeuronavigationApi(metaclass=Singleton): @@ -34,6 +37,8 @@ class NeuronavigationApi(metaclass=Singleton):
34 37
35 If connection object is not given or it is None, skip doing the updates. 38 If connection object is not given or it is None, skip doing the updates.
36 """ 39 """
  40 + N_VERTICES_IN_POLYGON = 3
  41 +
37 def __init__(self, connection=None): 42 def __init__(self, connection=None):
38 if connection is not None: 43 if connection is not None:
39 assert self._hasmethod(connection, 'update_coil_pose') 44 assert self._hasmethod(connection, 'update_coil_pose')
@@ -65,3 +70,28 @@ class NeuronavigationApi(metaclass=Singleton): @@ -65,3 +70,28 @@ class NeuronavigationApi(metaclass=Singleton):
65 position=position, 70 position=position,
66 orientation=orientation, 71 orientation=orientation,
67 ) 72 )
  73 +
  74 + def update_coil_mesh(self, polydata):
  75 + wrapped = dataset_adapter.WrapDataObject(polydata)
  76 +
  77 + points = np.asarray(wrapped.Points)
  78 + polygons_raw = np.asarray(wrapped.Polygons)
  79 +
  80 + # The polygons are returned as 1d-array of the form
  81 + #
  82 + # [n_0, id_0(0), id_0(1), ..., id_0(n_0),
  83 + # n_1, id_1(0), id_1(1), ..., id_1(n_1),
  84 + # ...]
  85 + #
  86 + # where n_i is the number of vertices in polygon i, and id_i's are indices to the vertex list.
  87 + #
  88 + # Assert that all polygons have an equal number of vertices, reshape the array, and drop n_i's.
  89 + #
  90 + assert np.all(polygons_raw[0::self.N_VERTICES_IN_POLYGON + 1] == self.N_VERTICES_IN_POLYGON)
  91 +
  92 + polygons = polygons_raw.reshape(-1, self.N_VERTICES_IN_POLYGON + 1)[:, 1:]
  93 +
  94 + self.connection.update_coil_mesh(
  95 + points=points,
  96 + polygons=polygons,
  97 + )
invesalius/net/remote_control.py
@@ -79,5 +79,7 @@ class RemoteControl: @@ -79,5 +79,7 @@ class RemoteControl:
79 }) 79 })
80 except TypeError: 80 except TypeError:
81 pass 81 pass
  82 + except socketio.exceptions.BadNamespaceError:
  83 + pass
82 84
83 Publisher.add_sendMessage_hook(_emit) 85 Publisher.add_sendMessage_hook(_emit)
requirements_m1.txt 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +Cython==0.29.24
  2 +Pillow==9.0.0
  3 +Pypubsub==4.0.3
  4 +configparser==5.0.1
  5 +h5py==3.6.0
  6 +imageio==2.9.0
  7 +nibabel==3.2.1
  8 +numpy==1.22.1
  9 +psutil==5.8.0
  10 +pyserial==3.5
  11 +python-gdcm==3.0.9.1
  12 +scikit-image==0.19.1
  13 +scipy==1.7.3
  14 +vtk==9.0.3
  15 +wxPython==4.1.1
  16 +torch==1.10.0
  17 +pyacvd==0.2.7