Commit 3f9f34c4dff93b27fa01eafaee41688987d6042d
Committed by
GitHub
1 parent
a6843134
Exists in
master
ADD: NeuronavigationApi to send coil pose via connection object (#413)
Showing
4 changed files
with
100 additions
and
7 deletions
Show diff stats
app.py
| @@ -490,9 +490,20 @@ def print_events(topic=Publisher.AUTO_TOPIC, **msg_data): | @@ -490,9 +490,20 @@ def print_events(topic=Publisher.AUTO_TOPIC, **msg_data): | ||
| 490 | utils.debug("%s\n\tParameters: %s" % (topic, msg_data)) | 490 | utils.debug("%s\n\tParameters: %s" % (topic, msg_data)) |
| 491 | 491 | ||
| 492 | 492 | ||
| 493 | -def main(): | 493 | +def main(connection=None): |
| 494 | """ | 494 | """ |
| 495 | Initialize InVesalius GUI | 495 | Initialize InVesalius GUI |
| 496 | + | ||
| 497 | + Parameters: | ||
| 498 | + connection: An object to communicate with the outside world. | ||
| 499 | + In theory, can be any object supports certain function calls. | ||
| 500 | + See invesalius.net.neuronavigation_api for a comprehensive | ||
| 501 | + description of how the object is used. | ||
| 502 | + | ||
| 503 | + Note that if InVesalius is started in the usual way by running | ||
| 504 | + app.py, the connection object defaults to None. To enable this | ||
| 505 | + functionality, InVesalius needs to be started by calling the main | ||
| 506 | + function directly with a proper connection object. | ||
| 496 | """ | 507 | """ |
| 497 | options, args = parse_command_line() | 508 | options, args = parse_command_line() |
| 498 | 509 | ||
| @@ -513,6 +524,10 @@ def main(): | @@ -513,6 +524,10 @@ def main(): | ||
| 513 | 524 | ||
| 514 | PedalConnection().start() | 525 | PedalConnection().start() |
| 515 | 526 | ||
| 527 | + from invesalius.net.neuronavigation_api import NeuronavigationApi | ||
| 528 | + | ||
| 529 | + NeuronavigationApi(connection) | ||
| 530 | + | ||
| 516 | if options.no_gui: | 531 | if options.no_gui: |
| 517 | non_gui_startup(options, args) | 532 | non_gui_startup(options, args) |
| 518 | else: | 533 | else: |
invesalius/gui/task_navigator.py
| @@ -73,6 +73,8 @@ from invesalius.navigation.navigation import Navigation | @@ -73,6 +73,8 @@ from invesalius.navigation.navigation import Navigation | ||
| 73 | from invesalius.navigation.tracker import Tracker | 73 | from invesalius.navigation.tracker import Tracker |
| 74 | from invesalius.navigation.robot import Robot | 74 | from invesalius.navigation.robot import Robot |
| 75 | 75 | ||
| 76 | +from invesalius.net.neuronavigation_api import NeuronavigationApi | ||
| 77 | + | ||
| 76 | HAS_PEDAL_CONNECTION = True | 78 | HAS_PEDAL_CONNECTION = True |
| 77 | try: | 79 | try: |
| 78 | from invesalius.net.pedal_connection import PedalConnection | 80 | from invesalius.net.pedal_connection import PedalConnection |
| @@ -172,6 +174,8 @@ class InnerFoldPanel(wx.Panel): | @@ -172,6 +174,8 @@ class InnerFoldPanel(wx.Panel): | ||
| 172 | tracker = Tracker() | 174 | tracker = Tracker() |
| 173 | pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None | 175 | pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None |
| 174 | 176 | ||
| 177 | + neuronavigation_api = NeuronavigationApi() | ||
| 178 | + | ||
| 175 | # Fold panel style | 179 | # Fold panel style |
| 176 | style = fpb.CaptionBarStyle() | 180 | style = fpb.CaptionBarStyle() |
| 177 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) | 181 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) |
| @@ -180,7 +184,12 @@ class InnerFoldPanel(wx.Panel): | @@ -180,7 +184,12 @@ class InnerFoldPanel(wx.Panel): | ||
| 180 | 184 | ||
| 181 | # Fold 1 - Navigation panel | 185 | # Fold 1 - Navigation panel |
| 182 | item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True) | 186 | item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True) |
| 183 | - ntw = NeuronavigationPanel(item, tracker, pedal_connection) | 187 | + ntw = NeuronavigationPanel( |
| 188 | + parent=item, | ||
| 189 | + tracker=tracker, | ||
| 190 | + pedal_connection=pedal_connection, | ||
| 191 | + neuronavigation_api=neuronavigation_api, | ||
| 192 | + ) | ||
| 184 | 193 | ||
| 185 | fold_panel.ApplyCaptionStyle(item, style) | 194 | fold_panel.ApplyCaptionStyle(item, style) |
| 186 | fold_panel.AddFoldPanelWindow(item, ntw, spacing=0, | 195 | fold_panel.AddFoldPanelWindow(item, ntw, spacing=0, |
| @@ -336,7 +345,7 @@ class InnerFoldPanel(wx.Panel): | @@ -336,7 +345,7 @@ class InnerFoldPanel(wx.Panel): | ||
| 336 | 345 | ||
| 337 | 346 | ||
| 338 | class NeuronavigationPanel(wx.Panel): | 347 | class NeuronavigationPanel(wx.Panel): |
| 339 | - def __init__(self, parent, tracker, pedal_connection): | 348 | + def __init__(self, parent, tracker, pedal_connection, neuronavigation_api): |
| 340 | wx.Panel.__init__(self, parent) | 349 | wx.Panel.__init__(self, parent) |
| 341 | try: | 350 | try: |
| 342 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) | 351 | default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) |
| @@ -350,8 +359,10 @@ class NeuronavigationPanel(wx.Panel): | @@ -350,8 +359,10 @@ class NeuronavigationPanel(wx.Panel): | ||
| 350 | 359 | ||
| 351 | # Initialize global variables | 360 | # Initialize global variables |
| 352 | self.pedal_connection = pedal_connection | 361 | self.pedal_connection = pedal_connection |
| 362 | + | ||
| 353 | self.navigation = Navigation( | 363 | self.navigation = Navigation( |
| 354 | pedal_connection=pedal_connection, | 364 | pedal_connection=pedal_connection, |
| 365 | + neuronavigation_api=neuronavigation_api, | ||
| 355 | ) | 366 | ) |
| 356 | self.icp = ICP() | 367 | self.icp = ICP() |
| 357 | self.tracker = tracker | 368 | self.tracker = tracker |
invesalius/navigation/navigation.py
| @@ -64,7 +64,7 @@ class QueueCustom(queue.Queue): | @@ -64,7 +64,7 @@ class QueueCustom(queue.Queue): | ||
| 64 | 64 | ||
| 65 | class UpdateNavigationScene(threading.Thread): | 65 | class UpdateNavigationScene(threading.Thread): |
| 66 | 66 | ||
| 67 | - def __init__(self, vis_queues, vis_components, event, sle): | 67 | + def __init__(self, vis_queues, vis_components, event, sle, neuronavigation_api): |
| 68 | """Class (threading) to update the navigation scene with all graphical elements. | 68 | """Class (threading) to update the navigation scene with all graphical elements. |
| 69 | 69 | ||
| 70 | Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | 70 | Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation |
| @@ -77,6 +77,8 @@ class UpdateNavigationScene(threading.Thread): | @@ -77,6 +77,8 @@ class UpdateNavigationScene(threading.Thread): | ||
| 77 | :type event: threading.Event | 77 | :type event: threading.Event |
| 78 | :param sle: Sleep pause in seconds | 78 | :param sle: Sleep pause in seconds |
| 79 | :type sle: float | 79 | :type sle: float |
| 80 | + :param neuronavigation_api: An API object for communicating the coil position. | ||
| 81 | + :type neuronavigation_api: invesalius.net.neuronavigation_api.NeuronavigationAPI | ||
| 80 | """ | 82 | """ |
| 81 | 83 | ||
| 82 | threading.Thread.__init__(self, name='UpdateScene') | 84 | threading.Thread.__init__(self, name='UpdateScene') |
| @@ -84,6 +86,7 @@ class UpdateNavigationScene(threading.Thread): | @@ -84,6 +86,7 @@ class UpdateNavigationScene(threading.Thread): | ||
| 84 | self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue = vis_queues | 86 | self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue = vis_queues |
| 85 | self.sle = sle | 87 | self.sle = sle |
| 86 | self.event = event | 88 | self.event = event |
| 89 | + self.neuronavigation_api = neuronavigation_api | ||
| 87 | 90 | ||
| 88 | def run(self): | 91 | def run(self): |
| 89 | # count = 0 | 92 | # count = 0 |
| @@ -121,6 +124,12 @@ class UpdateNavigationScene(threading.Thread): | @@ -121,6 +124,12 @@ class UpdateNavigationScene(threading.Thread): | ||
| 121 | if view_obj: | 124 | if view_obj: |
| 122 | wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | 125 | wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) |
| 123 | wx.CallAfter(Publisher.sendMessage, 'Update object arrow matrix', m_img=m_img, coord=coord, flag= self.peel_loaded) | 126 | wx.CallAfter(Publisher.sendMessage, 'Update object arrow matrix', m_img=m_img, coord=coord, flag= self.peel_loaded) |
| 127 | + | ||
| 128 | + self.neuronavigation_api.update_coil_pose( | ||
| 129 | + position=coord[:3], | ||
| 130 | + orientation=coord[3:], | ||
| 131 | + ) | ||
| 132 | + | ||
| 124 | self.coord_queue.task_done() | 133 | self.coord_queue.task_done() |
| 125 | # print('UpdateScene: done {}'.format(count)) | 134 | # print('UpdateScene: done {}'.format(count)) |
| 126 | # count += 1 | 135 | # count += 1 |
| @@ -132,8 +141,9 @@ class UpdateNavigationScene(threading.Thread): | @@ -132,8 +141,9 @@ class UpdateNavigationScene(threading.Thread): | ||
| 132 | 141 | ||
| 133 | 142 | ||
| 134 | class Navigation(): | 143 | class Navigation(): |
| 135 | - def __init__(self, pedal_connection): | 144 | + def __init__(self, pedal_connection, neuronavigation_api): |
| 136 | self.pedal_connection = pedal_connection | 145 | self.pedal_connection = pedal_connection |
| 146 | + self.neuronavigation_api = neuronavigation_api | ||
| 137 | 147 | ||
| 138 | self.image_fiducials = np.full([3, 3], np.nan) | 148 | self.image_fiducials = np.full([3, 3], np.nan) |
| 139 | self.correg = None | 149 | self.correg = None |
| @@ -329,8 +339,15 @@ class Navigation(): | @@ -329,8 +339,15 @@ class Navigation(): | ||
| 329 | else: | 339 | else: |
| 330 | jobs_list.append(dti.ComputeTractsThread(self.trk_inp, queues, self.event, self.sleep_nav)) | 340 | jobs_list.append(dti.ComputeTractsThread(self.trk_inp, queues, self.event, self.sleep_nav)) |
| 331 | 341 | ||
| 332 | - jobs_list.append(UpdateNavigationScene(vis_queues, vis_components, | ||
| 333 | - self.event, self.sleep_nav)) | 342 | + jobs_list.append( |
| 343 | + UpdateNavigationScene( | ||
| 344 | + vis_queues=vis_queues, | ||
| 345 | + vis_components=vis_components, | ||
| 346 | + event=self.event, | ||
| 347 | + sle=self.sleep_nav, | ||
| 348 | + neuronavigation_api=self.neuronavigation_api, | ||
| 349 | + ) | ||
| 350 | + ) | ||
| 334 | 351 | ||
| 335 | for jobs in jobs_list: | 352 | for jobs in jobs_list: |
| 336 | # jobs.daemon = True | 353 | # jobs.daemon = True |
| @@ -0,0 +1,50 @@ | @@ -0,0 +1,50 @@ | ||
| 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 | + | ||
| 20 | +from invesalius.utils import Singleton | ||
| 21 | + | ||
| 22 | +class NeuronavigationApi(metaclass=Singleton): | ||
| 23 | + """ | ||
| 24 | + An API used internally in InVesalius to communicate with the | ||
| 25 | + outside world. | ||
| 26 | + | ||
| 27 | + When something noteworthy happens when running InVesalius, e.g., | ||
| 28 | + the coil is moved during neuronavigation, an object created from | ||
| 29 | + this class can be used to update that information. | ||
| 30 | + | ||
| 31 | + When created for the first time, takes a connection object obtained | ||
| 32 | + from outside InVesalius (see the main entrypoint in app.py). | ||
| 33 | + | ||
| 34 | + If connection object is not given or it is None, skip doing the updates. | ||
| 35 | + """ | ||
| 36 | + def __init__(self, connection=None): | ||
| 37 | + if connection is not None: | ||
| 38 | + assert self._hasmethod(connection, 'update_coil_pose') | ||
| 39 | + | ||
| 40 | + self.connection = connection | ||
| 41 | + | ||
| 42 | + def _hasmethod(self, obj, name): | ||
| 43 | + return hasattr(obj, name) and callable(getattr(obj, name)) | ||
| 44 | + | ||
| 45 | + def update_coil_pose(self, position, orientation): | ||
| 46 | + if self.connection is not None: | ||
| 47 | + self.connection.update_coil_pose( | ||
| 48 | + position=position, | ||
| 49 | + orientation=orientation, | ||
| 50 | + ) |