Commit 3185fc2b24e5c0fad0a6c35c645c34babe08a2a6
Committed by
GitHub
Exists in
master
Merge branch 'invesalius:master' into 278_add_visualization_for_the_direction_of_the_marker
Showing
23 changed files
with
1600 additions
and
308 deletions
Show diff stats
app.py
@@ -87,11 +87,11 @@ session = ses.Session() | @@ -87,11 +87,11 @@ session = ses.Session() | ||
87 | if session.ReadSession(): | 87 | if session.ReadSession(): |
88 | lang = session.GetLanguage() | 88 | lang = session.GetLanguage() |
89 | if lang: | 89 | if lang: |
90 | - LANG = lang | ||
91 | try: | 90 | try: |
92 | _ = i18n.InstallLanguage(lang) | 91 | _ = i18n.InstallLanguage(lang) |
92 | + LANG = lang | ||
93 | except FileNotFoundError: | 93 | except FileNotFoundError: |
94 | - LANG = None | 94 | + pass |
95 | 95 | ||
96 | 96 | ||
97 | class InVesalius(wx.App): | 97 | class InVesalius(wx.App): |
@@ -134,7 +134,7 @@ class Inv3SplashScreen(SplashScreen): | @@ -134,7 +134,7 @@ class Inv3SplashScreen(SplashScreen): | ||
134 | Splash screen to be shown in InVesalius initialization. | 134 | Splash screen to be shown in InVesalius initialization. |
135 | """ | 135 | """ |
136 | def __init__(self): | 136 | def __init__(self): |
137 | - # Splash screen image will depend on currently language | 137 | + # Splash screen image will depend on the current language |
138 | lang = LANG | 138 | lang = LANG |
139 | self.locale = wx.Locale(wx.LANGUAGE_ENGLISH) | 139 | self.locale = wx.Locale(wx.LANGUAGE_ENGLISH) |
140 | 140 | ||
@@ -143,22 +143,20 @@ class Inv3SplashScreen(SplashScreen): | @@ -143,22 +143,20 @@ class Inv3SplashScreen(SplashScreen): | ||
143 | # should be created | 143 | # should be created |
144 | create_session = LANG is None | 144 | create_session = LANG is None |
145 | 145 | ||
146 | - install_lang = 0 | 146 | + install_lang = False |
147 | if lang: | 147 | if lang: |
148 | _ = i18n.InstallLanguage(lang) | 148 | _ = i18n.InstallLanguage(lang) |
149 | - install_lang = 1 | ||
150 | - else: | ||
151 | - install_lang = 0 | 149 | + install_lang = True |
152 | 150 | ||
153 | # If no language is set into session file, show dialog so | 151 | # If no language is set into session file, show dialog so |
154 | # user can select language | 152 | # user can select language |
155 | - if install_lang == 0: | 153 | + if not install_lang: |
156 | dialog = lang_dlg.LanguageDialog() | 154 | dialog = lang_dlg.LanguageDialog() |
157 | 155 | ||
158 | # FIXME: This works ok in linux2, darwin and win32, | 156 | # FIXME: This works ok in linux2, darwin and win32, |
159 | # except on win64, due to wxWidgets bug | 157 | # except on win64, due to wxWidgets bug |
160 | try: | 158 | try: |
161 | - ok = (dialog.ShowModal() == wx.ID_OK) | 159 | + ok = dialog.ShowModal() == wx.ID_OK |
162 | except wx._core.PyAssertionError: | 160 | except wx._core.PyAssertionError: |
163 | ok = True | 161 | ok = True |
164 | finally: | 162 | finally: |
@@ -174,9 +172,8 @@ class Inv3SplashScreen(SplashScreen): | @@ -174,9 +172,8 @@ class Inv3SplashScreen(SplashScreen): | ||
174 | 172 | ||
175 | dialog.Destroy() | 173 | dialog.Destroy() |
176 | 174 | ||
177 | - # Session file should be created... So we set the recent | ||
178 | - # choosen language | ||
179 | - if (create_session): | 175 | + # Session file should be created... So we set the recently chosen language. |
176 | + if create_session: | ||
180 | session.CreateItens() | 177 | session.CreateItens() |
181 | session.SetLanguage(lang) | 178 | session.SetLanguage(lang) |
182 | session.WriteSessionFile() | 179 | session.WriteSessionFile() |
@@ -184,8 +181,7 @@ class Inv3SplashScreen(SplashScreen): | @@ -184,8 +181,7 @@ class Inv3SplashScreen(SplashScreen): | ||
184 | # session.SaveConfigFileBackup() | 181 | # session.SaveConfigFileBackup() |
185 | 182 | ||
186 | 183 | ||
187 | - # Only after language was defined, splash screen will be | ||
188 | - # shown | 184 | + # Only after language was defined, splash screen will be shown. |
189 | if lang: | 185 | if lang: |
190 | 186 | ||
191 | #import locale | 187 | #import locale |
@@ -206,9 +202,7 @@ class Inv3SplashScreen(SplashScreen): | @@ -206,9 +202,7 @@ class Inv3SplashScreen(SplashScreen): | ||
206 | abs_file_path = os.path.abspath(".." + os.sep) | 202 | abs_file_path = os.path.abspath(".." + os.sep) |
207 | path = abs_file_path | 203 | path = abs_file_path |
208 | path = os.path.join(path, 'icons', icon_file) | 204 | path = os.path.join(path, 'icons', icon_file) |
209 | - | ||
210 | else: | 205 | else: |
211 | - | ||
212 | path = os.path.join(inv_paths.ICON_DIR, icon_file) | 206 | path = os.path.join(inv_paths.ICON_DIR, icon_file) |
213 | if not os.path.exists(path): | 207 | if not os.path.exists(path): |
214 | path = os.path.join(inv_paths.ICON_DIR, "splash_en.png") | 208 | path = os.path.join(inv_paths.ICON_DIR, "splash_en.png") |
@@ -241,7 +235,7 @@ class Inv3SplashScreen(SplashScreen): | @@ -241,7 +235,7 @@ class Inv3SplashScreen(SplashScreen): | ||
241 | self.control = Controller(self.main) | 235 | self.control = Controller(self.main) |
242 | 236 | ||
243 | self.fc = wx.CallLater(200, self.ShowMain) | 237 | self.fc = wx.CallLater(200, self.ShowMain) |
244 | - options, args = parse_comand_line() | 238 | + options, args = parse_command_line() |
245 | wx.CallLater(1, use_cmd_optargs, options, args) | 239 | wx.CallLater(1, use_cmd_optargs, options, args) |
246 | 240 | ||
247 | # Check for updates | 241 | # Check for updates |
@@ -292,7 +286,7 @@ def non_gui_startup(options, args): | @@ -292,7 +286,7 @@ def non_gui_startup(options, args): | ||
292 | # ------------------------------------------------------------------ | 286 | # ------------------------------------------------------------------ |
293 | 287 | ||
294 | 288 | ||
295 | -def parse_comand_line(): | 289 | +def parse_command_line(): |
296 | """ | 290 | """ |
297 | Handle command line arguments. | 291 | Handle command line arguments. |
298 | """ | 292 | """ |
@@ -302,7 +296,7 @@ def parse_comand_line(): | @@ -302,7 +296,7 @@ def parse_comand_line(): | ||
302 | # Parse command line arguments | 296 | # Parse command line arguments |
303 | parser = op.OptionParser() | 297 | parser = op.OptionParser() |
304 | 298 | ||
305 | - # -d or --debug: print all pubsub messagessent | 299 | + # -d or --debug: print all pubsub messages sent |
306 | parser.add_option("-d", "--debug", | 300 | parser.add_option("-d", "--debug", |
307 | action="store_true", | 301 | action="store_true", |
308 | dest="debug") | 302 | dest="debug") |
@@ -353,12 +347,6 @@ def parse_comand_line(): | @@ -353,12 +347,6 @@ def parse_comand_line(): | ||
353 | 347 | ||
354 | 348 | ||
355 | def use_cmd_optargs(options, args): | 349 | def use_cmd_optargs(options, args): |
356 | - # If debug argument... | ||
357 | - if options.debug: | ||
358 | - Publisher.subscribe(print_events, Publisher.ALL_TOPICS) | ||
359 | - session = ses.Session() | ||
360 | - session.debug = 1 | ||
361 | - | ||
362 | # If import DICOM argument... | 350 | # If import DICOM argument... |
363 | if options.dicom_dir: | 351 | if options.dicom_dir: |
364 | import_dir = options.dicom_dir | 352 | import_dir = options.dicom_dir |
@@ -494,11 +482,18 @@ def print_events(topic=Publisher.AUTO_TOPIC, **msg_data): | @@ -494,11 +482,18 @@ def print_events(topic=Publisher.AUTO_TOPIC, **msg_data): | ||
494 | """ | 482 | """ |
495 | utils.debug("%s\n\tParameters: %s" % (topic, msg_data)) | 483 | utils.debug("%s\n\tParameters: %s" % (topic, msg_data)) |
496 | 484 | ||
485 | + | ||
497 | def main(): | 486 | def main(): |
498 | """ | 487 | """ |
499 | Initialize InVesalius GUI | 488 | Initialize InVesalius GUI |
500 | """ | 489 | """ |
501 | - options, args = parse_comand_line() | 490 | + options, args = parse_command_line() |
491 | + | ||
492 | + session = ses.Session() | ||
493 | + session.debug = options.debug | ||
494 | + | ||
495 | + if options.debug: | ||
496 | + Publisher.subscribe(print_events, Publisher.ALL_TOPICS) | ||
502 | 497 | ||
503 | if options.remote_host is not None: | 498 | if options.remote_host is not None: |
504 | from invesalius.net.remote_control import RemoteControl | 499 | from invesalius.net.remote_control import RemoteControl |
@@ -517,6 +512,7 @@ def main(): | @@ -517,6 +512,7 @@ def main(): | ||
517 | application = InVesalius(0) | 512 | application = InVesalius(0) |
518 | application.MainLoop() | 513 | application.MainLoop() |
519 | 514 | ||
515 | + | ||
520 | if __name__ == '__main__': | 516 | if __name__ == '__main__': |
521 | #Is needed because of pyinstaller | 517 | #Is needed because of pyinstaller |
522 | multiprocessing.freeze_support() | 518 | multiprocessing.freeze_support() |
invesalius/constants.py
@@ -655,6 +655,18 @@ BOOLEAN_DIFF = 2 | @@ -655,6 +655,18 @@ BOOLEAN_DIFF = 2 | ||
655 | BOOLEAN_AND = 3 | 655 | BOOLEAN_AND = 3 |
656 | BOOLEAN_XOR = 4 | 656 | BOOLEAN_XOR = 4 |
657 | 657 | ||
658 | +# -------------- User interface --------------------- | ||
659 | + | ||
660 | +# The column order in the marker panel | ||
661 | +# | ||
662 | +ID_COLUMN = 0 | ||
663 | +SESSION_COLUMN = 1 | ||
664 | +LABEL_COLUMN = 2 | ||
665 | +TARGET_COLUMN = 3 | ||
666 | +X_COLUMN = 4 | ||
667 | +Y_COLUMN = 5 | ||
668 | +Z_COLUMN = 6 | ||
669 | + | ||
658 | #------------ Navigation defaults ------------------- | 670 | #------------ Navigation defaults ------------------- |
659 | 671 | ||
660 | MARKER_COLOUR = (1.0, 1.0, 0.) | 672 | MARKER_COLOUR = (1.0, 1.0, 0.) |
@@ -672,18 +684,19 @@ CAMERA = 5 | @@ -672,18 +684,19 @@ CAMERA = 5 | ||
672 | POLARIS = 6 | 684 | POLARIS = 6 |
673 | POLARISP4 = 7 | 685 | POLARISP4 = 7 |
674 | OPTITRACK = 8 | 686 | OPTITRACK = 8 |
675 | -DEBUGTRACKRANDOM = 9 | ||
676 | -DEBUGTRACKAPPROACH = 10 | 687 | +ROBOT = 9 |
688 | +DEBUGTRACKRANDOM = 10 | ||
689 | +DEBUGTRACKAPPROACH = 11 | ||
677 | DEFAULT_TRACKER = SELECT | 690 | DEFAULT_TRACKER = SELECT |
678 | 691 | ||
679 | NDICOMPORT = b'COM1' | 692 | NDICOMPORT = b'COM1' |
680 | 693 | ||
681 | TRACKERS = [_("Claron MicronTracker"), | 694 | TRACKERS = [_("Claron MicronTracker"), |
682 | - _("Polhemus FASTRAK"), _("Polhemus ISOTRAK II"), | ||
683 | - _("Polhemus PATRIOT"), _("Camera tracker"), | ||
684 | - _("NDI Polaris"), _("NDI Polaris P4"), | ||
685 | - _("Optitrack"), _("Debug tracker (random)"), | ||
686 | - _("Debug tracker (approach)")] | 695 | + _("Polhemus FASTRAK"), _("Polhemus ISOTRAK II"), |
696 | + _("Polhemus PATRIOT"), _("Camera tracker"), | ||
697 | + _("NDI Polaris"), _("NDI Polaris P4"), | ||
698 | + _("Optitrack"), _("Robot tracker"), | ||
699 | + _("Debug tracker (random)"), _("Debug tracker (approach)")] | ||
687 | 700 | ||
688 | STATIC_REF = 0 | 701 | STATIC_REF = 0 |
689 | DYNAMIC_REF = 1 | 702 | DYNAMIC_REF = 1 |
@@ -835,3 +848,13 @@ WILDCARD_MARKER_FILES = _("Marker scanner coord files (*.mkss)|*.mkss") | @@ -835,3 +848,13 @@ WILDCARD_MARKER_FILES = _("Marker scanner coord files (*.mkss)|*.mkss") | ||
835 | BAUD_RATES = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200] | 848 | BAUD_RATES = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200] |
836 | BAUD_RATE_DEFAULT_SELECTION = 4 | 849 | BAUD_RATE_DEFAULT_SELECTION = 4 |
837 | PULSE_DURATION_IN_MILLISECONDS = 0.2 | 850 | PULSE_DURATION_IN_MILLISECONDS = 0.2 |
851 | + | ||
852 | +#Robot | ||
853 | +ROBOT_ElFIN_IP = ['143.107.220.251', '169.254.153.251', '127.0.0.1'] | ||
854 | +ROBOT_ElFIN_PORT = 10003 | ||
855 | +ROBOT_MOTIONS = {"normal": 0, "linear out": 1, "arc": 2} | ||
856 | +ROBOT_HEAD_VELOCITY_THRESHOLD = 10 #mm/s | ||
857 | +ROBOT_ARC_THRESHOLD_DISTANCE = 100 #mm | ||
858 | +ROBOT_VERSOR_SCALE_FACTOR = 70 | ||
859 | +#Robot Working Space is defined as 800mm in Elfin manual. For safety, the value is reduced by 5%. | ||
860 | +ROBOT_WORKING_SPACE = 760 #mm |
invesalius/control.py
@@ -965,7 +965,7 @@ class Controller(): | @@ -965,7 +965,7 @@ class Controller(): | ||
965 | re_dialog = dialog.ResizeImageDialog() | 965 | re_dialog = dialog.ResizeImageDialog() |
966 | re_dialog.SetValue(int(resolution_percentage*100)) | 966 | re_dialog.SetValue(int(resolution_percentage*100)) |
967 | re_dialog_value = re_dialog.ShowModal() | 967 | re_dialog_value = re_dialog.ShowModal() |
968 | - re_dialog.Close() | 968 | + re_dialog.Close() |
969 | 969 | ||
970 | if re_dialog_value == wx.ID_OK: | 970 | if re_dialog_value == wx.ID_OK: |
971 | percentage = re_dialog.GetValue() | 971 | percentage = re_dialog.GetValue() |
@@ -1143,7 +1143,7 @@ class Controller(): | @@ -1143,7 +1143,7 @@ class Controller(): | ||
1143 | def show_mask_preview(self, index, flag=True): | 1143 | def show_mask_preview(self, index, flag=True): |
1144 | proj = prj.Project() | 1144 | proj = prj.Project() |
1145 | mask = proj.mask_dict[index] | 1145 | mask = proj.mask_dict[index] |
1146 | - slc = self.Slice.do_threshold_to_all_slices(mask) | 1146 | + self.Slice.do_threshold_to_all_slices(mask) |
1147 | mask.create_3d_preview() | 1147 | mask.create_3d_preview() |
1148 | Publisher.sendMessage("Load mask preview", mask_3d_actor=mask.volume._actor, flag=flag) | 1148 | Publisher.sendMessage("Load mask preview", mask_3d_actor=mask.volume._actor, flag=flag) |
1149 | Publisher.sendMessage("Reload actual slice") | 1149 | Publisher.sendMessage("Reload actual slice") |
invesalius/data/bases.py
@@ -244,3 +244,47 @@ def object_registration(fiducials, orients, coord_raw, m_change): | @@ -244,3 +244,47 @@ def object_registration(fiducials, orients, coord_raw, m_change): | ||
244 | ) | 244 | ) |
245 | 245 | ||
246 | return t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img | 246 | 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
@@ -19,7 +19,6 @@ | @@ -19,7 +19,6 @@ | ||
19 | 19 | ||
20 | from math import sin, cos | 20 | from math import sin, cos |
21 | import numpy as np | 21 | import numpy as np |
22 | -import queue | ||
23 | import threading | 22 | import threading |
24 | import wx | 23 | import wx |
25 | 24 | ||
@@ -35,13 +34,28 @@ class TrackerCoordinates(): | @@ -35,13 +34,28 @@ class TrackerCoordinates(): | ||
35 | def __init__(self): | 34 | def __init__(self): |
36 | self.coord = None | 35 | self.coord = None |
37 | self.markers_flag = [False, False, False] | 36 | self.markers_flag = [False, False, False] |
37 | + self.previous_markers_flag = self.markers_flag | ||
38 | + self.nav_status = False | ||
39 | + self.__bind_events() | ||
40 | + | ||
41 | + def __bind_events(self): | ||
42 | + Publisher.subscribe(self.OnUpdateNavigationStatus, 'Navigation status') | ||
43 | + | ||
44 | + def OnUpdateNavigationStatus(self, nav_status, vis_status): | ||
45 | + self.nav_status = nav_status | ||
38 | 46 | ||
39 | def SetCoordinates(self, coord, markers_flag): | 47 | def SetCoordinates(self, coord, markers_flag): |
40 | self.coord = coord | 48 | self.coord = coord |
41 | self.markers_flag = markers_flag | 49 | self.markers_flag = markers_flag |
42 | - wx.CallAfter(Publisher.sendMessage, 'Sensors ID', markers_flag=self.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 | ||
43 | 53 | ||
44 | def GetCoordinates(self): | 54 | 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 | ||
58 | + | ||
45 | return self.coord, self.markers_flag | 59 | return self.coord, self.markers_flag |
46 | 60 | ||
47 | 61 | ||
@@ -65,15 +79,57 @@ def GetCoordinatesForThread(trck_init, trck_id, ref_mode): | @@ -65,15 +79,57 @@ def GetCoordinatesForThread(trck_init, trck_id, ref_mode): | ||
65 | const.POLARIS: PolarisCoord, | 79 | const.POLARIS: PolarisCoord, |
66 | const.POLARISP4: PolarisP4Coord, | 80 | const.POLARISP4: PolarisP4Coord, |
67 | const.OPTITRACK: OptitrackCoord, | 81 | const.OPTITRACK: OptitrackCoord, |
82 | + const.ROBOT: RobotCoord, | ||
68 | const.DEBUGTRACKRANDOM: DebugCoordRandom, | 83 | const.DEBUGTRACKRANDOM: DebugCoordRandom, |
69 | const.DEBUGTRACKAPPROACH: DebugCoordRandom} | 84 | const.DEBUGTRACKAPPROACH: DebugCoordRandom} |
70 | - | ||
71 | coord, markers_flag = getcoord[trck_id](trck_init, trck_id, ref_mode) | 85 | coord, markers_flag = getcoord[trck_id](trck_init, trck_id, ref_mode) |
72 | else: | 86 | else: |
73 | print("Select Tracker") | 87 | print("Select Tracker") |
74 | 88 | ||
75 | return coord, markers_flag | 89 | return coord, markers_flag |
76 | 90 | ||
91 | +def PolarisP4Coord(trck_init, trck_id, ref_mode): | ||
92 | + trck = trck_init[0] | ||
93 | + trck.Run() | ||
94 | + | ||
95 | + probe = trck.probe.decode(const.FS_ENCODE) | ||
96 | + ref = trck.ref.decode(const.FS_ENCODE) | ||
97 | + obj = trck.obj.decode(const.FS_ENCODE) | ||
98 | + | ||
99 | + probe = probe[2:] | ||
100 | + ref = ref[2:] | ||
101 | + obj = obj[2:] | ||
102 | + | ||
103 | + if probe[:7] == "MISSING": | ||
104 | + coord1 = np.hstack(([0, 0, 0], [0, 0, 0])) | ||
105 | + else: | ||
106 | + q = [int(probe[i:i + 6]) * 0.0001 for i in range(0, 24, 6)] | ||
107 | + t = [int(probe[i:i + 7]) * 0.01 for i in range(24, 45, 7)] | ||
108 | + angles_probe = np.degrees(tr.euler_from_quaternion(q, axes='rzyx')) | ||
109 | + trans_probe = np.array(t).astype(float) | ||
110 | + coord1 = np.hstack((trans_probe, angles_probe)) | ||
111 | + | ||
112 | + if ref[:7] == "MISSING": | ||
113 | + coord2 = np.hstack(([0, 0, 0], [0, 0, 0])) | ||
114 | + else: | ||
115 | + q = [int(ref[i:i + 6]) * 0.0001 for i in range(0, 24, 6)] | ||
116 | + t = [int(ref[i:i + 7]) * 0.01 for i in range(24, 45, 7)] | ||
117 | + angles_ref = np.degrees(tr.euler_from_quaternion(q, axes='rzyx')) | ||
118 | + trans_ref = np.array(t).astype(float) | ||
119 | + coord2 = np.hstack((trans_ref, angles_ref)) | ||
120 | + | ||
121 | + if obj[:7] == "MISSING": | ||
122 | + coord3 = np.hstack(([0, 0, 0], [0, 0, 0])) | ||
123 | + else: | ||
124 | + q = [int(obj[i:i + 6]) * 0.0001 for i in range(0, 24, 6)] | ||
125 | + t = [int(obj[i:i + 7]) * 0.01 for i in range(24, 45, 7)] | ||
126 | + angles_obj = np.degrees(tr.euler_from_quaternion(q, axes='rzyx')) | ||
127 | + trans_obj = np.array(t).astype(float) | ||
128 | + coord3 = np.hstack((trans_obj, angles_obj)) | ||
129 | + | ||
130 | + coord = np.vstack([coord1, coord2, coord3]) | ||
131 | + | ||
132 | + return coord, [trck.probeID, trck.refID, trck.objID] | ||
77 | 133 | ||
78 | def OptitrackCoord(trck_init, trck_id, ref_mode): | 134 | def OptitrackCoord(trck_init, trck_id, ref_mode): |
79 | """ | 135 | """ |
@@ -139,49 +195,16 @@ def PolarisCoord(trck_init, trck_id, ref_mode): | @@ -139,49 +195,16 @@ def PolarisCoord(trck_init, trck_id, ref_mode): | ||
139 | 195 | ||
140 | return coord, [trck.probeID, trck.refID, trck.objID] | 196 | return coord, [trck.probeID, trck.refID, trck.objID] |
141 | 197 | ||
142 | - | ||
143 | -def PolarisP4Coord(trck_init, trck_id, ref_mode): | ||
144 | - trck = trck_init[0] | ||
145 | - trck.Run() | ||
146 | - | ||
147 | - probe = trck.probe.decode(const.FS_ENCODE) | ||
148 | - ref = trck.ref.decode(const.FS_ENCODE) | ||
149 | - obj = trck.obj.decode(const.FS_ENCODE) | ||
150 | - | ||
151 | - probe = probe[2:] | ||
152 | - ref = ref[2:] | ||
153 | - obj = obj[2:] | ||
154 | - | ||
155 | - if probe[:7] == "MISSING": | ||
156 | - coord1 = np.hstack(([0, 0, 0], [0, 0, 0])) | 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]) | ||
157 | else: | 204 | else: |
158 | - q = [int(probe[i:i + 6]) * 0.0001 for i in range(0, 24, 6)] | ||
159 | - t = [int(probe[i:i + 7]) * 0.01 for i in range(24, 45, 7)] | ||
160 | - angles_probe = np.degrees(tr.euler_from_quaternion(q, axes='rzyx')) | ||
161 | - trans_probe = np.array(t).astype(float) | ||
162 | - coord1 = np.hstack((trans_probe, angles_probe)) | ||
163 | - if ref[:7] == "MISSING": | ||
164 | - coord2 = np.hstack(([0, 0, 0], [0, 0, 0])) | ||
165 | - else: | ||
166 | - q = [int(ref[i:i + 6]) * 0.0001 for i in range(0, 24, 6)] | ||
167 | - t = [int(ref[i:i + 7]) * 0.01 for i in range(24, 45, 7)] | ||
168 | - angles_ref = np.degrees(tr.euler_from_quaternion(q, axes='rzyx')) | ||
169 | - trans_ref = np.array(t).astype(float) | ||
170 | - coord2 = np.hstack((trans_ref, angles_ref)) | ||
171 | - | ||
172 | - if obj[:7] == "MISSING": | ||
173 | - coord3 = np.hstack(([0, 0, 0], [0, 0, 0])) | ||
174 | - else: | ||
175 | - q = [int(obj[i:i + 6]) * 0.0001 for i in range(0, 24, 6)] | ||
176 | - t = [int(obj[i:i + 7]) * 0.01 for i in range(24, 45, 7)] | ||
177 | - angles_obj = np.degrees(tr.euler_from_quaternion(q, axes='rzyx')) | ||
178 | - trans_obj = np.array(t).astype(float) | ||
179 | - coord3 = np.hstack((trans_obj, angles_obj)) | ||
180 | - | ||
181 | - coord = np.vstack([coord1, coord2, coord3]) | ||
182 | - | ||
183 | - return coord, [trck.probeID, trck.refID, trck.objID] | 205 | + coord = np.array([0, 0, 0, 0, 0, 0]) |
184 | 206 | ||
207 | + return coord | ||
185 | 208 | ||
186 | def CameraCoord(trck_init, trck_id, ref_mode): | 209 | def CameraCoord(trck_init, trck_id, ref_mode): |
187 | trck = trck_init[0] | 210 | trck = trck_init[0] |
@@ -223,11 +246,10 @@ def PolhemusCoord(trck, trck_id, ref_mode): | @@ -223,11 +246,10 @@ def PolhemusCoord(trck, trck_id, ref_mode): | ||
223 | elif trck[1] == 'wrapper': | 246 | elif trck[1] == 'wrapper': |
224 | coord = PolhemusWrapperCoord(trck[0], trck_id, ref_mode) | 247 | coord = PolhemusWrapperCoord(trck[0], trck_id, ref_mode) |
225 | 248 | ||
226 | - return coord, None | 249 | + return coord, [False, False, False] |
227 | 250 | ||
228 | 251 | ||
229 | def PolhemusWrapperCoord(trck, trck_id, ref_mode): | 252 | def PolhemusWrapperCoord(trck, trck_id, ref_mode): |
230 | - | ||
231 | trck.Run() | 253 | trck.Run() |
232 | scale = 10.0 * np.array([1., 1., 1.]) | 254 | scale = 10.0 * np.array([1., 1., 1.]) |
233 | 255 | ||
@@ -475,6 +497,22 @@ def dynamic_reference_m(probe, reference): | @@ -475,6 +497,22 @@ def dynamic_reference_m(probe, reference): | ||
475 | return coord_rot | 497 | return coord_rot |
476 | 498 | ||
477 | 499 | ||
500 | +def RobotCoord(trk_init, trck_id, ref_mode): | ||
501 | + coord_tracker, markers_flag = GetCoordinatesForThread(trk_init[0], trk_init[2], ref_mode) | ||
502 | + coord_robot = ElfinCoord(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 | + | ||
478 | def dynamic_reference_m2(probe, reference): | 516 | def dynamic_reference_m2(probe, reference): |
479 | """ | 517 | """ |
480 | Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama | 518 | Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama |
invesalius/data/coregistration.py
@@ -23,9 +23,9 @@ import threading | @@ -23,9 +23,9 @@ import threading | ||
23 | from time import sleep | 23 | from time import sleep |
24 | 24 | ||
25 | import invesalius.constants as const | 25 | import invesalius.constants as const |
26 | -import invesalius.data.coordinates as dco | ||
27 | import invesalius.data.transformations as tr | 26 | import invesalius.data.transformations as tr |
28 | import invesalius.data.bases as bases | 27 | import invesalius.data.bases as bases |
28 | +import invesalius.data.coordinates as dco | ||
29 | 29 | ||
30 | 30 | ||
31 | # TODO: Replace the use of degrees by radians in every part of the navigation pipeline | 31 | # TODO: Replace the use of degrees by radians in every part of the navigation pipeline |
@@ -88,9 +88,9 @@ def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | @@ -88,9 +88,9 @@ def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | ||
88 | :type m_probe_ref: numpy.ndarray | 88 | :type m_probe_ref: numpy.ndarray |
89 | :param r_obj_img: Object coordinate system in image space (3d model) | 89 | :param r_obj_img: Object coordinate system in image space (3d model) |
90 | :type r_obj_img: numpy.ndarray | 90 | :type r_obj_img: numpy.ndarray |
91 | - :param m_obj_raw: Object basis in raw coordinates from tacker | 91 | + :param m_obj_raw: Object basis in raw coordinates from tracker |
92 | :type m_obj_raw: numpy.ndarray | 92 | :type m_obj_raw: numpy.ndarray |
93 | - :param s0_dyn: | 93 | + :param s0_dyn: Initial alignment of probe fixed in the object in reference (or static) frame |
94 | :type s0_dyn: numpy.ndarray | 94 | :type s0_dyn: numpy.ndarray |
95 | :return: 4 x 4 numpy double array | 95 | :return: 4 x 4 numpy double array |
96 | :rtype: numpy.ndarray | 96 | :rtype: numpy.ndarray |
@@ -190,11 +190,14 @@ class CoordinateCorregistrate(threading.Thread): | @@ -190,11 +190,14 @@ class CoordinateCorregistrate(threading.Thread): | ||
190 | self.event = event | 190 | self.event = event |
191 | self.sle = sle | 191 | self.sle = sle |
192 | self.icp_queue = queues[2] | 192 | self.icp_queue = queues[2] |
193 | + self.object_at_target_queue = queues[3] | ||
193 | self.icp = None | 194 | self.icp = None |
194 | self.m_icp = None | 195 | self.m_icp = None |
196 | + self.robot_tracker_flag = None | ||
195 | self.last_coord = None | 197 | self.last_coord = None |
196 | self.tracker_id = tracker_id | 198 | self.tracker_id = tracker_id |
197 | self.target = target | 199 | self.target = target |
200 | + self.target_flag = False | ||
198 | 201 | ||
199 | if self.target is not None: | 202 | if self.target is not None: |
200 | self.target = np.array(self.target) | 203 | self.target = np.array(self.target) |
@@ -214,10 +217,12 @@ class CoordinateCorregistrate(threading.Thread): | @@ -214,10 +217,12 @@ class CoordinateCorregistrate(threading.Thread): | ||
214 | # print('CoordCoreg: event {}'.format(self.event.is_set())) | 217 | # print('CoordCoreg: event {}'.format(self.event.is_set())) |
215 | while not self.event.is_set(): | 218 | while not self.event.is_set(): |
216 | try: | 219 | try: |
217 | - if self.icp_queue.empty(): | ||
218 | - None | ||
219 | - else: | 220 | + if not self.icp_queue.empty(): |
220 | self.icp, self.m_icp = self.icp_queue.get_nowait() | 221 | self.icp, self.m_icp = self.icp_queue.get_nowait() |
222 | + | ||
223 | + if not self.object_at_target_queue.empty(): | ||
224 | + self.target_flag = self.object_at_target_queue.get_nowait() | ||
225 | + | ||
221 | # print(f"Set the coordinate") | 226 | # print(f"Set the coordinate") |
222 | coord_raw, markers_flag = self.tracker.TrackerCoordinates.GetCoordinates() | 227 | coord_raw, markers_flag = self.tracker.TrackerCoordinates.GetCoordinates() |
223 | coord, m_img = corregistrate_object_dynamic(coreg_data, coord_raw, self.ref_mode_id, [self.icp, self.m_icp]) | 228 | coord, m_img = corregistrate_object_dynamic(coreg_data, coord_raw, self.ref_mode_id, [self.icp, self.m_icp]) |
@@ -249,7 +254,7 @@ class CoordinateCorregistrate(threading.Thread): | @@ -249,7 +254,7 @@ class CoordinateCorregistrate(threading.Thread): | ||
249 | if self.icp: | 254 | if self.icp: |
250 | m_img = bases.transform_icp(m_img, self.m_icp) | 255 | m_img = bases.transform_icp(m_img, self.m_icp) |
251 | 256 | ||
252 | - self.coord_queue.put_nowait([coord, m_img, view_obj]) | 257 | + self.coord_queue.put_nowait([coord, [coord_raw, markers_flag], m_img, view_obj]) |
253 | # print('CoordCoreg: put {}'.format(count)) | 258 | # print('CoordCoreg: put {}'.format(count)) |
254 | # count += 1 | 259 | # count += 1 |
255 | 260 | ||
@@ -258,7 +263,6 @@ class CoordinateCorregistrate(threading.Thread): | @@ -258,7 +263,6 @@ class CoordinateCorregistrate(threading.Thread): | ||
258 | 263 | ||
259 | if not self.icp_queue.empty(): | 264 | if not self.icp_queue.empty(): |
260 | self.icp_queue.task_done() | 265 | self.icp_queue.task_done() |
261 | - | ||
262 | # The sleep has to be in both threads | 266 | # The sleep has to be in both threads |
263 | sleep(self.sle) | 267 | sleep(self.sle) |
264 | except queue.Full: | 268 | except queue.Full: |
@@ -303,7 +307,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | @@ -303,7 +307,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | ||
303 | if self.icp: | 307 | if self.icp: |
304 | m_img = bases.transform_icp(m_img, self.m_icp) | 308 | m_img = bases.transform_icp(m_img, self.m_icp) |
305 | 309 | ||
306 | - self.coord_queue.put_nowait([coord, m_img, view_obj]) | 310 | + self.coord_queue.put_nowait([coord, [coord_raw, markers_flag], m_img, view_obj]) |
307 | 311 | ||
308 | if self.view_tracts: | 312 | if self.view_tracts: |
309 | self.coord_tracts_queue.put_nowait(m_img_flip) | 313 | self.coord_tracts_queue.put_nowait(m_img_flip) |
@@ -0,0 +1,249 @@ | @@ -0,0 +1,249 @@ | ||
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, 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 != 1009: | ||
34 | + if type == const.ROBOT_MOTIONS["normal"] or type == const.ROBOT_MOTIONS["linear out"]: | ||
35 | + self.cobot.MoveL(target) | ||
36 | + elif type == const.ROBOT_MOTIONS["arc"]: | ||
37 | + self.cobot.MoveC(target) | ||
38 | + | ||
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 | ||
42 | + self.cobot.GrpStop() | ||
43 | + sleep(0.1) | ||
44 | + | ||
45 | + def Close(self): | ||
46 | + self.cobot.close() | ||
47 | + | ||
48 | +class Elfin: | ||
49 | + def __init__(self): | ||
50 | + """ | ||
51 | + Class to communicate with elfin robot. This class follows "HansRobot Communication Protocol Interface". | ||
52 | + """ | ||
53 | + self.end_msg = ",;" | ||
54 | + | ||
55 | + def connect(self, server_ip, port_number, message_size, robot_id): | ||
56 | + mySocket = socket(AF_INET, SOCK_STREAM) | ||
57 | + mySocket.connect((server_ip, port_number)) | ||
58 | + | ||
59 | + self.message_size = message_size | ||
60 | + self.robot_id = str(robot_id) | ||
61 | + self.mySocket = mySocket | ||
62 | + | ||
63 | + def send(self, message): | ||
64 | + self.mySocket.sendall(message.encode('utf-8')) | ||
65 | + data = self.mySocket.recv(self.message_size).decode('utf-8').split(',') | ||
66 | + status = self.check_status(data) | ||
67 | + if status and type(data) != bool: | ||
68 | + if len(data) > 3: | ||
69 | + return data[2:-1] | ||
70 | + return status | ||
71 | + | ||
72 | + def check_status(self, recv_message): | ||
73 | + status = recv_message[1] | ||
74 | + if status == 'OK': | ||
75 | + return True | ||
76 | + | ||
77 | + elif status == 'Fail': | ||
78 | + print("Error code: ", recv_message[2]) | ||
79 | + return False | ||
80 | + | ||
81 | + def Electrify(self): | ||
82 | + """ | ||
83 | + Function: Power the robot | ||
84 | + Notes: successful completion of power up before returning, power up time is | ||
85 | + about 44s. | ||
86 | + :return: | ||
87 | + if Error Return False | ||
88 | + if not Error Return True | ||
89 | + """ | ||
90 | + message = "Electrify" + self.end_msg | ||
91 | + status = self.send(message) | ||
92 | + return status | ||
93 | + | ||
94 | + def BlackOut(self): | ||
95 | + """ | ||
96 | + Function: Robot blackout | ||
97 | + Notes: successful power outage will only return, power failure time is 3s. | ||
98 | + :return: | ||
99 | + if Error Return False | ||
100 | + if not Error Return True | ||
101 | + """ | ||
102 | + message = "BlackOut" + self.end_msg | ||
103 | + status = self.send(message) | ||
104 | + return status | ||
105 | + | ||
106 | + def StartMaster(self): | ||
107 | + """ | ||
108 | + Function: Start master station | ||
109 | + Notes: the master station will not be returned until successfully started, startup | ||
110 | + master time is about 4s. | ||
111 | + :return: | ||
112 | + if Error Return False | ||
113 | + if not Error Return True | ||
114 | + """ | ||
115 | + message = "StartMaster" + self.end_msg | ||
116 | + status = self.send(message) | ||
117 | + return status | ||
118 | + | ||
119 | + def CloseMaster(self): | ||
120 | + """ | ||
121 | + Function: Close master station | ||
122 | + Notes: the master station will not be returned until successfully closed, shut | ||
123 | + down the master station time is about 2s. | ||
124 | + :return: | ||
125 | + if Error Return False | ||
126 | + if not Error Return True | ||
127 | + """ | ||
128 | + message = "CloseMaster" + self.end_msg | ||
129 | + status = self.send(message) | ||
130 | + return status | ||
131 | + | ||
132 | + def GrpPowerOn(self): | ||
133 | + """ | ||
134 | + Function: Robot servo on | ||
135 | + :return: | ||
136 | + if Error Return False | ||
137 | + if not Error Return True | ||
138 | + """ | ||
139 | + message = "GrpPowerOn," + self.robot_id + self.end_msg | ||
140 | + status = self.send(message) | ||
141 | + return status | ||
142 | + | ||
143 | + def GrpPowerOff(self): | ||
144 | + """ | ||
145 | + Function: Robot servo off | ||
146 | + :return: | ||
147 | + if Error Return False | ||
148 | + if not Error Return True | ||
149 | + """ | ||
150 | + message = "GrpPowerOff," + self.robot_id + self.end_msg | ||
151 | + status = self.send(message) | ||
152 | + return status | ||
153 | + | ||
154 | + def GrpStop(self): | ||
155 | + """ | ||
156 | + Function: Stop robot | ||
157 | + :return: | ||
158 | + if Error Return False | ||
159 | + if not Error Return True | ||
160 | + """ | ||
161 | + message = "GrpStop," + self.robot_id + self.end_msg | ||
162 | + status = self.send(message) | ||
163 | + return status | ||
164 | + | ||
165 | + def SetOverride(self, override): | ||
166 | + """ | ||
167 | + function: Set speed ratio | ||
168 | + :param override: | ||
169 | + double: set speed ratio, range of 0.01~1 | ||
170 | + :return: | ||
171 | + if Error Return False | ||
172 | + if not Error Return True | ||
173 | + """ | ||
174 | + | ||
175 | + message = "SetOverride," + self.robot_id + ',' + str(override) + self.end_msg | ||
176 | + status = self.send(message) | ||
177 | + return status | ||
178 | + | ||
179 | + def ReadPcsActualPos(self): | ||
180 | + """Function: Get the actual position of the space coordinate | ||
181 | + :return: | ||
182 | + if True Return x,y,z,a,b,c | ||
183 | + if Error Return False | ||
184 | + """ | ||
185 | + message = "ReadPcsActualPos," + self.robot_id + self.end_msg | ||
186 | + coord = self.send(message) | ||
187 | + if coord: | ||
188 | + return [float(s) for s in coord] | ||
189 | + | ||
190 | + return coord | ||
191 | + | ||
192 | + def MoveL(self, target): | ||
193 | + """ | ||
194 | + function: Robot moves straight to the specified space coordinates | ||
195 | + :param: target:[X,Y,Z,RX,RY,RZ] | ||
196 | + :return: | ||
197 | + """ | ||
198 | + target = [str(s) for s in target] | ||
199 | + target = (",".join(target)) | ||
200 | + message = "MoveL," + self.robot_id + ',' + target + self.end_msg | ||
201 | + return self.send(message) | ||
202 | + | ||
203 | + def SetToolCoordinateMotion(self, status): | ||
204 | + """ | ||
205 | + function: Function: Set tool coordinate motion | ||
206 | + :param: int Switch 0=close 1=open | ||
207 | + :return: | ||
208 | + if Error Return False | ||
209 | + if not Error Return True | ||
210 | + """ | ||
211 | + message = "SetToolCoordinateMotion," + self.robot_id + ',' + str(status) + self.end_msg | ||
212 | + status = self.send(message) | ||
213 | + return status | ||
214 | + | ||
215 | + def ReadMoveState(self): | ||
216 | + """ | ||
217 | + Function: Get the motion state of the robot | ||
218 | + :return: | ||
219 | + Current state of motion of robot: | ||
220 | + 0=motion completion; | ||
221 | + 1009=in motion; | ||
222 | + 1013=waiting for execution; | ||
223 | + 1025 =Error reporting | ||
224 | + """ | ||
225 | + message = "ReadMoveState," + self.robot_id + self.end_msg | ||
226 | + status = int(self.send(message)[0]) | ||
227 | + return status | ||
228 | + | ||
229 | + def MoveHoming(self): | ||
230 | + """ | ||
231 | + Function: Robot returns to the origin | ||
232 | + :return: | ||
233 | + if Error Return False | ||
234 | + if not Error Return True | ||
235 | + """ | ||
236 | + message = "MoveHoming," + self.robot_id + self.end_msg | ||
237 | + status = self.send(message) | ||
238 | + return status | ||
239 | + | ||
240 | + def MoveC(self, target): | ||
241 | + """ | ||
242 | + function: Arc motion | ||
243 | + :param: Through position[X,Y,Z],GoalCoord[X,Y,Z,RX,RY,RZ],Type[0 or 1],; | ||
244 | + :return: | ||
245 | + """ | ||
246 | + target = [str(s) for s in target] | ||
247 | + target = (",".join(target)) | ||
248 | + message = "MoveC," + self.robot_id + ',' + target + self.end_msg | ||
249 | + return self.send(message) |
@@ -0,0 +1,212 @@ | @@ -0,0 +1,212 @@ | ||
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/slice_.py
@@ -264,8 +264,6 @@ class Slice(metaclass=utils.Singleton): | @@ -264,8 +264,6 @@ class Slice(metaclass=utils.Singleton): | ||
264 | def OnRemoveMasks(self, mask_indexes): | 264 | def OnRemoveMasks(self, mask_indexes): |
265 | proj = Project() | 265 | proj = Project() |
266 | for item in mask_indexes: | 266 | for item in mask_indexes: |
267 | - proj.RemoveMask(item) | ||
268 | - | ||
269 | # if the deleted mask is the current mask, cleans the current mask | 267 | # if the deleted mask is the current mask, cleans the current mask |
270 | # and discard from buffer all datas related to mask. | 268 | # and discard from buffer all datas related to mask. |
271 | if self.current_mask is not None and item == self.current_mask.index: | 269 | if self.current_mask is not None and item == self.current_mask.index: |
@@ -277,6 +275,7 @@ class Slice(metaclass=utils.Singleton): | @@ -277,6 +275,7 @@ class Slice(metaclass=utils.Singleton): | ||
277 | 275 | ||
278 | Publisher.sendMessage("Show mask", index=item, value=False) | 276 | Publisher.sendMessage("Show mask", index=item, value=False) |
279 | Publisher.sendMessage("Reload actual slice") | 277 | Publisher.sendMessage("Reload actual slice") |
278 | + proj.RemoveMask(item) | ||
280 | 279 | ||
281 | def OnDuplicateMasks(self, mask_indexes): | 280 | def OnDuplicateMasks(self, mask_indexes): |
282 | proj = Project() | 281 | proj = Project() |
@@ -1185,6 +1184,7 @@ class Slice(metaclass=utils.Singleton): | @@ -1185,6 +1184,7 @@ class Slice(metaclass=utils.Singleton): | ||
1185 | future_mask = proj.GetMask(index) | 1184 | future_mask = proj.GetMask(index) |
1186 | future_mask.is_shown = True | 1185 | future_mask.is_shown = True |
1187 | self.current_mask = future_mask | 1186 | self.current_mask = future_mask |
1187 | + self.current_mask.on_show() | ||
1188 | 1188 | ||
1189 | colour = future_mask.colour | 1189 | colour = future_mask.colour |
1190 | self.SetMaskColour(index, colour, update=False) | 1190 | self.SetMaskColour(index, colour, update=False) |
invesalius/data/trackers.py
@@ -41,7 +41,8 @@ def TrackerConnection(tracker_id, trck_init, action): | @@ -41,7 +41,8 @@ def TrackerConnection(tracker_id, trck_init, action): | ||
41 | const.CAMERA: CameraTracker, # CAMERA | 41 | const.CAMERA: CameraTracker, # CAMERA |
42 | const.POLARIS: PolarisTracker, # POLARIS | 42 | const.POLARIS: PolarisTracker, # POLARIS |
43 | const.POLARISP4: PolarisP4Tracker, # POLARISP4 | 43 | const.POLARISP4: PolarisP4Tracker, # POLARISP4 |
44 | - const.OPTITRACK: OptitrackTracker, #Optitrack | 44 | + const.OPTITRACK: OptitrackTracker, #Optitrack |
45 | + const.ROBOT: RobotTracker, #Robot | ||
45 | const.DEBUGTRACKRANDOM: DebugTrackerRandom, | 46 | const.DEBUGTRACKRANDOM: DebugTrackerRandom, |
46 | const.DEBUGTRACKAPPROACH: DebugTrackerApproach} | 47 | const.DEBUGTRACKAPPROACH: DebugTrackerApproach} |
47 | 48 | ||
@@ -65,36 +66,68 @@ def DefaultTracker(tracker_id): | @@ -65,36 +66,68 @@ def DefaultTracker(tracker_id): | ||
65 | # return tracker initialization variable and type of connection | 66 | # return tracker initialization variable and type of connection |
66 | return trck_init, 'wrapper' | 67 | return trck_init, 'wrapper' |
67 | 68 | ||
68 | -def OptitrackTracker(tracker_id): | ||
69 | - """ | ||
70 | - Imports optitrack wrapper from Motive 2.2. Initialize cameras, attach listener, loads Calibration, loads User Profile | ||
71 | - (Rigid bodies information). | 69 | +def ClaronTracker(tracker_id): |
70 | + import invesalius.constants as const | ||
71 | + from invesalius import inv_paths | ||
72 | 72 | ||
73 | - Parameters | ||
74 | - ---------- | ||
75 | - tracker_id : Optitrack ID | 73 | + trck_init = None |
74 | + try: | ||
75 | + import pyclaron | ||
76 | 76 | ||
77 | - Returns | ||
78 | - ------- | ||
79 | - trck_init : local name for Optitrack module | ||
80 | - """ | ||
81 | - from wx import ID_OK | 77 | + lib_mode = 'wrapper' |
78 | + trck_init = pyclaron.pyclaron() | ||
79 | + trck_init.CalibrationDir = inv_paths.MTC_CAL_DIR.encode(const.FS_ENCODE) | ||
80 | + trck_init.MarkerDir = inv_paths.MTC_MAR_DIR.encode(const.FS_ENCODE) | ||
81 | + trck_init.NumberFramesProcessed = 1 | ||
82 | + trck_init.FramesExtrapolated = 0 | ||
83 | + trck_init.PROBE_NAME = const.MTC_PROBE_NAME.encode(const.FS_ENCODE) | ||
84 | + trck_init.REF_NAME = const.MTC_REF_NAME.encode(const.FS_ENCODE) | ||
85 | + trck_init.OBJ_NAME = const.MTC_OBJ_NAME.encode(const.FS_ENCODE) | ||
86 | + trck_init.Initialize() | ||
87 | + | ||
88 | + if trck_init.GetIdentifyingCamera(): | ||
89 | + trck_init.Run() | ||
90 | + print("MicronTracker camera identified.") | ||
91 | + else: | ||
92 | + trck_init = None | ||
93 | + | ||
94 | + except ImportError: | ||
95 | + lib_mode = 'error' | ||
96 | + print('The ClaronTracker library is not installed.') | ||
97 | + | ||
98 | + return trck_init, lib_mode | ||
99 | + | ||
100 | +def PolhemusTracker(tracker_id): | ||
101 | + try: | ||
102 | + trck_init = PlhWrapperConnection(tracker_id) | ||
103 | + lib_mode = 'wrapper' | ||
104 | + if not trck_init: | ||
105 | + print('Could not connect with Polhemus wrapper, trying USB connection...') | ||
106 | + trck_init = PlhUSBConnection(tracker_id) | ||
107 | + lib_mode = 'usb' | ||
108 | + if not trck_init: | ||
109 | + print('Could not connect with Polhemus USB, trying serial connection...') | ||
110 | + trck_init = PlhSerialConnection(tracker_id) | ||
111 | + lib_mode = 'serial' | ||
112 | + except: | ||
113 | + trck_init = None | ||
114 | + lib_mode = 'error' | ||
115 | + print('Could not connect to Polhemus by any method.') | ||
116 | + | ||
117 | + return trck_init, lib_mode | ||
118 | + | ||
119 | +def CameraTracker(tracker_id): | ||
82 | trck_init = None | 120 | trck_init = None |
83 | - dlg_port = dlg.SetOptitrackconfigs() | ||
84 | - if dlg_port.ShowModal() == ID_OK: | ||
85 | - Cal_optitrack, User_profile_optitrack = dlg_port.GetValue() | ||
86 | - try: | ||
87 | - import optitrack | ||
88 | - trck_init = optitrack.optr() | 121 | + try: |
122 | + import invesalius.data.camera_tracker as cam | ||
123 | + trck_init = cam.camera() | ||
124 | + trck_init.Initialize() | ||
125 | + print('Connect to camera tracking device.') | ||
89 | 126 | ||
90 | - if trck_init.Initialize(Cal_optitrack, User_profile_optitrack)==0: | ||
91 | - trck_init.Run() #Runs once Run function, to update cameras. | ||
92 | - else: | ||
93 | - trck_init = None | ||
94 | - except ImportError: | ||
95 | - print('Error') | ||
96 | - else: | ||
97 | - print('#####') | 127 | + except: |
128 | + print('Could not connect to camera tracker.') | ||
129 | + | ||
130 | + # return tracker initialization variable and type of connection | ||
98 | return trck_init, 'wrapper' | 131 | return trck_init, 'wrapper' |
99 | 132 | ||
100 | def PolarisTracker(tracker_id): | 133 | def PolarisTracker(tracker_id): |
@@ -111,22 +144,21 @@ def PolarisTracker(tracker_id): | @@ -111,22 +144,21 @@ def PolarisTracker(tracker_id): | ||
111 | if trck_init.Initialize(com_port, PROBE_DIR, REF_DIR, OBJ_DIR) != 0: | 144 | if trck_init.Initialize(com_port, PROBE_DIR, REF_DIR, OBJ_DIR) != 0: |
112 | trck_init = None | 145 | trck_init = None |
113 | lib_mode = None | 146 | lib_mode = None |
114 | - print('Could not connect to default tracker.') | 147 | + print('Could not connect to polaris tracker.') |
115 | else: | 148 | else: |
116 | print('Connect to polaris tracking device.') | 149 | print('Connect to polaris tracking device.') |
117 | 150 | ||
118 | except: | 151 | except: |
119 | lib_mode = 'error' | 152 | lib_mode = 'error' |
120 | trck_init = None | 153 | trck_init = None |
121 | - print('Could not connect to default tracker.') | 154 | + print('Could not connect to polaris tracker.') |
122 | else: | 155 | else: |
123 | lib_mode = None | 156 | lib_mode = None |
124 | - print('Could not connect to default tracker.') | 157 | + print('Could not connect to polaris tracker.') |
125 | 158 | ||
126 | # return tracker initialization variable and type of connection | 159 | # return tracker initialization variable and type of connection |
127 | return trck_init, lib_mode | 160 | return trck_init, lib_mode |
128 | 161 | ||
129 | - | ||
130 | def PolarisP4Tracker(tracker_id): | 162 | def PolarisP4Tracker(tracker_id): |
131 | from wx import ID_OK | 163 | from wx import ID_OK |
132 | trck_init = None | 164 | trck_init = None |
@@ -155,73 +187,74 @@ def PolarisP4Tracker(tracker_id): | @@ -155,73 +187,74 @@ def PolarisP4Tracker(tracker_id): | ||
155 | # return tracker initialization variable and type of connection | 187 | # return tracker initialization variable and type of connection |
156 | return trck_init, lib_mode | 188 | return trck_init, lib_mode |
157 | 189 | ||
190 | +def OptitrackTracker(tracker_id): | ||
191 | + """ | ||
192 | + Imports optitrack wrapper from Motive 2.2. Initialize cameras, attach listener, loads Calibration, loads User Profile | ||
193 | + (Rigid bodies information). | ||
194 | + | ||
195 | + Parameters | ||
196 | + ---------- | ||
197 | + tracker_id : Optitrack ID | ||
158 | 198 | ||
159 | -def CameraTracker(tracker_id): | 199 | + Returns |
200 | + ------- | ||
201 | + trck_init : local name for Optitrack module | ||
202 | + """ | ||
203 | + from wx import ID_OK | ||
160 | trck_init = None | 204 | trck_init = None |
161 | - try: | ||
162 | - import invesalius.data.camera_tracker as cam | ||
163 | - trck_init = cam.camera() | ||
164 | - trck_init.Initialize() | ||
165 | - print('Connect to camera tracking device.') | ||
166 | - | ||
167 | - except: | ||
168 | - print('Could not connect to default tracker.') | 205 | + dlg_port = dlg.SetOptitrackconfigs() |
206 | + if dlg_port.ShowModal() == ID_OK: | ||
207 | + Cal_optitrack, User_profile_optitrack = dlg_port.GetValue() | ||
208 | + try: | ||
209 | + import optitrack | ||
210 | + trck_init = optitrack.optr() | ||
169 | 211 | ||
170 | - # return tracker initialization variable and type of connection | 212 | + if trck_init.Initialize(Cal_optitrack, User_profile_optitrack)==0: |
213 | + trck_init.Run() #Runs once Run function, to update cameras. | ||
214 | + else: | ||
215 | + trck_init = None | ||
216 | + except ImportError: | ||
217 | + print('Error') | ||
218 | + else: | ||
219 | + print('#####') | ||
171 | return trck_init, 'wrapper' | 220 | return trck_init, 'wrapper' |
172 | 221 | ||
173 | - | ||
174 | -def ClaronTracker(tracker_id): | ||
175 | - import invesalius.constants as const | ||
176 | - from invesalius import inv_paths | ||
177 | - | 222 | +def ElfinRobot(robot_IP): |
178 | trck_init = None | 223 | trck_init = None |
179 | try: | 224 | try: |
180 | - import pyclaron | ||
181 | - | ||
182 | - lib_mode = 'wrapper' | ||
183 | - trck_init = pyclaron.pyclaron() | ||
184 | - trck_init.CalibrationDir = inv_paths.MTC_CAL_DIR.encode(const.FS_ENCODE) | ||
185 | - trck_init.MarkerDir = inv_paths.MTC_MAR_DIR.encode(const.FS_ENCODE) | ||
186 | - trck_init.NumberFramesProcessed = 1 | ||
187 | - trck_init.FramesExtrapolated = 0 | ||
188 | - trck_init.PROBE_NAME = const.MTC_PROBE_NAME.encode(const.FS_ENCODE) | ||
189 | - trck_init.REF_NAME = const.MTC_REF_NAME.encode(const.FS_ENCODE) | ||
190 | - trck_init.OBJ_NAME = const.MTC_OBJ_NAME.encode(const.FS_ENCODE) | 225 | + import invesalius.data.elfin as elfin |
226 | + print("Trying to connect Robot via: ", robot_IP) | ||
227 | + trck_init = elfin.Elfin_Server(robot_IP, const.ROBOT_ElFIN_PORT) | ||
191 | trck_init.Initialize() | 228 | trck_init.Initialize() |
192 | - | ||
193 | - if trck_init.GetIdentifyingCamera(): | ||
194 | - trck_init.Run() | ||
195 | - print("MicronTracker camera identified.") | ||
196 | - else: | ||
197 | - trck_init = None | ||
198 | - | ||
199 | - except ImportError: | ||
200 | - lib_mode = 'error' | ||
201 | - print('The ClaronTracker library is not installed.') | ||
202 | - | ||
203 | - return trck_init, lib_mode | ||
204 | - | ||
205 | - | ||
206 | -def PolhemusTracker(tracker_id): | ||
207 | - try: | ||
208 | - trck_init = PlhWrapperConnection(tracker_id) | ||
209 | lib_mode = 'wrapper' | 229 | lib_mode = 'wrapper' |
210 | - if not trck_init: | ||
211 | - print('Could not connect with Polhemus wrapper, trying USB connection...') | ||
212 | - trck_init = PlhUSBConnection(tracker_id) | ||
213 | - lib_mode = 'usb' | ||
214 | - if not trck_init: | ||
215 | - print('Could not connect with Polhemus USB, trying serial connection...') | ||
216 | - trck_init = PlhSerialConnection(tracker_id) | ||
217 | - lib_mode = 'serial' | 230 | + print('Connect to elfin robot tracking device.') |
231 | + | ||
218 | except: | 232 | except: |
233 | + lib_mode = 'disconnect' | ||
219 | trck_init = None | 234 | trck_init = None |
220 | - lib_mode = 'error' | ||
221 | - print('Could not connect to Polhemus by any method.') | 235 | + print('Could not connect to elfin robot tracker.') |
222 | 236 | ||
237 | + # return tracker initialization variable and type of connection | ||
223 | return trck_init, lib_mode | 238 | return trck_init, lib_mode |
224 | 239 | ||
240 | +def RobotTracker(tracker_id): | ||
241 | + from wx import ID_OK | ||
242 | + trck_init = None | ||
243 | + trck_init_robot = None | ||
244 | + tracker_id = None | ||
245 | + dlg_device = dlg.SetTrackerDeviceToRobot() | ||
246 | + if dlg_device.ShowModal() == ID_OK: | ||
247 | + tracker_id = dlg_device.GetValue() | ||
248 | + if tracker_id: | ||
249 | + trck_connection = TrackerConnection(tracker_id, None, 'connect') | ||
250 | + if trck_connection[0]: | ||
251 | + dlg_ip = dlg.SetRobotIP() | ||
252 | + if dlg_ip.ShowModal() == ID_OK: | ||
253 | + robot_IP = dlg_ip.GetValue() | ||
254 | + trck_init = trck_connection | ||
255 | + trck_init_robot = ElfinRobot(robot_IP) | ||
256 | + | ||
257 | + return [trck_init, trck_init_robot, tracker_id] | ||
225 | 258 | ||
226 | def DebugTrackerRandom(tracker_id): | 259 | def DebugTrackerRandom(tracker_id): |
227 | trck_init = True | 260 | trck_init = True |
@@ -294,10 +327,10 @@ def PlhSerialConnection(tracker_id): | @@ -294,10 +327,10 @@ def PlhSerialConnection(tracker_id): | ||
294 | except: | 327 | except: |
295 | lib_mode = 'error' | 328 | lib_mode = 'error' |
296 | trck_init = None | 329 | trck_init = None |
297 | - print('Could not connect to default tracker.') | 330 | + print('Could not connect to Polhemus tracker.') |
298 | else: | 331 | else: |
299 | lib_mode = None | 332 | lib_mode = None |
300 | - print('Could not connect to default tracker.') | 333 | + print('Could not connect to Polhemus tracker.') |
301 | 334 | ||
302 | return trck_init | 335 | return trck_init |
303 | 336 | ||
@@ -335,7 +368,6 @@ def PlhUSBConnection(tracker_id): | @@ -335,7 +368,6 @@ def PlhUSBConnection(tracker_id): | ||
335 | 368 | ||
336 | return trck_init | 369 | return trck_init |
337 | 370 | ||
338 | - | ||
339 | def DisconnectTracker(tracker_id, trck_init): | 371 | def DisconnectTracker(tracker_id, trck_init): |
340 | """ | 372 | """ |
341 | Disconnect current spatial tracker | 373 | Disconnect current spatial tracker |
@@ -355,6 +387,11 @@ def DisconnectTracker(tracker_id, trck_init): | @@ -355,6 +387,11 @@ def DisconnectTracker(tracker_id, trck_init): | ||
355 | trck_init = False | 387 | trck_init = False |
356 | lib_mode = 'serial' | 388 | lib_mode = 'serial' |
357 | print('Tracker disconnected.') | 389 | print('Tracker disconnected.') |
390 | + elif tracker_id == const.ROBOT: | ||
391 | + trck_init[0].Close() | ||
392 | + trck_init = False | ||
393 | + lib_mode = 'wrapper' | ||
394 | + print('Tracker disconnected.') | ||
358 | else: | 395 | else: |
359 | trck_init.Close() | 396 | trck_init.Close() |
360 | trck_init = False | 397 | trck_init = False |
invesalius/data/tractography.py
@@ -633,7 +633,7 @@ class ComputeTractsThreadSingleBlock(threading.Thread): | @@ -633,7 +633,7 @@ class ComputeTractsThreadSingleBlock(threading.Thread): | ||
633 | # print('ComputeTractsThread: event {}'.format(self.event.is_set())) | 633 | # print('ComputeTractsThread: event {}'.format(self.event.is_set())) |
634 | while not self.event.is_set(): | 634 | while not self.event.is_set(): |
635 | try: | 635 | try: |
636 | - coord, m_img, m_img_flip = self.coord_queue.get_nowait() | 636 | + coord, [coord_raw, markers_flag], m_img, m_img_flip = self.coord_queue.get_nowait() |
637 | 637 | ||
638 | # translate the coordinate along the normal vector of the object/coil | 638 | # translate the coordinate along the normal vector of the object/coil |
639 | coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] | 639 | coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] |
invesalius/data/viewer_volume.py
@@ -987,10 +987,10 @@ class Viewer(wx.Panel): | @@ -987,10 +987,10 @@ class Viewer(wx.Panel): | ||
987 | 987 | ||
988 | if thrdist and thrcoordx and thrcoordy and thrcoordz: | 988 | if thrdist and thrcoordx and thrcoordy and thrcoordz: |
989 | self.dummy_coil_actor.GetProperty().SetDiffuseColor(vtk_colors.GetColor3d('Green')) | 989 | self.dummy_coil_actor.GetProperty().SetDiffuseColor(vtk_colors.GetColor3d('Green')) |
990 | - Publisher.sendMessage('Coil at target', state=True) | 990 | + wx.CallAfter(Publisher.sendMessage, 'Coil at target', state=True) |
991 | else: | 991 | else: |
992 | self.dummy_coil_actor.GetProperty().SetDiffuseColor(vtk_colors.GetColor3d('DarkOrange')) | 992 | self.dummy_coil_actor.GetProperty().SetDiffuseColor(vtk_colors.GetColor3d('DarkOrange')) |
993 | - Publisher.sendMessage('Coil at target', state=False) | 993 | + wx.CallAfter(Publisher.sendMessage, 'Coil at target', state=False) |
994 | 994 | ||
995 | self.arrow_actor_list = arrow_roll_x1, arrow_roll_x2, arrow_yaw_z1, arrow_yaw_z2, \ | 995 | self.arrow_actor_list = arrow_roll_x1, arrow_roll_x2, arrow_yaw_z1, arrow_yaw_z2, \ |
996 | arrow_pitch_y1, arrow_pitch_y2 | 996 | arrow_pitch_y1, arrow_pitch_y2 |
invesalius/gui/dialogs.py
@@ -48,6 +48,7 @@ from wx.lib import masked | @@ -48,6 +48,7 @@ from wx.lib import masked | ||
48 | from wx.lib.agw import floatspin | 48 | from wx.lib.agw import floatspin |
49 | from wx.lib.wordwrap import wordwrap | 49 | from wx.lib.wordwrap import wordwrap |
50 | from invesalius.pubsub import pub as Publisher | 50 | from invesalius.pubsub import pub as Publisher |
51 | +import csv | ||
51 | 52 | ||
52 | try: | 53 | try: |
53 | from wx.adv import AboutDialogInfo, AboutBox | 54 | from wx.adv import AboutDialogInfo, AboutBox |
@@ -56,6 +57,7 @@ except ImportError: | @@ -56,6 +57,7 @@ except ImportError: | ||
56 | 57 | ||
57 | import invesalius.constants as const | 58 | import invesalius.constants as const |
58 | import invesalius.data.coordinates as dco | 59 | import invesalius.data.coordinates as dco |
60 | +import invesalius.data.transformations as tr | ||
59 | import invesalius.gui.widgets.gradient as grad | 61 | import invesalius.gui.widgets.gradient as grad |
60 | import invesalius.session as ses | 62 | import invesalius.session as ses |
61 | import invesalius.utils as utils | 63 | import invesalius.utils as utils |
@@ -873,6 +875,7 @@ def ShowNavigationTrackerWarning(trck_id, lib_mode): | @@ -873,6 +875,7 @@ def ShowNavigationTrackerWarning(trck_id, lib_mode): | ||
873 | const.POLARIS: 'NDI Polaris', | 875 | const.POLARIS: 'NDI Polaris', |
874 | const.POLARISP4: 'NDI Polaris P4', | 876 | const.POLARISP4: 'NDI Polaris P4', |
875 | const.OPTITRACK: 'Optitrack', | 877 | const.OPTITRACK: 'Optitrack', |
878 | + const.ROBOT: 'Robotic navigation', | ||
876 | const.DEBUGTRACKRANDOM: 'Debug tracker device (random)', | 879 | const.DEBUGTRACKRANDOM: 'Debug tracker device (random)', |
877 | const.DEBUGTRACKAPPROACH: 'Debug tracker device (approach)'} | 880 | const.DEBUGTRACKAPPROACH: 'Debug tracker device (approach)'} |
878 | 881 | ||
@@ -3309,7 +3312,6 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3309,7 +3312,6 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
3309 | self.pedal_connection = pedal_connection | 3312 | self.pedal_connection = pedal_connection |
3310 | 3313 | ||
3311 | self.trk_init, self.tracker_id = tracker.GetTrackerInfo() | 3314 | self.trk_init, self.tracker_id = tracker.GetTrackerInfo() |
3312 | - | ||
3313 | self.obj_ref_id = 2 | 3315 | self.obj_ref_id = 2 |
3314 | self.obj_name = None | 3316 | self.obj_name = None |
3315 | self.polydata = None | 3317 | self.polydata = None |
@@ -3594,8 +3596,14 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3594,8 +3596,14 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
3594 | # and not change the function to the point of potentially breaking it.) | 3596 | # and not change the function to the point of potentially breaking it.) |
3595 | # | 3597 | # |
3596 | if self.obj_ref_id and fiducial_index == 4: | 3598 | if self.obj_ref_id and fiducial_index == 4: |
3597 | - coord = coord_raw[self.obj_ref_id, :] | ||
3598 | - coord[2] = -coord[2] | 3599 | + if self.tracker_id == const.ROBOT: |
3600 | + trck_init_robot = self.trk_init[1][0] | ||
3601 | + coord = trck_init_robot.Run() | ||
3602 | + else: | ||
3603 | + coord = coord_raw[self.obj_ref_id, :] | ||
3604 | + else: | ||
3605 | + coord = coord_raw[0, :] | ||
3606 | + coord[2] = -coord[2] | ||
3599 | 3607 | ||
3600 | if fiducial_index == 3: | 3608 | if fiducial_index == 3: |
3601 | coord = np.zeros([6,]) | 3609 | coord = np.zeros([6,]) |
@@ -4301,6 +4309,312 @@ class SetOptitrackconfigs(wx.Dialog): | @@ -4301,6 +4309,312 @@ class SetOptitrackconfigs(wx.Dialog): | ||
4301 | 4309 | ||
4302 | return fn_cal, fn_userprofile | 4310 | return fn_cal, fn_userprofile |
4303 | 4311 | ||
4312 | +class SetTrackerDeviceToRobot(wx.Dialog): | ||
4313 | + """ | ||
4314 | + Robot navigation requires a tracker device to tracker the head position and the object (coil) position. | ||
4315 | + A dialog pops up showing a combobox with all trackers but debugs and the robot itself (const.TRACKERS[:-3]) | ||
4316 | + """ | ||
4317 | + def __init__(self, title=_("Setting tracker device:")): | ||
4318 | + wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, size=wx.Size(1000, 200), | ||
4319 | + style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP|wx.RESIZE_BORDER) | ||
4320 | + self.tracker_id = const.DEFAULT_TRACKER | ||
4321 | + self._init_gui() | ||
4322 | + | ||
4323 | + def _init_gui(self): | ||
4324 | + # ComboBox for spatial tracker device selection | ||
4325 | + tooltip = wx.ToolTip(_("Choose the tracking device")) | ||
4326 | + tracker_options = [_("Select tracker:")] + const.TRACKERS[:-3] | ||
4327 | + choice_trck = wx.ComboBox(self, -1, "", | ||
4328 | + choices=tracker_options, style=wx.CB_DROPDOWN | wx.CB_READONLY) | ||
4329 | + choice_trck.SetToolTip(tooltip) | ||
4330 | + choice_trck.SetSelection(const.DEFAULT_TRACKER) | ||
4331 | + choice_trck.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceTracker, ctrl=choice_trck)) | ||
4332 | + | ||
4333 | + btn_ok = wx.Button(self, wx.ID_OK) | ||
4334 | + btn_ok.SetHelpText("") | ||
4335 | + btn_ok.SetDefault() | ||
4336 | + | ||
4337 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | ||
4338 | + btn_cancel.SetHelpText("") | ||
4339 | + | ||
4340 | + btnsizer = wx.StdDialogButtonSizer() | ||
4341 | + btnsizer.AddButton(btn_ok) | ||
4342 | + btnsizer.AddButton(btn_cancel) | ||
4343 | + btnsizer.Realize() | ||
4344 | + | ||
4345 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
4346 | + | ||
4347 | + main_sizer.Add((5, 5)) | ||
4348 | + main_sizer.Add(choice_trck, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5) | ||
4349 | + main_sizer.Add((15, 15)) | ||
4350 | + main_sizer.Add(btnsizer, 0, wx.EXPAND) | ||
4351 | + main_sizer.Add((5, 5)) | ||
4352 | + | ||
4353 | + self.SetSizer(main_sizer) | ||
4354 | + main_sizer.Fit(self) | ||
4355 | + | ||
4356 | + self.CenterOnParent() | ||
4357 | + | ||
4358 | + def OnChoiceTracker(self, evt, ctrl): | ||
4359 | + choice = evt.GetSelection() | ||
4360 | + self.tracker_id = choice | ||
4361 | + | ||
4362 | + def GetValue(self): | ||
4363 | + return self.tracker_id | ||
4364 | + | ||
4365 | +class SetRobotIP(wx.Dialog): | ||
4366 | + def __init__(self, title=_("Setting Robot IP")): | ||
4367 | + wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, size=wx.Size(1000, 200), | ||
4368 | + style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP|wx.RESIZE_BORDER) | ||
4369 | + self.robot_ip = None | ||
4370 | + self._init_gui() | ||
4371 | + | ||
4372 | + def _init_gui(self): | ||
4373 | + # ComboBox for spatial tracker device selection | ||
4374 | + tooltip = wx.ToolTip(_("Choose or type the robot IP")) | ||
4375 | + robot_ip_options = [_("Select robot IP:")] + const.ROBOT_ElFIN_IP | ||
4376 | + choice_IP = wx.ComboBox(self, -1, "", | ||
4377 | + choices=robot_ip_options, style=wx.CB_DROPDOWN | wx.TE_PROCESS_ENTER) | ||
4378 | + choice_IP.SetToolTip(tooltip) | ||
4379 | + choice_IP.SetSelection(const.DEFAULT_TRACKER) | ||
4380 | + choice_IP.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceIP, ctrl=choice_IP)) | ||
4381 | + choice_IP.Bind(wx.EVT_TEXT, partial(self.OnTxt_Ent, ctrl=choice_IP)) | ||
4382 | + | ||
4383 | + btn_ok = wx.Button(self, wx.ID_OK) | ||
4384 | + btn_ok.SetHelpText("") | ||
4385 | + btn_ok.SetDefault() | ||
4386 | + | ||
4387 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | ||
4388 | + btn_cancel.SetHelpText("") | ||
4389 | + | ||
4390 | + btnsizer = wx.StdDialogButtonSizer() | ||
4391 | + btnsizer.AddButton(btn_ok) | ||
4392 | + btnsizer.AddButton(btn_cancel) | ||
4393 | + btnsizer.Realize() | ||
4394 | + | ||
4395 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
4396 | + | ||
4397 | + main_sizer.Add((5, 5)) | ||
4398 | + main_sizer.Add(choice_IP, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5) | ||
4399 | + main_sizer.Add((15, 15)) | ||
4400 | + main_sizer.Add(btnsizer, 0, wx.EXPAND) | ||
4401 | + main_sizer.Add((5, 5)) | ||
4402 | + | ||
4403 | + self.SetSizer(main_sizer) | ||
4404 | + main_sizer.Fit(self) | ||
4405 | + | ||
4406 | + self.CenterOnParent() | ||
4407 | + | ||
4408 | + def OnTxt_Ent(self, evt, ctrl): | ||
4409 | + self.robot_ip = str(ctrl.GetValue()) | ||
4410 | + | ||
4411 | + def OnChoiceIP(self, evt, ctrl): | ||
4412 | + self.robot_ip = ctrl.GetStringSelection() | ||
4413 | + | ||
4414 | + def GetValue(self): | ||
4415 | + return self.robot_ip | ||
4416 | + | ||
4417 | +class CreateTransformationMatrixRobot(wx.Dialog): | ||
4418 | + def __init__(self, tracker, title=_("Create transformation matrix to robot space")): | ||
4419 | + wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, #size=wx.Size(1000, 200), | ||
4420 | + style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP|wx.RESIZE_BORDER) | ||
4421 | + ''' | ||
4422 | + M_robot_2_tracker is created by an affine transformation. Robot TCP should be calibrated to the center of the tracker marker | ||
4423 | + ''' | ||
4424 | + #TODO: make aboutbox | ||
4425 | + self.tracker_coord = [] | ||
4426 | + self.tracker_angles = [] | ||
4427 | + self.robot_coord = [] | ||
4428 | + self.robot_angles = [] | ||
4429 | + | ||
4430 | + self.tracker = tracker | ||
4431 | + | ||
4432 | + self.timer = wx.Timer(self) | ||
4433 | + self.Bind(wx.EVT_TIMER, self.OnUpdate, self.timer) | ||
4434 | + | ||
4435 | + self._init_gui() | ||
4436 | + | ||
4437 | + def _init_gui(self): | ||
4438 | + # Buttons to acquire and remove points | ||
4439 | + txt_acquisition = wx.StaticText(self, -1, _('Poses acquisition for robot registration:')) | ||
4440 | + | ||
4441 | + btn_create_point = wx.Button(self, -1, label=_('Single')) | ||
4442 | + btn_create_point.Bind(wx.EVT_BUTTON, self.OnCreatePoint) | ||
4443 | + | ||
4444 | + btn_cont_point = wx.ToggleButton(self, -1, label=_('Continuous')) | ||
4445 | + btn_cont_point.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnContinuousAcquisition, btn=btn_cont_point)) | ||
4446 | + self.btn_cont_point = btn_cont_point | ||
4447 | + | ||
4448 | + txt_number = wx.StaticText(self, -1, _('0')) | ||
4449 | + txt_recorded = wx.StaticText(self, -1, _('Poses recorded')) | ||
4450 | + self.txt_number = txt_number | ||
4451 | + | ||
4452 | + btn_reset = wx.Button(self, -1, label=_('Reset')) | ||
4453 | + btn_reset.Bind(wx.EVT_BUTTON, self.OnReset) | ||
4454 | + | ||
4455 | + btn_apply_reg = wx.Button(self, -1, label=_('Apply')) | ||
4456 | + btn_apply_reg.Bind(wx.EVT_BUTTON, self.OnApply) | ||
4457 | + btn_apply_reg.Enable(False) | ||
4458 | + self.btn_apply_reg = btn_apply_reg | ||
4459 | + | ||
4460 | + # Buttons to save and load | ||
4461 | + txt_file = wx.StaticText(self, -1, _('Registration file')) | ||
4462 | + | ||
4463 | + btn_save = wx.Button(self, -1, label=_('Save'), size=wx.Size(65, 23)) | ||
4464 | + btn_save.Bind(wx.EVT_BUTTON, self.OnSaveReg) | ||
4465 | + btn_save.Enable(False) | ||
4466 | + self.btn_save = btn_save | ||
4467 | + | ||
4468 | + btn_load = wx.Button(self, -1, label=_('Load'), size=wx.Size(65, 23)) | ||
4469 | + btn_load.Bind(wx.EVT_BUTTON, self.OnLoadReg) | ||
4470 | + | ||
4471 | + # Create a horizontal sizers | ||
4472 | + border = 1 | ||
4473 | + acquisition = wx.BoxSizer(wx.HORIZONTAL) | ||
4474 | + acquisition.AddMany([(btn_create_point, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
4475 | + (btn_cont_point, 1, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
4476 | + | ||
4477 | + txt_pose = wx.BoxSizer(wx.HORIZONTAL) | ||
4478 | + txt_pose.AddMany([(txt_number, 1, wx.LEFT, 50), | ||
4479 | + (txt_recorded, 1, wx.LEFT, border)]) | ||
4480 | + | ||
4481 | + apply_reset = wx.BoxSizer(wx.HORIZONTAL) | ||
4482 | + apply_reset.AddMany([(btn_reset, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
4483 | + (btn_apply_reg, 1, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
4484 | + | ||
4485 | + save_load = wx.BoxSizer(wx.HORIZONTAL) | ||
4486 | + save_load.AddMany([(btn_save, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
4487 | + (btn_load, 1, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
4488 | + | ||
4489 | + btn_ok = wx.Button(self, wx.ID_OK) | ||
4490 | + btn_ok.SetHelpText("") | ||
4491 | + btn_ok.SetDefault() | ||
4492 | + btn_ok.Enable(False) | ||
4493 | + self.btn_ok = btn_ok | ||
4494 | + | ||
4495 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | ||
4496 | + btn_cancel.SetHelpText("") | ||
4497 | + | ||
4498 | + btnsizer = wx.StdDialogButtonSizer() | ||
4499 | + btnsizer.AddButton(btn_ok) | ||
4500 | + btnsizer.AddButton(btn_cancel) | ||
4501 | + btnsizer.Realize() | ||
4502 | + | ||
4503 | + # Add line sizers into main sizer | ||
4504 | + border = 10 | ||
4505 | + border_last = 10 | ||
4506 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
4507 | + main_sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, border) | ||
4508 | + main_sizer.Add(txt_acquisition, 0, wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_HORIZONTAL , border) | ||
4509 | + main_sizer.Add(acquisition, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border) | ||
4510 | + main_sizer.Add(txt_pose, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.BOTTOM, border) | ||
4511 | + main_sizer.Add(apply_reset, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT , border_last) | ||
4512 | + main_sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, border) | ||
4513 | + main_sizer.Add(txt_file, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border/2) | ||
4514 | + main_sizer.Add(save_load, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border) | ||
4515 | + main_sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, border) | ||
4516 | + main_sizer.Add(btnsizer, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border) | ||
4517 | + main_sizer.Fit(self) | ||
4518 | + | ||
4519 | + self.SetSizer(main_sizer) | ||
4520 | + self.Update() | ||
4521 | + main_sizer.Fit(self) | ||
4522 | + | ||
4523 | + self.CenterOnParent() | ||
4524 | + | ||
4525 | + def affine_correg(self, tracker, robot): | ||
4526 | + m_change = tr.affine_matrix_from_points(robot[:].T, tracker[:].T, | ||
4527 | + shear=False, scale=False, usesvd=False) | ||
4528 | + return m_change | ||
4529 | + | ||
4530 | + def OnContinuousAcquisition(self, evt=None, btn=None): | ||
4531 | + value = btn.GetValue() | ||
4532 | + if value: | ||
4533 | + self.timer.Start(500) | ||
4534 | + else: | ||
4535 | + self.timer.Stop() | ||
4536 | + | ||
4537 | + def OnUpdate(self, evt): | ||
4538 | + self.OnCreatePoint(evt=None) | ||
4539 | + | ||
4540 | + def OnCreatePoint(self, evt): | ||
4541 | + coord_raw, markers_flag = self.tracker.TrackerCoordinates.GetCoordinates() | ||
4542 | + #robot thread is not initialized yet | ||
4543 | + coord_raw_robot = self.tracker.trk_init[1][0].Run() | ||
4544 | + coord_raw_tracker_obj = coord_raw[3] | ||
4545 | + | ||
4546 | + if markers_flag[2]: | ||
4547 | + self.tracker_coord.append(coord_raw_tracker_obj[:3]) | ||
4548 | + self.tracker_angles.append(coord_raw_tracker_obj[3:]) | ||
4549 | + self.robot_coord.append(coord_raw_robot[:3]) | ||
4550 | + self.robot_angles.append(coord_raw_robot[3:]) | ||
4551 | + self.txt_number.SetLabel(str(int(self.txt_number.GetLabel())+1)) | ||
4552 | + else: | ||
4553 | + print('Cannot detect the coil markers, pls try again') | ||
4554 | + | ||
4555 | + if len(self.tracker_coord) >= 3: | ||
4556 | + self.btn_apply_reg.Enable(True) | ||
4557 | + | ||
4558 | + def OnReset(self, evt): | ||
4559 | + if self.btn_cont_point: | ||
4560 | + self.btn_cont_point.SetValue(False) | ||
4561 | + self.OnContinuousAcquisition(evt=None, btn=self.btn_cont_point) | ||
4562 | + | ||
4563 | + self.tracker_coord = [] | ||
4564 | + self.tracker_angles = [] | ||
4565 | + self.robot_coord = [] | ||
4566 | + self.robot_angles = [] | ||
4567 | + self.M_tracker_2_robot = [] | ||
4568 | + self.txt_number.SetLabel('0') | ||
4569 | + | ||
4570 | + self.btn_apply_reg.Enable(False) | ||
4571 | + self.btn_save.Enable(False) | ||
4572 | + self.btn_ok.Enable(False) | ||
4573 | + | ||
4574 | + def OnApply(self, evt): | ||
4575 | + if self.btn_cont_point: | ||
4576 | + self.btn_cont_point.SetValue(False) | ||
4577 | + self.OnContinuousAcquisition(evt=None, btn=self.btn_cont_point) | ||
4578 | + | ||
4579 | + tracker_coord = np.array(self.tracker_coord) | ||
4580 | + robot_coord = np.array(self.robot_coord) | ||
4581 | + | ||
4582 | + M_robot_2_tracker = self.affine_correg(tracker_coord, robot_coord) | ||
4583 | + M_tracker_2_robot = tr.inverse_matrix(M_robot_2_tracker) | ||
4584 | + self.M_tracker_2_robot = M_tracker_2_robot | ||
4585 | + | ||
4586 | + self.btn_save.Enable(True) | ||
4587 | + self.btn_ok.Enable(True) | ||
4588 | + | ||
4589 | + #TODO: make a colored circle to sinalize that the transformation was made (green) (red if not) | ||
4590 | + | ||
4591 | + def OnSaveReg(self, evt): | ||
4592 | + filename = ShowLoadSaveDialog(message=_(u"Save robot transformation file as..."), | ||
4593 | + wildcard=_("Robot transformation files (*.rbtf)|*.rbtf"), | ||
4594 | + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, | ||
4595 | + default_filename="robottransform.rbtf", save_ext="rbtf") | ||
4596 | + | ||
4597 | + if filename: | ||
4598 | + if self.M_tracker_2_robot is not None: | ||
4599 | + with open(filename, 'w', newline='') as file: | ||
4600 | + writer = csv.writer(file, delimiter='\t') | ||
4601 | + writer.writerows(self.M_tracker_2_robot) | ||
4602 | + | ||
4603 | + def OnLoadReg(self, evt): | ||
4604 | + filename = ShowLoadSaveDialog(message=_(u"Load robot transformation"), | ||
4605 | + wildcard=_("Robot transformation files (*.rbtf)|*.rbtf")) | ||
4606 | + if filename: | ||
4607 | + with open(filename, 'r') as file: | ||
4608 | + reader = csv.reader(file, delimiter='\t') | ||
4609 | + content = [row for row in reader] | ||
4610 | + | ||
4611 | + self.M_tracker_2_robot = np.vstack(list(np.float_(content))) | ||
4612 | + print("Matrix tracker to robot:", self.M_tracker_2_robot) | ||
4613 | + self.btn_ok.Enable(True) | ||
4614 | + | ||
4615 | + def GetValue(self): | ||
4616 | + return self.M_tracker_2_robot | ||
4617 | + | ||
4304 | class SetNDIconfigs(wx.Dialog): | 4618 | class SetNDIconfigs(wx.Dialog): |
4305 | def __init__(self, title=_("Setting NDI polaris configs:")): | 4619 | def __init__(self, title=_("Setting NDI polaris configs:")): |
4306 | wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, size=wx.Size(1000, 200), | 4620 | wx.Dialog.__init__(self, wx.GetApp().GetTopWindow(), -1, title, size=wx.Size(1000, 200), |
@@ -4329,16 +4643,19 @@ class SetNDIconfigs(wx.Dialog): | @@ -4329,16 +4643,19 @@ class SetNDIconfigs(wx.Dialog): | ||
4329 | return port_list, port_selec | 4643 | return port_list, port_selec |
4330 | 4644 | ||
4331 | def _init_gui(self): | 4645 | def _init_gui(self): |
4332 | - self.com_ports = wx.ComboBox(self, -1, style=wx.CB_DROPDOWN|wx.CB_READONLY) | 4646 | + com_ports = wx.ComboBox(self, -1, style=wx.CB_DROPDOWN|wx.CB_READONLY) |
4647 | + com_ports.Bind(wx.EVT_COMBOBOX, partial(self.OnChoicePort, ctrl=com_ports)) | ||
4333 | row_com = wx.BoxSizer(wx.VERTICAL) | 4648 | row_com = wx.BoxSizer(wx.VERTICAL) |
4334 | row_com.Add(wx.StaticText(self, wx.ID_ANY, "Select the COM port"), 0, wx.TOP|wx.RIGHT,5) | 4649 | row_com.Add(wx.StaticText(self, wx.ID_ANY, "Select the COM port"), 0, wx.TOP|wx.RIGHT,5) |
4335 | - row_com.Add(self.com_ports, 0, wx.EXPAND) | 4650 | + row_com.Add(com_ports, 0, wx.EXPAND) |
4336 | 4651 | ||
4337 | port_list, port_selec = self.serial_ports() | 4652 | port_list, port_selec = self.serial_ports() |
4338 | 4653 | ||
4339 | - self.com_ports.Append(port_list) | 4654 | + com_ports.Append(port_list) |
4340 | if port_selec: | 4655 | if port_selec: |
4341 | - self.com_ports.SetSelection(port_selec[0]) | 4656 | + com_ports.SetSelection(port_selec[0]) |
4657 | + | ||
4658 | + self.com_ports = com_ports | ||
4342 | 4659 | ||
4343 | session = ses.Session() | 4660 | session = ses.Session() |
4344 | last_ndi_probe_marker = session.get('paths', 'last_ndi_probe_marker', '') | 4661 | last_ndi_probe_marker = session.get('paths', 'last_ndi_probe_marker', '') |
@@ -4374,6 +4691,9 @@ class SetNDIconfigs(wx.Dialog): | @@ -4374,6 +4691,9 @@ class SetNDIconfigs(wx.Dialog): | ||
4374 | btn_ok = wx.Button(self, wx.ID_OK) | 4691 | btn_ok = wx.Button(self, wx.ID_OK) |
4375 | btn_ok.SetHelpText("") | 4692 | btn_ok.SetHelpText("") |
4376 | btn_ok.SetDefault() | 4693 | btn_ok.SetDefault() |
4694 | + if not port_selec: | ||
4695 | + btn_ok.Enable(False) | ||
4696 | + self.btn_ok = btn_ok | ||
4377 | 4697 | ||
4378 | btn_cancel = wx.Button(self, wx.ID_CANCEL) | 4698 | btn_cancel = wx.Button(self, wx.ID_CANCEL) |
4379 | btn_cancel.SetHelpText("") | 4699 | btn_cancel.SetHelpText("") |
@@ -4402,6 +4722,9 @@ class SetNDIconfigs(wx.Dialog): | @@ -4402,6 +4722,9 @@ class SetNDIconfigs(wx.Dialog): | ||
4402 | 4722 | ||
4403 | self.CenterOnParent() | 4723 | self.CenterOnParent() |
4404 | 4724 | ||
4725 | + def OnChoicePort(self, evt, ctrl): | ||
4726 | + self.btn_ok.Enable(True) | ||
4727 | + | ||
4405 | def GetValue(self): | 4728 | def GetValue(self): |
4406 | fn_probe = self.dir_probe.GetPath().encode(const.FS_ENCODE) | 4729 | fn_probe = self.dir_probe.GetPath().encode(const.FS_ENCODE) |
4407 | fn_ref = self.dir_ref.GetPath().encode(const.FS_ENCODE) | 4730 | fn_ref = self.dir_ref.GetPath().encode(const.FS_ENCODE) |
invesalius/gui/task_navigator.py
@@ -20,7 +20,10 @@ | @@ -20,7 +20,10 @@ | ||
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 | ||
23 | import time | 25 | import time |
26 | +import threading | ||
24 | 27 | ||
25 | import nibabel as nb | 28 | import nibabel as nb |
26 | import numpy as np | 29 | import numpy as np |
@@ -29,6 +32,13 @@ try: | @@ -29,6 +32,13 @@ try: | ||
29 | has_trekker = True | 32 | has_trekker = True |
30 | except ImportError: | 33 | except ImportError: |
31 | has_trekker = False | 34 | has_trekker = False |
35 | +try: | ||
36 | + import invesalius.data.elfin as elfin | ||
37 | + import invesalius.data.elfin_processing as elfin_process | ||
38 | + has_robot = True | ||
39 | +except ImportError: | ||
40 | + has_robot = False | ||
41 | + | ||
32 | import wx | 42 | import wx |
33 | 43 | ||
34 | try: | 44 | try: |
@@ -51,13 +61,17 @@ import invesalius.data.slice_ as sl | @@ -51,13 +61,17 @@ import invesalius.data.slice_ as sl | ||
51 | import invesalius.data.tractography as dti | 61 | import invesalius.data.tractography as dti |
52 | import invesalius.data.record_coords as rec | 62 | import invesalius.data.record_coords as rec |
53 | import invesalius.data.vtk_utils as vtk_utils | 63 | import invesalius.data.vtk_utils as vtk_utils |
64 | +import invesalius.data.bases as db | ||
54 | import invesalius.gui.dialogs as dlg | 65 | import invesalius.gui.dialogs as dlg |
55 | import invesalius.project as prj | 66 | import invesalius.project as prj |
67 | +import invesalius.session as ses | ||
68 | + | ||
56 | from invesalius import utils | 69 | from invesalius import utils |
57 | from invesalius.gui import utils as gui_utils | 70 | from invesalius.gui import utils as gui_utils |
58 | from invesalius.navigation.icp import ICP | 71 | from invesalius.navigation.icp import ICP |
59 | from invesalius.navigation.navigation import Navigation | 72 | from invesalius.navigation.navigation import Navigation |
60 | from invesalius.navigation.tracker import Tracker | 73 | from invesalius.navigation.tracker import Tracker |
74 | +from invesalius.navigation.robot import Robot | ||
61 | 75 | ||
62 | HAS_PEDAL_CONNECTION = True | 76 | HAS_PEDAL_CONNECTION = True |
63 | try: | 77 | try: |
@@ -183,7 +197,7 @@ class InnerFoldPanel(wx.Panel): | @@ -183,7 +197,7 @@ class InnerFoldPanel(wx.Panel): | ||
183 | 197 | ||
184 | # Fold 3 - Markers panel | 198 | # Fold 3 - Markers panel |
185 | item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) | 199 | item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) |
186 | - mtw = MarkersPanel(item) | 200 | + mtw = MarkersPanel(item, tracker) |
187 | 201 | ||
188 | fold_panel.ApplyCaptionStyle(item, style) | 202 | fold_panel.ApplyCaptionStyle(item, style) |
189 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, | 203 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, |
@@ -341,6 +355,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -341,6 +355,7 @@ class NeuronavigationPanel(wx.Panel): | ||
341 | ) | 355 | ) |
342 | self.icp = ICP() | 356 | self.icp = ICP() |
343 | self.tracker = tracker | 357 | self.tracker = tracker |
358 | + self.robot = Robot(tracker) | ||
344 | 359 | ||
345 | self.nav_status = False | 360 | self.nav_status = False |
346 | self.tracker_fiducial_being_set = None | 361 | self.tracker_fiducial_being_set = None |
@@ -640,6 +655,8 @@ class NeuronavigationPanel(wx.Panel): | @@ -640,6 +655,8 @@ class NeuronavigationPanel(wx.Panel): | ||
640 | self.checkbox_icp.SetValue(False) | 655 | self.checkbox_icp.SetValue(False) |
641 | 656 | ||
642 | def OnDisconnectTracker(self): | 657 | def OnDisconnectTracker(self): |
658 | + if self.tracker.tracker_id == const.ROBOT: | ||
659 | + self.robot.StopRobotThreadNavigation() | ||
643 | self.tracker.DisconnectTracker() | 660 | self.tracker.DisconnectTracker() |
644 | self.ResetICP() | 661 | self.ResetICP() |
645 | self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_fiducial[3:6], self.txtctrl_fre) | 662 | self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_fiducial[3:6], self.txtctrl_fre) |
@@ -657,6 +674,9 @@ class NeuronavigationPanel(wx.Panel): | @@ -657,6 +674,9 @@ class NeuronavigationPanel(wx.Panel): | ||
657 | choice = None | 674 | choice = None |
658 | 675 | ||
659 | self.tracker.SetTracker(choice) | 676 | self.tracker.SetTracker(choice) |
677 | + if self.tracker.tracker_id == const.ROBOT: | ||
678 | + self.tracker.ConnectToRobot(self.navigation, self.tracker, self.robot) | ||
679 | + | ||
660 | self.ResetICP() | 680 | self.ResetICP() |
661 | self.tracker.UpdateUI(ctrl, self.numctrls_fiducial[3:6], self.txtctrl_fre) | 681 | self.tracker.UpdateUI(ctrl, self.numctrls_fiducial[3:6], self.txtctrl_fre) |
662 | 682 | ||
@@ -739,6 +759,9 @@ class NeuronavigationPanel(wx.Panel): | @@ -739,6 +759,9 @@ class NeuronavigationPanel(wx.Panel): | ||
739 | choice_ref = self.choice_ref | 759 | choice_ref = self.choice_ref |
740 | 760 | ||
741 | self.navigation.StopNavigation() | 761 | self.navigation.StopNavigation() |
762 | + if self.tracker.tracker_id == const.ROBOT: | ||
763 | + Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False, | ||
764 | + m_change_robot_to_head=None) | ||
742 | 765 | ||
743 | # Enable all navigation buttons | 766 | # Enable all navigation buttons |
744 | choice_ref.Enable(True) | 767 | choice_ref.Enable(True) |
@@ -841,6 +864,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -841,6 +864,7 @@ class NeuronavigationPanel(wx.Panel): | ||
841 | ) | 864 | ) |
842 | self.tracker.__init__() | 865 | self.tracker.__init__() |
843 | self.icp.__init__() | 866 | self.icp.__init__() |
867 | + self.robot.__init__(self.tracker) | ||
844 | 868 | ||
845 | 869 | ||
846 | class ObjectRegistrationPanel(wx.Panel): | 870 | class ObjectRegistrationPanel(wx.Panel): |
@@ -899,16 +923,16 @@ class ObjectRegistrationPanel(wx.Panel): | @@ -899,16 +923,16 @@ class ObjectRegistrationPanel(wx.Panel): | ||
899 | 923 | ||
900 | # Change angles threshold | 924 | # Change angles threshold |
901 | text_angles = wx.StaticText(self, -1, _("Angle threshold [degrees]:")) | 925 | text_angles = wx.StaticText(self, -1, _("Angle threshold [degrees]:")) |
902 | - spin_size_angles = wx.SpinCtrl(self, -1, "", size=wx.Size(50, 23)) | ||
903 | - spin_size_angles.SetRange(1, 99) | 926 | + spin_size_angles = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23)) |
927 | + spin_size_angles.SetRange(0.1, 99) | ||
904 | spin_size_angles.SetValue(const.COIL_ANGLES_THRESHOLD) | 928 | spin_size_angles.SetValue(const.COIL_ANGLES_THRESHOLD) |
905 | spin_size_angles.Bind(wx.EVT_TEXT, partial(self.OnSelectAngleThreshold, ctrl=spin_size_angles)) | 929 | spin_size_angles.Bind(wx.EVT_TEXT, partial(self.OnSelectAngleThreshold, ctrl=spin_size_angles)) |
906 | spin_size_angles.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectAngleThreshold, ctrl=spin_size_angles)) | 930 | spin_size_angles.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectAngleThreshold, ctrl=spin_size_angles)) |
907 | 931 | ||
908 | # Change dist threshold | 932 | # Change dist threshold |
909 | text_dist = wx.StaticText(self, -1, _("Distance threshold [mm]:")) | 933 | text_dist = wx.StaticText(self, -1, _("Distance threshold [mm]:")) |
910 | - spin_size_dist = wx.SpinCtrl(self, -1, "", size=wx.Size(50, 23)) | ||
911 | - spin_size_dist.SetRange(1, 99) | 934 | + spin_size_dist = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23)) |
935 | + spin_size_dist.SetRange(0.1, 99) | ||
912 | spin_size_dist.SetValue(const.COIL_ANGLES_THRESHOLD) | 936 | spin_size_dist.SetValue(const.COIL_ANGLES_THRESHOLD) |
913 | spin_size_dist.Bind(wx.EVT_TEXT, partial(self.OnSelectDistThreshold, ctrl=spin_size_dist)) | 937 | spin_size_dist.Bind(wx.EVT_TEXT, partial(self.OnSelectDistThreshold, ctrl=spin_size_dist)) |
914 | spin_size_dist.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectDistThreshold, ctrl=spin_size_dist)) | 938 | spin_size_dist.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectDistThreshold, ctrl=spin_size_dist)) |
@@ -1200,7 +1224,20 @@ class MarkersPanel(wx.Panel): | @@ -1200,7 +1224,20 @@ class MarkersPanel(wx.Panel): | ||
1200 | if field.type is bool: | 1224 | if field.type is bool: |
1201 | setattr(self, field.name, str_val=='True') | 1225 | setattr(self, field.name, str_val=='True') |
1202 | 1226 | ||
1203 | - def __init__(self, parent): | 1227 | + @dataclasses.dataclass |
1228 | + class Robot_Marker: | ||
1229 | + """Class for storing robot target.""" | ||
1230 | + m_robot_target : list = None | ||
1231 | + | ||
1232 | + @property | ||
1233 | + def robot_target_matrix(self): | ||
1234 | + return self.m_robot_target | ||
1235 | + | ||
1236 | + @robot_target_matrix.setter | ||
1237 | + def robot_target_matrix(self, new_m_robot_target): | ||
1238 | + self.m_robot_target = new_m_robot_target | ||
1239 | + | ||
1240 | + def __init__(self, parent, tracker): | ||
1204 | wx.Panel.__init__(self, parent) | 1241 | wx.Panel.__init__(self, parent) |
1205 | try: | 1242 | try: |
1206 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) | 1243 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) |
@@ -1210,13 +1247,20 @@ class MarkersPanel(wx.Panel): | @@ -1210,13 +1247,20 @@ class MarkersPanel(wx.Panel): | ||
1210 | 1247 | ||
1211 | self.SetAutoLayout(1) | 1248 | self.SetAutoLayout(1) |
1212 | 1249 | ||
1250 | + self.tracker = tracker | ||
1251 | + | ||
1213 | self.__bind_events() | 1252 | self.__bind_events() |
1214 | 1253 | ||
1254 | + self.session = ses.Session() | ||
1255 | + | ||
1215 | self.current_coord = 0, 0, 0, 0, 0, 0 | 1256 | self.current_coord = 0, 0, 0, 0, 0, 0 |
1216 | self.current_angle = 0, 0, 0 | 1257 | self.current_angle = 0, 0, 0 |
1217 | self.current_seed = 0, 0, 0 | 1258 | self.current_seed = 0, 0, 0 |
1259 | + self.current_robot_target_matrix = [None] * 9 | ||
1218 | self.markers = [] | 1260 | self.markers = [] |
1261 | + self.robot_markers = [] | ||
1219 | self.nav_status = False | 1262 | self.nav_status = False |
1263 | + self.raw_target_robot = None, None | ||
1220 | 1264 | ||
1221 | self.marker_colour = const.MARKER_COLOUR | 1265 | self.marker_colour = const.MARKER_COLOUR |
1222 | self.marker_size = const.MARKER_SIZE | 1266 | self.marker_size = const.MARKER_SIZE |
@@ -1269,21 +1313,27 @@ class MarkersPanel(wx.Panel): | @@ -1269,21 +1313,27 @@ class MarkersPanel(wx.Panel): | ||
1269 | 1313 | ||
1270 | # List of markers | 1314 | # List of markers |
1271 | self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT, size=wx.Size(0,120)) | 1315 | self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT, size=wx.Size(0,120)) |
1272 | - self.lc.InsertColumn(0, '#') | ||
1273 | - self.lc.InsertColumn(1, 'X') | ||
1274 | - self.lc.InsertColumn(2, 'Y') | ||
1275 | - self.lc.InsertColumn(3, 'Z') | ||
1276 | - self.lc.InsertColumn(4, 'Label') | ||
1277 | - self.lc.InsertColumn(5, 'Target') | ||
1278 | - self.lc.InsertColumn(6, 'Session') | ||
1279 | - | ||
1280 | - self.lc.SetColumnWidth(0, 28) | ||
1281 | - self.lc.SetColumnWidth(1, 50) | ||
1282 | - self.lc.SetColumnWidth(2, 50) | ||
1283 | - self.lc.SetColumnWidth(3, 50) | ||
1284 | - self.lc.SetColumnWidth(4, 60) | ||
1285 | - self.lc.SetColumnWidth(5, 60) | ||
1286 | - self.lc.SetColumnWidth(5, 50) | 1316 | + self.lc.InsertColumn(const.ID_COLUMN, '#') |
1317 | + self.lc.SetColumnWidth(const.ID_COLUMN, 28) | ||
1318 | + | ||
1319 | + self.lc.InsertColumn(const.SESSION_COLUMN, 'Session') | ||
1320 | + self.lc.SetColumnWidth(const.SESSION_COLUMN, 52) | ||
1321 | + | ||
1322 | + self.lc.InsertColumn(const.LABEL_COLUMN, 'Label') | ||
1323 | + self.lc.SetColumnWidth(const.LABEL_COLUMN, 118) | ||
1324 | + | ||
1325 | + self.lc.InsertColumn(const.TARGET_COLUMN, 'Target') | ||
1326 | + self.lc.SetColumnWidth(const.TARGET_COLUMN, 45) | ||
1327 | + | ||
1328 | + if self.session.debug: | ||
1329 | + self.lc.InsertColumn(const.X_COLUMN, 'X') | ||
1330 | + self.lc.SetColumnWidth(const.X_COLUMN, 45) | ||
1331 | + | ||
1332 | + self.lc.InsertColumn(const.Y_COLUMN, 'Y') | ||
1333 | + self.lc.SetColumnWidth(const.Y_COLUMN, 45) | ||
1334 | + | ||
1335 | + self.lc.InsertColumn(const.Z_COLUMN, 'Z') | ||
1336 | + self.lc.SetColumnWidth(const.Z_COLUMN, 45) | ||
1287 | 1337 | ||
1288 | self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnMouseRightDown) | 1338 | self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnMouseRightDown) |
1289 | self.lc.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemBlink) | 1339 | self.lc.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemBlink) |
@@ -1301,7 +1351,6 @@ class MarkersPanel(wx.Panel): | @@ -1301,7 +1351,6 @@ class MarkersPanel(wx.Panel): | ||
1301 | self.Update() | 1351 | self.Update() |
1302 | 1352 | ||
1303 | def __bind_events(self): | 1353 | def __bind_events(self): |
1304 | - # Publisher.subscribe(self.UpdateCurrentCoord, 'Co-registered points') | ||
1305 | Publisher.subscribe(self.UpdateCurrentCoord, 'Set cross focal point') | 1354 | Publisher.subscribe(self.UpdateCurrentCoord, 'Set cross focal point') |
1306 | Publisher.subscribe(self.OnDeleteMultipleMarkers, 'Delete fiducial marker') | 1355 | Publisher.subscribe(self.OnDeleteMultipleMarkers, 'Delete fiducial marker') |
1307 | Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers') | 1356 | Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers') |
@@ -1309,15 +1358,18 @@ class MarkersPanel(wx.Panel): | @@ -1309,15 +1358,18 @@ class MarkersPanel(wx.Panel): | ||
1309 | Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') | 1358 | Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') |
1310 | Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts') | 1359 | Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts') |
1311 | Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed') | 1360 | Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed') |
1361 | + Publisher.subscribe(self.UpdateRobotCoordinates, 'Update raw coordinates') | ||
1312 | 1362 | ||
1313 | def __find_target_marker(self): | 1363 | def __find_target_marker(self): |
1314 | - """Return the index of the marker currently selected as target (there | ||
1315 | - should be at most one). If there is no such marker, return -1.""" | 1364 | + """ |
1365 | + Return the index of the marker currently selected as target (there | ||
1366 | + should be at most one). If there is no such marker, return None. | ||
1367 | + """ | ||
1316 | for i in range(len(self.markers)): | 1368 | for i in range(len(self.markers)): |
1317 | if self.markers[i].is_target: | 1369 | if self.markers[i].is_target: |
1318 | return i | 1370 | return i |
1319 | 1371 | ||
1320 | - return -1 | 1372 | + return None |
1321 | 1373 | ||
1322 | def __get_selected_items(self): | 1374 | def __get_selected_items(self): |
1323 | """ | 1375 | """ |
@@ -1334,18 +1386,22 @@ class MarkersPanel(wx.Panel): | @@ -1334,18 +1386,22 @@ class MarkersPanel(wx.Panel): | ||
1334 | return selection | 1386 | return selection |
1335 | 1387 | ||
1336 | def __delete_multiple_markers(self, index): | 1388 | def __delete_multiple_markers(self, index): |
1337 | - """ Delete multiple markers indexed by index. index must be sorted in | 1389 | + """ |
1390 | + Delete multiple markers indexed by index. index must be sorted in | ||
1338 | the ascending order. | 1391 | the ascending order. |
1339 | """ | 1392 | """ |
1340 | for i in reversed(index): | 1393 | for i in reversed(index): |
1341 | del self.markers[i] | 1394 | del self.markers[i] |
1395 | + del self.robot_markers[i] | ||
1342 | self.lc.DeleteItem(i) | 1396 | self.lc.DeleteItem(i) |
1343 | for n in range(0, self.lc.GetItemCount()): | 1397 | for n in range(0, self.lc.GetItemCount()): |
1344 | - self.lc.SetItem(n, 0, str(n+1)) | 1398 | + self.lc.SetItem(n, 0, str(n + 1)) |
1345 | Publisher.sendMessage('Remove multiple markers', index=index) | 1399 | Publisher.sendMessage('Remove multiple markers', index=index) |
1346 | 1400 | ||
1347 | def __set_marker_as_target(self, idx): | 1401 | def __set_marker_as_target(self, idx): |
1348 | - """Set marker indexed by idx as the new target. idx must be a valid index.""" | 1402 | + """ |
1403 | + Set marker indexed by idx as the new target. idx must be a valid index. | ||
1404 | + """ | ||
1349 | # Find the previous target | 1405 | # Find the previous target |
1350 | prev_idx = self.__find_target_marker() | 1406 | prev_idx = self.__find_target_marker() |
1351 | 1407 | ||
@@ -1354,16 +1410,16 @@ class MarkersPanel(wx.Panel): | @@ -1354,16 +1410,16 @@ class MarkersPanel(wx.Panel): | ||
1354 | return | 1410 | return |
1355 | 1411 | ||
1356 | # Unset the previous target | 1412 | # Unset the previous target |
1357 | - if prev_idx != -1: | 1413 | + if prev_idx is not None: |
1358 | self.markers[prev_idx].is_target = False | 1414 | self.markers[prev_idx].is_target = False |
1359 | self.lc.SetItemBackgroundColour(prev_idx, 'white') | 1415 | self.lc.SetItemBackgroundColour(prev_idx, 'white') |
1360 | Publisher.sendMessage('Set target transparency', status=False, index=prev_idx) | 1416 | Publisher.sendMessage('Set target transparency', status=False, index=prev_idx) |
1361 | - self.lc.SetItem(prev_idx, 5, "") | 1417 | + self.lc.SetItem(prev_idx, const.TARGET_COLUMN, "") |
1362 | 1418 | ||
1363 | # Set the new target | 1419 | # Set the new target |
1364 | self.markers[idx].is_target = True | 1420 | self.markers[idx].is_target = True |
1365 | self.lc.SetItemBackgroundColour(idx, 'RED') | 1421 | self.lc.SetItemBackgroundColour(idx, 'RED') |
1366 | - self.lc.SetItem(idx, 5, _("Yes")) | 1422 | + self.lc.SetItem(idx, const.TARGET_COLUMN, _("Yes")) |
1367 | 1423 | ||
1368 | Publisher.sendMessage('Update target', coord=self.markers[idx].coord) | 1424 | Publisher.sendMessage('Update target', coord=self.markers[idx].coord) |
1369 | Publisher.sendMessage('Set target transparency', status=True, index=idx) | 1425 | Publisher.sendMessage('Set target transparency', status=True, index=idx) |
@@ -1389,6 +1445,9 @@ class MarkersPanel(wx.Panel): | @@ -1389,6 +1445,9 @@ class MarkersPanel(wx.Panel): | ||
1389 | def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0)): | 1445 | def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0)): |
1390 | self.current_seed = coord_offset | 1446 | self.current_seed = coord_offset |
1391 | 1447 | ||
1448 | + def UpdateRobotCoordinates(self, coordinates_raw, markers_flag): | ||
1449 | + self.raw_target_robot = coordinates_raw[1], coordinates_raw[2] | ||
1450 | + | ||
1392 | def OnMouseRightDown(self, evt): | 1451 | def OnMouseRightDown(self, evt): |
1393 | # TODO: Enable the "Set as target" only when target is created with registered object | 1452 | # TODO: Enable the "Set as target" only when target is created with registered object |
1394 | menu_id = wx.Menu() | 1453 | menu_id = wx.Menu() |
@@ -1399,6 +1458,16 @@ class MarkersPanel(wx.Panel): | @@ -1399,6 +1458,16 @@ class MarkersPanel(wx.Panel): | ||
1399 | menu_id.AppendSeparator() | 1458 | menu_id.AppendSeparator() |
1400 | target_menu = menu_id.Append(1, _('Set as target')) | 1459 | target_menu = menu_id.Append(1, _('Set as target')) |
1401 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) | 1460 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) |
1461 | + menu_id.AppendSeparator() | ||
1462 | + send_target_to_robot = menu_id.Append(3, _('Send target to robot')) | ||
1463 | + menu_id.Bind(wx.EVT_MENU, self.OnMenuSendTargetToRobot, send_target_to_robot) | ||
1464 | + | ||
1465 | + # Enable "Send target to robot" button only if tracker is robot, if navigation is on and if target is not none | ||
1466 | + m_target_robot = np.array([self.robot_markers[self.lc.GetFocusedItem()].robot_target_matrix]) | ||
1467 | + if self.tracker.tracker_id == const.ROBOT and self.nav_status and m_target_robot.any(): | ||
1468 | + send_target_to_robot.Enable(True) | ||
1469 | + else: | ||
1470 | + send_target_to_robot.Enable(False) | ||
1402 | # TODO: Create the remove target option so the user can disable the target without removing the marker | 1471 | # TODO: Create the remove target option so the user can disable the target without removing the marker |
1403 | # target_menu_rem = menu_id.Append(3, _('Remove target')) | 1472 | # target_menu_rem = menu_id.Append(3, _('Remove target')) |
1404 | # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem) | 1473 | # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem) |
@@ -1416,9 +1485,9 @@ class MarkersPanel(wx.Panel): | @@ -1416,9 +1485,9 @@ class MarkersPanel(wx.Panel): | ||
1416 | def OnMenuEditMarkerLabel(self, evt): | 1485 | def OnMenuEditMarkerLabel(self, evt): |
1417 | list_index = self.lc.GetFocusedItem() | 1486 | list_index = self.lc.GetFocusedItem() |
1418 | if list_index != -1: | 1487 | if list_index != -1: |
1419 | - new_label = dlg.ShowEnterMarkerID(self.lc.GetItemText(list_index, 4)) | 1488 | + new_label = dlg.ShowEnterMarkerID(self.lc.GetItemText(list_index, const.LABEL_COLUMN)) |
1420 | self.markers[list_index].label = str(new_label) | 1489 | self.markers[list_index].label = str(new_label) |
1421 | - self.lc.SetItem(list_index, 4, new_label) | 1490 | + self.lc.SetItem(list_index, const.LABEL_COLUMN, new_label) |
1422 | else: | 1491 | else: |
1423 | wx.MessageBox(_("No data selected."), _("InVesalius 3")) | 1492 | wx.MessageBox(_("No data selected."), _("InVesalius 3")) |
1424 | 1493 | ||
@@ -1445,25 +1514,32 @@ class MarkersPanel(wx.Panel): | @@ -1445,25 +1514,32 @@ class MarkersPanel(wx.Panel): | ||
1445 | # XXX: Seems like a slightly too early point for rounding; better to round only when the value | 1514 | # XXX: Seems like a slightly too early point for rounding; better to round only when the value |
1446 | # is printed to the screen or file. | 1515 | # is printed to the screen or file. |
1447 | # | 1516 | # |
1448 | - self.markers[index].colour = [round(s/255.0, 3) for s in color_new] | 1517 | + self.markers[index].colour = [round(s / 255.0, 3) for s in color_new] |
1449 | 1518 | ||
1450 | Publisher.sendMessage('Set new color', index=index, color=color_new) | 1519 | Publisher.sendMessage('Set new color', index=index, color=color_new) |
1451 | 1520 | ||
1521 | + def OnMenuSendTargetToRobot(self, evt): | ||
1522 | + if isinstance(evt, int): | ||
1523 | + self.lc.Focus(evt) | ||
1524 | + | ||
1525 | + m_target_robot = self.robot_markers[self.lc.GetFocusedItem()].robot_target_matrix | ||
1526 | + | ||
1527 | + Publisher.sendMessage('Reset robot process') | ||
1528 | + Publisher.sendMessage('Robot target matrix', robot_tracker_flag=True, m_change_robot_to_head=m_target_robot) | ||
1529 | + | ||
1452 | def OnDeleteAllMarkers(self, evt=None): | 1530 | def OnDeleteAllMarkers(self, evt=None): |
1453 | - if evt is None: | ||
1454 | - result = wx.ID_OK | ||
1455 | - else: | 1531 | + if evt is not None: |
1456 | result = dlg.ShowConfirmationDialog(msg=_("Remove all markers? Cannot be undone.")) | 1532 | result = dlg.ShowConfirmationDialog(msg=_("Remove all markers? Cannot be undone.")) |
1533 | + if result != wx.ID_OK: | ||
1534 | + return | ||
1457 | 1535 | ||
1458 | - if result != wx.ID_OK: | ||
1459 | - return | ||
1460 | - | ||
1461 | - if self.__find_target_marker() != -1: | 1536 | + if self.__find_target_marker() is not None: |
1462 | Publisher.sendMessage('Disable or enable coil tracker', status=False) | 1537 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
1463 | if evt is not None: | 1538 | if evt is not None: |
1464 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) | 1539 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
1465 | 1540 | ||
1466 | self.markers = [] | 1541 | self.markers = [] |
1542 | + self.robot_markers = [] | ||
1467 | Publisher.sendMessage('Remove all markers', indexes=self.lc.GetItemCount()) | 1543 | Publisher.sendMessage('Remove all markers', indexes=self.lc.GetItemCount()) |
1468 | self.lc.DeleteAllItems() | 1544 | self.lc.DeleteAllItems() |
1469 | Publisher.sendMessage('Stop Blink Marker', index='DeleteAll') | 1545 | Publisher.sendMessage('Stop Blink Marker', index='DeleteAll') |
@@ -1472,22 +1548,27 @@ class MarkersPanel(wx.Panel): | @@ -1472,22 +1548,27 @@ class MarkersPanel(wx.Panel): | ||
1472 | # OnDeleteMultipleMarkers is used for both pubsub and button click events | 1548 | # OnDeleteMultipleMarkers is used for both pubsub and button click events |
1473 | # Pubsub is used for fiducial handle and button click for all others | 1549 | # Pubsub is used for fiducial handle and button click for all others |
1474 | 1550 | ||
1475 | - if not evt: # called through pubsub | 1551 | + # called through pubsub |
1552 | + if not evt: | ||
1476 | index = [] | 1553 | index = [] |
1477 | 1554 | ||
1478 | if label and (label in self.__list_fiducial_labels()): | 1555 | if label and (label in self.__list_fiducial_labels()): |
1479 | for id_n in range(self.lc.GetItemCount()): | 1556 | for id_n in range(self.lc.GetItemCount()): |
1480 | - item = self.lc.GetItem(id_n, 4) | 1557 | + item = self.lc.GetItem(id_n, const.LABEL_COLUMN) |
1481 | if item.GetText() == label: | 1558 | if item.GetText() == label: |
1482 | self.lc.Focus(item.GetId()) | 1559 | self.lc.Focus(item.GetId()) |
1483 | index = [self.lc.GetFocusedItem()] | 1560 | index = [self.lc.GetFocusedItem()] |
1484 | 1561 | ||
1485 | - else: # called from button click | 1562 | + # called from button click |
1563 | + else: | ||
1486 | index = self.__get_selected_items() | 1564 | index = self.__get_selected_items() |
1487 | 1565 | ||
1488 | if index: | 1566 | if index: |
1489 | if self.__find_target_marker() in index: | 1567 | if self.__find_target_marker() in index: |
1490 | Publisher.sendMessage('Disable or enable coil tracker', status=False) | 1568 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
1569 | + if self.tracker.tracker_id == const.ROBOT: | ||
1570 | + Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False, | ||
1571 | + m_change_robot_to_head=[]) | ||
1491 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) | 1572 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
1492 | 1573 | ||
1493 | self.__delete_multiple_markers(index) | 1574 | self.__delete_multiple_markers(index) |
@@ -1532,13 +1613,12 @@ class MarkersPanel(wx.Panel): | @@ -1532,13 +1613,12 @@ class MarkersPanel(wx.Panel): | ||
1532 | # If the new marker has is_target=True, we first create | 1613 | # If the new marker has is_target=True, we first create |
1533 | # a marker with is_target=False, and then call __set_marker_as_target | 1614 | # a marker with is_target=False, and then call __set_marker_as_target |
1534 | if marker.is_target: | 1615 | if marker.is_target: |
1535 | - self.__set_marker_as_target(len(self.markers)-1) | 1616 | + self.__set_marker_as_target(len(self.markers) - 1) |
1536 | 1617 | ||
1537 | except: | 1618 | except: |
1538 | wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) | 1619 | wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) |
1539 | 1620 | ||
1540 | def OnMarkersVisibility(self, evt, ctrl): | 1621 | def OnMarkersVisibility(self, evt, ctrl): |
1541 | - | ||
1542 | if ctrl.GetValue(): | 1622 | if ctrl.GetValue(): |
1543 | Publisher.sendMessage('Hide all markers', indexes=self.lc.GetItemCount()) | 1623 | Publisher.sendMessage('Hide all markers', indexes=self.lc.GetItemCount()) |
1544 | ctrl.SetLabel('Show') | 1624 | ctrl.SetLabel('Show') |
@@ -1573,8 +1653,8 @@ class MarkersPanel(wx.Panel): | @@ -1573,8 +1653,8 @@ class MarkersPanel(wx.Panel): | ||
1573 | wx.MessageBox(_("Error writing markers file."), _("InVesalius 3")) | 1653 | wx.MessageBox(_("Error writing markers file."), _("InVesalius 3")) |
1574 | 1654 | ||
1575 | def OnSelectColour(self, evt, ctrl): | 1655 | def OnSelectColour(self, evt, ctrl): |
1576 | - #TODO: Make sure GetValue returns 3 numbers (without alpha) | ||
1577 | - self.marker_colour = [colour/255.0 for colour in ctrl.GetValue()][:3] | 1656 | + # TODO: Make sure GetValue returns 3 numbers (without alpha) |
1657 | + self.marker_colour = [colour / 255.0 for colour in ctrl.GetValue()][:3] | ||
1578 | 1658 | ||
1579 | def OnSelectSize(self, evt, ctrl): | 1659 | def OnSelectSize(self, evt, ctrl): |
1580 | self.marker_size = ctrl.GetValue() | 1660 | self.marker_size = ctrl.GetValue() |
@@ -1592,6 +1672,14 @@ class MarkersPanel(wx.Panel): | @@ -1592,6 +1672,14 @@ class MarkersPanel(wx.Panel): | ||
1592 | new_marker.seed = seed or self.current_seed | 1672 | new_marker.seed = seed or self.current_seed |
1593 | new_marker.session_id = session_id or self.current_session | 1673 | new_marker.session_id = session_id or self.current_session |
1594 | 1674 | ||
1675 | + if self.tracker.tracker_id == const.ROBOT and self.nav_status: | ||
1676 | + self.current_robot_target_matrix = db.compute_robot_to_head_matrix(self.raw_target_robot) | ||
1677 | + else: | ||
1678 | + self.current_robot_target_matrix = [None] * 9 | ||
1679 | + | ||
1680 | + new_robot_marker = self.Robot_Marker() | ||
1681 | + new_robot_marker.robot_target_matrix = self.current_robot_target_matrix | ||
1682 | + | ||
1595 | # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added | 1683 | # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added |
1596 | Publisher.sendMessage('Add marker', ball_id=len(self.markers), | 1684 | Publisher.sendMessage('Add marker', ball_id=len(self.markers), |
1597 | size=new_marker.size, | 1685 | size=new_marker.size, |
@@ -1603,15 +1691,19 @@ class MarkersPanel(wx.Panel): | @@ -1603,15 +1691,19 @@ class MarkersPanel(wx.Panel): | ||
1603 | # coord=new_marker.coord[:3]) | 1691 | # coord=new_marker.coord[:3]) |
1604 | 1692 | ||
1605 | self.markers.append(new_marker) | 1693 | self.markers.append(new_marker) |
1694 | + self.robot_markers.append(new_robot_marker) | ||
1606 | 1695 | ||
1607 | # Add item to list control in panel | 1696 | # Add item to list control in panel |
1608 | num_items = self.lc.GetItemCount() | 1697 | num_items = self.lc.GetItemCount() |
1609 | self.lc.InsertItem(num_items, str(num_items + 1)) | 1698 | self.lc.InsertItem(num_items, str(num_items + 1)) |
1610 | - self.lc.SetItem(num_items, 1, str(round(new_marker.x, 2))) | ||
1611 | - self.lc.SetItem(num_items, 2, str(round(new_marker.y, 2))) | ||
1612 | - self.lc.SetItem(num_items, 3, str(round(new_marker.z, 2))) | ||
1613 | - self.lc.SetItem(num_items, 4, new_marker.label) | ||
1614 | - self.lc.SetItem(num_items, 6, str(new_marker.session_id)) | 1699 | + self.lc.SetItem(num_items, const.SESSION_COLUMN, str(new_marker.session_id)) |
1700 | + self.lc.SetItem(num_items, const.LABEL_COLUMN, new_marker.label) | ||
1701 | + | ||
1702 | + if self.session.debug: | ||
1703 | + self.lc.SetItem(num_items, const.X_COLUMN, str(round(new_marker.x, 1))) | ||
1704 | + self.lc.SetItem(num_items, const.Y_COLUMN, str(round(new_marker.y, 1))) | ||
1705 | + self.lc.SetItem(num_items, const.Z_COLUMN, str(round(new_marker.z, 1))) | ||
1706 | + | ||
1615 | self.lc.EnsureVisible(num_items) | 1707 | self.lc.EnsureVisible(num_items) |
1616 | 1708 | ||
1617 | class DbsPanel(wx.Panel): | 1709 | class DbsPanel(wx.Panel): |
invesalius/i18n.py
@@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
21 | 21 | ||
22 | try: | 22 | try: |
23 | import configparser as ConfigParser | 23 | import configparser as ConfigParser |
24 | -except(ImportError): | 24 | +except ImportError: |
25 | import ConfigParser | 25 | import ConfigParser |
26 | 26 | ||
27 | import locale | 27 | import locale |
@@ -66,22 +66,21 @@ def GetLocaleOS(): | @@ -66,22 +66,21 @@ def GetLocaleOS(): | ||
66 | def InstallLanguage(language): | 66 | def InstallLanguage(language): |
67 | file_path = os.path.split(__file__)[0] | 67 | file_path = os.path.split(__file__)[0] |
68 | 68 | ||
69 | - if hasattr(sys,"frozen") and (sys.frozen == "windows_exe"\ | ||
70 | - or sys.frozen == "console_exe"): | 69 | + abs_file_path = os.path.abspath(file_path + os.sep + "..") |
71 | 70 | ||
72 | - abs_file_path = os.path.abspath(file_path + os.sep + ".." + os.sep + ".." + os.sep + "..") | ||
73 | - language_dir = os.path.join(abs_file_path, 'locale') | ||
74 | - else: | ||
75 | - abs_file_path = os.path.abspath(file_path + os.sep + "..") | ||
76 | - language_dir = os.path.join(abs_file_path, 'locale') | 71 | + if hasattr(sys, "frozen") and (sys.frozen == "windows_exe" or sys.frozen == "console_exe"): |
72 | + abs_file_path = os.path.abspath(abs_file_path + os.sep + ".." + os.sep + "..") | ||
77 | 73 | ||
78 | - # MAC App | 74 | + language_dir = os.path.join(abs_file_path, 'locale') |
75 | + | ||
76 | + # MAC app | ||
79 | if not os.path.exists(language_dir): | 77 | if not os.path.exists(language_dir): |
80 | abs_file_path = os.path.abspath(os.path.join(file_path, '..', '..', '..', '..')) | 78 | abs_file_path = os.path.abspath(os.path.join(file_path, '..', '..', '..', '..')) |
81 | language_dir = os.path.join(abs_file_path, 'locale') | 79 | language_dir = os.path.join(abs_file_path, 'locale') |
82 | 80 | ||
83 | - lang = gettext.translation('invesalius', language_dir,\ | ||
84 | - languages=[language], codeset='utf8') | 81 | + lang = gettext.translation('invesalius', language_dir, |
82 | + languages=[language], codeset='utf8') | ||
83 | + | ||
85 | # Using unicode | 84 | # Using unicode |
86 | try: | 85 | try: |
87 | lang.install(unicode=1) | 86 | lang.install(unicode=1) |
invesalius/navigation/navigation.py
@@ -82,7 +82,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -82,7 +82,7 @@ class UpdateNavigationScene(threading.Thread): | ||
82 | 82 | ||
83 | threading.Thread.__init__(self, name='UpdateScene') | 83 | threading.Thread.__init__(self, name='UpdateScene') |
84 | self.serial_port_enabled, self.view_tracts, self.peel_loaded = vis_components | 84 | self.serial_port_enabled, self.view_tracts, self.peel_loaded = vis_components |
85 | - self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue = vis_queues | 85 | + self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue = vis_queues |
86 | self.sle = sle | 86 | self.sle = sle |
87 | self.event = event | 87 | self.event = event |
88 | 88 | ||
@@ -91,7 +91,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -91,7 +91,7 @@ class UpdateNavigationScene(threading.Thread): | ||
91 | while not self.event.is_set(): | 91 | while not self.event.is_set(): |
92 | got_coords = False | 92 | got_coords = False |
93 | try: | 93 | try: |
94 | - coord, m_img, view_obj = self.coord_queue.get_nowait() | 94 | + coord, [coordinates_raw, markers_flag], m_img, view_obj = self.coord_queue.get_nowait() |
95 | got_coords = True | 95 | got_coords = True |
96 | 96 | ||
97 | # print('UpdateScene: get {}'.format(count)) | 97 | # print('UpdateScene: get {}'.format(count)) |
@@ -116,6 +116,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -116,6 +116,7 @@ class UpdateNavigationScene(threading.Thread): | ||
116 | # see the red cross in the position of the offset marker | 116 | # see the red cross in the position of the offset marker |
117 | wx.CallAfter(Publisher.sendMessage, 'Update slices position', position=coord[:3]) | 117 | wx.CallAfter(Publisher.sendMessage, 'Update slices position', position=coord[:3]) |
118 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) | 118 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) |
119 | + wx.CallAfter(Publisher.sendMessage, 'Update raw coordinates', coordinates_raw=coordinates_raw, markers_flag=markers_flag) | ||
119 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') | 120 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') |
120 | 121 | ||
121 | if view_obj: | 122 | if view_obj: |
@@ -146,6 +147,8 @@ class Navigation(): | @@ -146,6 +147,8 @@ class Navigation(): | ||
146 | self.event = threading.Event() | 147 | self.event = threading.Event() |
147 | self.coord_queue = QueueCustom(maxsize=1) | 148 | self.coord_queue = QueueCustom(maxsize=1) |
148 | self.icp_queue = QueueCustom(maxsize=1) | 149 | self.icp_queue = QueueCustom(maxsize=1) |
150 | + self.object_at_target_queue = QueueCustom(maxsize=1) | ||
151 | + self.robot_target_queue = QueueCustom(maxsize=1) | ||
149 | # self.visualization_queue = QueueCustom(maxsize=1) | 152 | # self.visualization_queue = QueueCustom(maxsize=1) |
150 | self.serial_port_queue = QueueCustom(maxsize=1) | 153 | self.serial_port_queue = QueueCustom(maxsize=1) |
151 | self.coord_tracts_queue = QueueCustom(maxsize=1) | 154 | self.coord_tracts_queue = QueueCustom(maxsize=1) |
@@ -243,7 +246,7 @@ class Navigation(): | @@ -243,7 +246,7 @@ class Navigation(): | ||
243 | self.event.clear() | 246 | self.event.clear() |
244 | 247 | ||
245 | vis_components = [self.serial_port_in_use, self.view_tracts, self.peel_loaded] | 248 | vis_components = [self.serial_port_in_use, self.view_tracts, self.peel_loaded] |
246 | - vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue] | 249 | + vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue] |
247 | 250 | ||
248 | Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) | 251 | Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) |
249 | 252 | ||
@@ -277,7 +280,7 @@ class Navigation(): | @@ -277,7 +280,7 @@ class Navigation(): | ||
277 | obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | 280 | obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) |
278 | coreg_data.extend(obj_data) | 281 | coreg_data.extend(obj_data) |
279 | 282 | ||
280 | - queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue] | 283 | + queues = [self.coord_queue, self.coord_tracts_queue, self.icp_queue, self.object_at_target_queue] |
281 | jobs_list.append(dcr.CoordinateCorregistrate(self.ref_mode_id, tracker, coreg_data, | 284 | jobs_list.append(dcr.CoordinateCorregistrate(self.ref_mode_id, tracker, coreg_data, |
282 | self.view_tracts, queues, | 285 | self.view_tracts, queues, |
283 | self.event, self.sleep_nav, tracker.tracker_id, | 286 | self.event, self.sleep_nav, tracker.tracker_id, |
@@ -0,0 +1,286 @@ | @@ -0,0 +1,286 @@ | ||
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 | + | ||
24 | +import invesalius.data.bases as db | ||
25 | +import invesalius.gui.dialogs as dlg | ||
26 | +import invesalius.constants as const | ||
27 | +from invesalius.pubsub import pub as Publisher | ||
28 | + | ||
29 | +try: | ||
30 | + import invesalius.data.elfin as elfin | ||
31 | + import invesalius.data.elfin_processing as elfin_process | ||
32 | + has_robot = True | ||
33 | +except ImportError: | ||
34 | + has_robot = False | ||
35 | + | ||
36 | +class Robot(): | ||
37 | + def __init__(self, tracker): | ||
38 | + """ | ||
39 | + Class to establish the connection between the robot and InVesalius | ||
40 | + :param tracker: tracker.py instance | ||
41 | + """ | ||
42 | + self.tracker = tracker | ||
43 | + self.trk_init = None | ||
44 | + self.robot_target_queue = None | ||
45 | + self.object_at_target_queue = None | ||
46 | + self.process_tracker = None | ||
47 | + | ||
48 | + self.thread_robot = None | ||
49 | + | ||
50 | + self.robotcoordinates = RobotCoordinates() | ||
51 | + | ||
52 | + self.__bind_events() | ||
53 | + | ||
54 | + def __bind_events(self): | ||
55 | + Publisher.subscribe(self.OnSendCoordinates, 'Send coord to robot') | ||
56 | + Publisher.subscribe(self.OnUpdateRobotTargetMatrix, 'Robot target matrix') | ||
57 | + Publisher.subscribe(self.OnObjectTarget, 'Coil at target') | ||
58 | + Publisher.subscribe(self.OnResetProcessTracker, 'Reset robot process') | ||
59 | + | ||
60 | + def OnRobotConnection(self): | ||
61 | + if not self.tracker.trk_init[0][0] or not self.tracker.trk_init[1][0]: | ||
62 | + dlg.ShowNavigationTrackerWarning(self.tracker.tracker_id, self.tracker.trk_init[1]) | ||
63 | + self.tracker.tracker_id = 0 | ||
64 | + self.tracker.tracker_connected = False | ||
65 | + else: | ||
66 | + self.tracker.trk_init.append(self.robotcoordinates) | ||
67 | + self.process_tracker = elfin_process.TrackerProcessing() | ||
68 | + dlg_correg_robot = dlg.CreateTransformationMatrixRobot(self.tracker) | ||
69 | + if dlg_correg_robot.ShowModal() == wx.ID_OK: | ||
70 | + M_tracker_to_robot = dlg_correg_robot.GetValue() | ||
71 | + db.transform_tracker_to_robot.M_tracker_to_robot = M_tracker_to_robot | ||
72 | + self.robot_server = self.tracker.trk_init[1][0] | ||
73 | + self.trk_init = self.tracker.trk_init | ||
74 | + else: | ||
75 | + dlg.ShowNavigationTrackerWarning(self.tracker.tracker_id, 'disconnect') | ||
76 | + self.tracker.trk_init = None | ||
77 | + self.tracker.tracker_id = 0 | ||
78 | + self.tracker.tracker_connected = False | ||
79 | + | ||
80 | + def StartRobotThreadNavigation(self, tracker, coord_queue): | ||
81 | + if tracker.event_robot.is_set(): | ||
82 | + tracker.event_robot.clear() | ||
83 | + self.thread_robot = ControlRobot(self.trk_init, tracker, self.robotcoordinates, | ||
84 | + [coord_queue, self.robot_target_queue, self.object_at_target_queue], | ||
85 | + self.process_tracker, tracker.event_robot) | ||
86 | + self.thread_robot.start() | ||
87 | + | ||
88 | + def StopRobotThreadNavigation(self): | ||
89 | + self.thread_robot.join() | ||
90 | + self.OnResetProcessTracker() | ||
91 | + | ||
92 | + def OnResetProcessTracker(self): | ||
93 | + self.process_tracker.__init__() | ||
94 | + | ||
95 | + def OnSendCoordinates(self, coord): | ||
96 | + self.robot_server.SendCoordinates(coord) | ||
97 | + | ||
98 | + def OnUpdateRobotTargetMatrix(self, robot_tracker_flag, m_change_robot_to_head): | ||
99 | + try: | ||
100 | + self.robot_target_queue.put_nowait([robot_tracker_flag, m_change_robot_to_head]) | ||
101 | + except queue.Full: | ||
102 | + pass | ||
103 | + | ||
104 | + def OnObjectTarget(self, state): | ||
105 | + try: | ||
106 | + if self.object_at_target_queue: | ||
107 | + self.object_at_target_queue.put_nowait(state) | ||
108 | + except queue.Full: | ||
109 | + pass | ||
110 | + | ||
111 | + def SetRobotQueues(self, queues): | ||
112 | + self.robot_target_queue, self.object_at_target_queue = queues | ||
113 | + | ||
114 | + | ||
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 | + """ | ||
120 | + def __init__(self): | ||
121 | + self.coord = None | ||
122 | + | ||
123 | + def SetRobotCoordinates(self, coord): | ||
124 | + self.coord = coord | ||
125 | + | ||
126 | + def GetRobotCoordinates(self): | ||
127 | + return self.coord | ||
128 | + | ||
129 | + | ||
130 | +class ControlRobot(threading.Thread): | ||
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 | + """ | ||
145 | + threading.Thread.__init__(self, name='ControlRobot') | ||
146 | + | ||
147 | + self.trck_init_robot = trck_init[1][0] | ||
148 | + self.trck_init_tracker = trck_init[0] | ||
149 | + self.trk_id = trck_init[2] | ||
150 | + self.tracker = tracker | ||
151 | + self.robotcoordinates = robotcoordinates | ||
152 | + self.robot_tracker_flag = False | ||
153 | + self.objattarget_flag = False | ||
154 | + self.target_flag = False | ||
155 | + self.m_change_robot_to_head = None | ||
156 | + self.coord_inv_old = None | ||
157 | + self.coord_queue = queues[0] | ||
158 | + self.robot_target_queue = queues[1] | ||
159 | + self.object_at_target_queue = queues[2] | ||
160 | + self.process_tracker = process_tracker | ||
161 | + self.event_robot = event_robot | ||
162 | + self.arc_motion_flag = False | ||
163 | + self.arc_motion_step_flag = None | ||
164 | + self.target_linear_out = None | ||
165 | + self.target_linear_in = None | ||
166 | + self.target_arc = None | ||
167 | + | ||
168 | + def get_coordinates_from_tracker_devices(self): | ||
169 | + coord_robot_raw = self.trck_init_robot.Run() | ||
170 | + coord_robot = np.array(coord_robot_raw) | ||
171 | + coord_robot[3], coord_robot[5] = coord_robot[5], coord_robot[3] | ||
172 | + self.robotcoordinates.SetRobotCoordinates(coord_robot) | ||
173 | + | ||
174 | + coord_raw, markers_flag = self.tracker.TrackerCoordinates.GetCoordinates() | ||
175 | + | ||
176 | + return coord_raw, coord_robot_raw, markers_flag | ||
177 | + | ||
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 | + """ | ||
197 | + #Check if the target is inside the working space | ||
198 | + if self.process_tracker.estimate_robot_target_length(new_robot_coordinates) < const.ROBOT_WORKING_SPACE: | ||
199 | + #Check the target distance to define the motion mode | ||
200 | + if distance_target < const.ROBOT_ARC_THRESHOLD_DISTANCE and not self.arc_motion_flag: | ||
201 | + self.trck_init_robot.SendCoordinates(new_robot_coordinates, const.ROBOT_MOTIONS["normal"]) | ||
202 | + | ||
203 | + elif distance_target >= const.ROBOT_ARC_THRESHOLD_DISTANCE or self.arc_motion_flag: | ||
204 | + actual_point = current_robot_coordinates | ||
205 | + if not self.arc_motion_flag: | ||
206 | + head_center_coordinates = self.process_tracker.estimate_head_center(self.tracker, | ||
207 | + current_head_filtered).tolist() | ||
208 | + | ||
209 | + self.target_linear_out, self.target_arc = self.process_tracker.compute_arc_motion(current_robot_coordinates, head_center_coordinates, | ||
210 | + new_robot_coordinates) | ||
211 | + self.arc_motion_flag = True | ||
212 | + self.arc_motion_step_flag = const.ROBOT_MOTIONS["linear out"] | ||
213 | + | ||
214 | + if self.arc_motion_flag and self.arc_motion_step_flag == const.ROBOT_MOTIONS["linear out"]: | ||
215 | + coord = self.target_linear_out | ||
216 | + if np.allclose(np.array(actual_point), np.array(self.target_linear_out), 0, 1): | ||
217 | + self.arc_motion_step_flag = const.ROBOT_MOTIONS["arc"] | ||
218 | + coord = self.target_arc | ||
219 | + | ||
220 | + elif self.arc_motion_flag and self.arc_motion_step_flag == const.ROBOT_MOTIONS["arc"]: | ||
221 | + head_center_coordinates = self.process_tracker.estimate_head_center(self.tracker, | ||
222 | + current_head_filtered).tolist() | ||
223 | + | ||
224 | + _, new_target_arc = self.process_tracker.compute_arc_motion(current_robot_coordinates, head_center_coordinates, | ||
225 | + new_robot_coordinates) | ||
226 | + if np.allclose(np.array(new_target_arc[3:-1]), np.array(self.target_arc[3:-1]), 0, 1): | ||
227 | + None | ||
228 | + else: | ||
229 | + if self.process_tracker.correction_distance_calculation_target(new_robot_coordinates, current_robot_coordinates) >= \ | ||
230 | + const.ROBOT_ARC_THRESHOLD_DISTANCE*0.8: | ||
231 | + self.target_arc = new_target_arc | ||
232 | + | ||
233 | + coord = self.target_arc | ||
234 | + | ||
235 | + if np.allclose(np.array(actual_point), np.array(self.target_arc[3:-1]), 0, 10): | ||
236 | + self.arc_motion_flag = False | ||
237 | + self.arc_motion_step_flag = const.ROBOT_MOTIONS["normal"] | ||
238 | + coord = new_robot_coordinates | ||
239 | + | ||
240 | + self.trck_init_robot.SendCoordinates(coord, self.arc_motion_step_flag) | ||
241 | + else: | ||
242 | + print("Head is too far from the robot basis") | ||
243 | + | ||
244 | + def robot_control(self, current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag): | ||
245 | + coord_head_tracker_in_robot = current_tracker_coordinates_in_robot[1] | ||
246 | + marker_head_flag = markers_flag[1] | ||
247 | + coord_obj_tracker_in_robot = current_tracker_coordinates_in_robot[2] | ||
248 | + marker_obj_flag = markers_flag[2] | ||
249 | + | ||
250 | + if self.robot_tracker_flag: | ||
251 | + current_head = coord_head_tracker_in_robot | ||
252 | + if current_head is not None and marker_head_flag: | ||
253 | + current_head_filtered = self.process_tracker.kalman_filter(current_head) | ||
254 | + if self.process_tracker.compute_head_move_threshold(current_head_filtered): | ||
255 | + new_robot_coordinates = self.process_tracker.compute_head_move_compensation(current_head_filtered, | ||
256 | + self.m_change_robot_to_head) | ||
257 | + if self.coord_inv_old is None: | ||
258 | + self.coord_inv_old = new_robot_coordinates | ||
259 | + | ||
260 | + if np.allclose(np.array(new_robot_coordinates), np.array(current_robot_coordinates), 0, 0.01): | ||
261 | + #avoid small movements (0.01 mm) | ||
262 | + pass | ||
263 | + elif not np.allclose(np.array(new_robot_coordinates), np.array(self.coord_inv_old), 0, 5): | ||
264 | + #if the head moves (>5mm) before the robot reach the target | ||
265 | + self.trck_init_robot.StopRobot() | ||
266 | + self.coord_inv_old = new_robot_coordinates | ||
267 | + else: | ||
268 | + distance_target = self.process_tracker.correction_distance_calculation_target(new_robot_coordinates, current_robot_coordinates) | ||
269 | + self.robot_move_decision(distance_target, new_robot_coordinates, current_robot_coordinates, current_head_filtered) | ||
270 | + self.coord_inv_old = new_robot_coordinates | ||
271 | + else: | ||
272 | + self.trck_init_robot.StopRobot() | ||
273 | + | ||
274 | + def run(self): | ||
275 | + while not self.event_robot.is_set(): | ||
276 | + current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag = self.get_coordinates_from_tracker_devices() | ||
277 | + | ||
278 | + if not self.robot_target_queue.empty(): | ||
279 | + self.robot_tracker_flag, self.m_change_robot_to_head = self.robot_target_queue.get_nowait() | ||
280 | + self.robot_target_queue.task_done() | ||
281 | + | ||
282 | + if not self.object_at_target_queue.empty(): | ||
283 | + self.target_flag = self.object_at_target_queue.get_nowait() | ||
284 | + self.object_at_target_queue.task_done() | ||
285 | + | ||
286 | + self.robot_control(current_tracker_coordinates_in_robot, current_robot_coordinates, markers_flag) |
invesalius/navigation/tracker.py
@@ -24,6 +24,7 @@ import invesalius.constants as const | @@ -24,6 +24,7 @@ import invesalius.constants as const | ||
24 | import invesalius.data.coordinates as dco | 24 | import invesalius.data.coordinates as dco |
25 | import invesalius.data.trackers as dt | 25 | import invesalius.data.trackers as dt |
26 | import invesalius.gui.dialogs as dlg | 26 | import invesalius.gui.dialogs as dlg |
27 | +import invesalius.data.coregistration as dcr | ||
27 | from invesalius.pubsub import pub as Publisher | 28 | from invesalius.pubsub import pub as Publisher |
28 | 29 | ||
29 | 30 | ||
@@ -34,12 +35,14 @@ class Tracker(): | @@ -34,12 +35,14 @@ class Tracker(): | ||
34 | 35 | ||
35 | self.tracker_fiducials = np.full([3, 3], np.nan) | 36 | self.tracker_fiducials = np.full([3, 3], np.nan) |
36 | self.tracker_fiducials_raw = np.zeros((6, 6)) | 37 | self.tracker_fiducials_raw = np.zeros((6, 6)) |
38 | + self.m_tracker_fiducials_raw = np.zeros((6, 4, 4)) | ||
37 | 39 | ||
38 | self.tracker_connected = False | 40 | self.tracker_connected = False |
39 | 41 | ||
40 | self.thread_coord = None | 42 | self.thread_coord = None |
41 | 43 | ||
42 | self.event_coord = threading.Event() | 44 | self.event_coord = threading.Event() |
45 | + self.event_robot = threading.Event() | ||
43 | 46 | ||
44 | self.TrackerCoordinates = dco.TrackerCoordinates() | 47 | self.TrackerCoordinates = dco.TrackerCoordinates() |
45 | 48 | ||
@@ -74,8 +77,11 @@ class Tracker(): | @@ -74,8 +77,11 @@ class Tracker(): | ||
74 | 77 | ||
75 | if self.thread_coord: | 78 | if self.thread_coord: |
76 | self.event_coord.set() | 79 | self.event_coord.set() |
80 | + self.event_robot.set() | ||
77 | self.thread_coord.join() | 81 | self.thread_coord.join() |
78 | self.event_coord.clear() | 82 | self.event_coord.clear() |
83 | + self.event_robot.clear() | ||
84 | + | ||
79 | 85 | ||
80 | Publisher.sendMessage('Update status text in GUI', | 86 | Publisher.sendMessage('Update status text in GUI', |
81 | label=_("Tracker disconnected")) | 87 | label=_("Tracker disconnected")) |
@@ -85,6 +91,13 @@ class Tracker(): | @@ -85,6 +91,13 @@ class Tracker(): | ||
85 | label=_("Tracker still connected")) | 91 | label=_("Tracker still connected")) |
86 | print("Tracker still connected!") | 92 | print("Tracker still connected!") |
87 | 93 | ||
94 | + def ConnectToRobot(self, navigation, tracker, robot): | ||
95 | + robot.SetRobotQueues([navigation.robot_target_queue, navigation.object_at_target_queue]) | ||
96 | + robot.OnRobotConnection() | ||
97 | + trk_init_robot = self.trk_init[1][0] | ||
98 | + if trk_init_robot: | ||
99 | + robot.StartRobotThreadNavigation(tracker, navigation.coord_queue) | ||
100 | + | ||
88 | def IsTrackerInitialized(self): | 101 | def IsTrackerInitialized(self): |
89 | return self.trk_init and self.tracker_id and self.tracker_connected | 102 | return self.trk_init and self.tracker_id and self.tracker_connected |
90 | 103 | ||
@@ -126,6 +139,9 @@ class Tracker(): | @@ -126,6 +139,9 @@ class Tracker(): | ||
126 | self.tracker_fiducials_raw[2 * fiducial_index, :] = coord_raw[0, :] | 139 | self.tracker_fiducials_raw[2 * fiducial_index, :] = coord_raw[0, :] |
127 | self.tracker_fiducials_raw[2 * fiducial_index + 1, :] = coord_raw[1, :] | 140 | self.tracker_fiducials_raw[2 * fiducial_index + 1, :] = coord_raw[1, :] |
128 | 141 | ||
142 | + self.m_tracker_fiducials_raw[2 * fiducial_index, :] = dcr.compute_marker_transformation(coord_raw, 0) | ||
143 | + self.m_tracker_fiducials_raw[2 * fiducial_index + 1, :] = dcr.compute_marker_transformation(coord_raw, 1) | ||
144 | + | ||
129 | print("Set tracker fiducial {} to coordinates {}.".format(fiducial_index, coord[0:3])) | 145 | print("Set tracker fiducial {} to coordinates {}.".format(fiducial_index, coord[0:3])) |
130 | 146 | ||
131 | def ResetTrackerFiducials(self): | 147 | def ResetTrackerFiducials(self): |
@@ -135,6 +151,13 @@ class Tracker(): | @@ -135,6 +151,13 @@ class Tracker(): | ||
135 | def GetTrackerFiducials(self): | 151 | def GetTrackerFiducials(self): |
136 | return self.tracker_fiducials, self.tracker_fiducials_raw | 152 | return self.tracker_fiducials, self.tracker_fiducials_raw |
137 | 153 | ||
154 | + def GetMatrixTrackerFiducials(self): | ||
155 | + m_probe_ref_left = np.linalg.inv(self.m_tracker_fiducials_raw[1]) @ self.m_tracker_fiducials_raw[0] | ||
156 | + m_probe_ref_right = np.linalg.inv(self.m_tracker_fiducials_raw[3]) @ self.m_tracker_fiducials_raw[2] | ||
157 | + m_probe_ref_nasion = np.linalg.inv(self.m_tracker_fiducials_raw[5]) @ self.m_tracker_fiducials_raw[4] | ||
158 | + | ||
159 | + return [m_probe_ref_left, m_probe_ref_right, m_probe_ref_nasion] | ||
160 | + | ||
138 | def GetTrackerInfo(self): | 161 | def GetTrackerInfo(self): |
139 | return self.trk_init, self.tracker_id | 162 | return self.trk_init, self.tracker_id |
140 | 163 |
navigation/mtc_files/Markers/2Ref
1 | + | ||
1 | [Marker] | 2 | [Marker] |
2 | FacetsCount=1 | 3 | FacetsCount=1 |
3 | SliderControlledXpointsCount=0 | 4 | SliderControlledXpointsCount=0 |
@@ -7,20 +8,20 @@ Name=2Ref | @@ -7,20 +8,20 @@ Name=2Ref | ||
7 | VectorsCount=2 | 8 | VectorsCount=2 |
8 | 9 | ||
9 | [Facet1V1] | 10 | [Facet1V1] |
10 | -EndPos(0,0)=-8.9120676333490003 | ||
11 | -EndPos(0,1)=2.3980817331903383e-016 | ||
12 | -EndPos(0,2)=-1.2505552149377763e-015 | ||
13 | -EndPos(1,0)=8.9120676333490003 | ||
14 | -EndPos(1,1)=2.6645352591003756e-017 | ||
15 | -EndPos(1,2)=0. | 11 | +EndPos(0,0)=-32.862195626831138 |
12 | +EndPos(0,1)=2.2319079841241168e-003 | ||
13 | +EndPos(0,2)=7.6730702825049029e-003 | ||
14 | +EndPos(1,0)=32.860817892941867 | ||
15 | +EndPos(1,1)=1.862130420993991e-003 | ||
16 | +EndPos(1,2)=5.6832171433645459e-003 | ||
16 | 17 | ||
17 | [Facet1V2] | 18 | [Facet1V2] |
18 | -EndPos(0,0)=-8.9951734202602971 | ||
19 | -EndPos(0,1)=-12.045839883319603 | ||
20 | -EndPos(0,2)=-4.5474735088646413e-016 | ||
21 | -EndPos(1,0)=-8.9120676333490003 | ||
22 | -EndPos(1,1)=2.3980817331903383e-016 | ||
23 | -EndPos(1,2)=-1.2505552149377763e-015 | 19 | +EndPos(0,0)=-4.6454520609455985 |
20 | +EndPos(0,1)=24.559311056585091 | ||
21 | +EndPos(0,2)=-1.6538883706334069 | ||
22 | +EndPos(1,0)=-4.8071411059089124 | ||
23 | +EndPos(1,1)=-22.341630540181473 | ||
24 | +EndPos(1,2)=-1.654808075526687 | ||
24 | 25 | ||
25 | [Tooltip2MarkerXf] | 26 | [Tooltip2MarkerXf] |
26 | Scale=1. | 27 | Scale=1. |
navigation/mtc_files/Markers/3Coil_big
@@ -1,39 +0,0 @@ | @@ -1,39 +0,0 @@ | ||
1 | - | ||
2 | -[Marker] | ||
3 | -FacetsCount=1 | ||
4 | -SliderControlledXpointsCount=0 | ||
5 | -Name=3Coil_big | ||
6 | - | ||
7 | -[Facet1] | ||
8 | -VectorsCount=2 | ||
9 | - | ||
10 | -[Facet1V1] | ||
11 | -EndPos(0,0)=-12.130921665071337 | ||
12 | -EndPos(0,1)=4.771099944171531e-005 | ||
13 | -EndPos(0,2)=-1.6777868612989378e-016 | ||
14 | -EndPos(1,0)=12.131083622124752 | ||
15 | -EndPos(1,1)=1.0800131569805988e-004 | ||
16 | -EndPos(1,2)=-2.4831245547224282e-015 | ||
17 | - | ||
18 | -[Facet1V2] | ||
19 | -EndPos(0,0)=-12.143021882072084 | ||
20 | -EndPos(0,1)=-16.190735690907974 | ||
21 | -EndPos(0,2)=-1.3422294890391503e-015 | ||
22 | -EndPos(1,0)=-12.130921665071337 | ||
23 | -EndPos(1,1)=4.771099944171531e-005 | ||
24 | -EndPos(1,2)=-1.6777868612989378e-016 | ||
25 | - | ||
26 | -[Tooltip2MarkerXf] | ||
27 | -Scale=1. | ||
28 | -S0=0. | ||
29 | -R0,0=1. | ||
30 | -R1,0=0. | ||
31 | -R2,0=0. | ||
32 | -S1=0. | ||
33 | -R0,1=0. | ||
34 | -R1,1=1. | ||
35 | -R2,1=0. | ||
36 | -S2=0. | ||
37 | -R0,2=0. | ||
38 | -R1,2=0. | ||
39 | -R2,2=1. |
No preview for this file type
navigation/ndi_files/Markers/refP4.rom
No preview for this file type
optional-requirements.txt