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