From 3f9f34c4dff93b27fa01eafaee41688987d6042d Mon Sep 17 00:00:00 2001 From: Olli-Pekka Kahilakoski Date: Fri, 14 Jan 2022 21:16:43 +0200 Subject: [PATCH] ADD: NeuronavigationApi to send coil pose via connection object (#413) --- app.py | 17 ++++++++++++++++- invesalius/gui/task_navigator.py | 15 +++++++++++++-- invesalius/navigation/navigation.py | 25 +++++++++++++++++++++---- invesalius/net/neuronavigation_api.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 invesalius/net/neuronavigation_api.py diff --git a/app.py b/app.py index bbec907..a5bc7cf 100644 --- a/app.py +++ b/app.py @@ -490,9 +490,20 @@ def print_events(topic=Publisher.AUTO_TOPIC, **msg_data): utils.debug("%s\n\tParameters: %s" % (topic, msg_data)) -def main(): +def main(connection=None): """ Initialize InVesalius GUI + + Parameters: + connection: An object to communicate with the outside world. + In theory, can be any object supports certain function calls. + See invesalius.net.neuronavigation_api for a comprehensive + description of how the object is used. + + Note that if InVesalius is started in the usual way by running + app.py, the connection object defaults to None. To enable this + functionality, InVesalius needs to be started by calling the main + function directly with a proper connection object. """ options, args = parse_command_line() @@ -513,6 +524,10 @@ def main(): PedalConnection().start() + from invesalius.net.neuronavigation_api import NeuronavigationApi + + NeuronavigationApi(connection) + if options.no_gui: non_gui_startup(options, args) else: diff --git a/invesalius/gui/task_navigator.py b/invesalius/gui/task_navigator.py index 07aeb12..e7691a7 100644 --- a/invesalius/gui/task_navigator.py +++ b/invesalius/gui/task_navigator.py @@ -73,6 +73,8 @@ from invesalius.navigation.navigation import Navigation from invesalius.navigation.tracker import Tracker from invesalius.navigation.robot import Robot +from invesalius.net.neuronavigation_api import NeuronavigationApi + HAS_PEDAL_CONNECTION = True try: from invesalius.net.pedal_connection import PedalConnection @@ -172,6 +174,8 @@ class InnerFoldPanel(wx.Panel): tracker = Tracker() pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None + neuronavigation_api = NeuronavigationApi() + # Fold panel style style = fpb.CaptionBarStyle() style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) @@ -180,7 +184,12 @@ class InnerFoldPanel(wx.Panel): # Fold 1 - Navigation panel item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True) - ntw = NeuronavigationPanel(item, tracker, pedal_connection) + ntw = NeuronavigationPanel( + parent=item, + tracker=tracker, + pedal_connection=pedal_connection, + neuronavigation_api=neuronavigation_api, + ) fold_panel.ApplyCaptionStyle(item, style) fold_panel.AddFoldPanelWindow(item, ntw, spacing=0, @@ -336,7 +345,7 @@ class InnerFoldPanel(wx.Panel): class NeuronavigationPanel(wx.Panel): - def __init__(self, parent, tracker, pedal_connection): + def __init__(self, parent, tracker, pedal_connection, neuronavigation_api): wx.Panel.__init__(self, parent) try: default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) @@ -350,8 +359,10 @@ class NeuronavigationPanel(wx.Panel): # Initialize global variables self.pedal_connection = pedal_connection + self.navigation = Navigation( pedal_connection=pedal_connection, + neuronavigation_api=neuronavigation_api, ) self.icp = ICP() self.tracker = tracker diff --git a/invesalius/navigation/navigation.py b/invesalius/navigation/navigation.py index bc5c299..86e7301 100644 --- a/invesalius/navigation/navigation.py +++ b/invesalius/navigation/navigation.py @@ -64,7 +64,7 @@ class QueueCustom(queue.Queue): class UpdateNavigationScene(threading.Thread): - def __init__(self, vis_queues, vis_components, event, sle): + def __init__(self, vis_queues, vis_components, event, sle, neuronavigation_api): """Class (threading) to update the navigation scene with all graphical elements. 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): :type event: threading.Event :param sle: Sleep pause in seconds :type sle: float + :param neuronavigation_api: An API object for communicating the coil position. + :type neuronavigation_api: invesalius.net.neuronavigation_api.NeuronavigationAPI """ threading.Thread.__init__(self, name='UpdateScene') @@ -84,6 +86,7 @@ class UpdateNavigationScene(threading.Thread): self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue = vis_queues self.sle = sle self.event = event + self.neuronavigation_api = neuronavigation_api def run(self): # count = 0 @@ -121,6 +124,12 @@ class UpdateNavigationScene(threading.Thread): if view_obj: wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) wx.CallAfter(Publisher.sendMessage, 'Update object arrow matrix', m_img=m_img, coord=coord, flag= self.peel_loaded) + + self.neuronavigation_api.update_coil_pose( + position=coord[:3], + orientation=coord[3:], + ) + self.coord_queue.task_done() # print('UpdateScene: done {}'.format(count)) # count += 1 @@ -132,8 +141,9 @@ class UpdateNavigationScene(threading.Thread): class Navigation(): - def __init__(self, pedal_connection): + def __init__(self, pedal_connection, neuronavigation_api): self.pedal_connection = pedal_connection + self.neuronavigation_api = neuronavigation_api self.image_fiducials = np.full([3, 3], np.nan) self.correg = None @@ -329,8 +339,15 @@ class Navigation(): else: jobs_list.append(dti.ComputeTractsThread(self.trk_inp, queues, self.event, self.sleep_nav)) - jobs_list.append(UpdateNavigationScene(vis_queues, vis_components, - self.event, self.sleep_nav)) + jobs_list.append( + UpdateNavigationScene( + vis_queues=vis_queues, + vis_components=vis_components, + event=self.event, + sle=self.sleep_nav, + neuronavigation_api=self.neuronavigation_api, + ) + ) for jobs in jobs_list: # jobs.daemon = True diff --git a/invesalius/net/neuronavigation_api.py b/invesalius/net/neuronavigation_api.py new file mode 100644 index 0000000..bcd0849 --- /dev/null +++ b/invesalius/net/neuronavigation_api.py @@ -0,0 +1,50 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- + +from invesalius.utils import Singleton + +class NeuronavigationApi(metaclass=Singleton): + """ + An API used internally in InVesalius to communicate with the + outside world. + + When something noteworthy happens when running InVesalius, e.g., + the coil is moved during neuronavigation, an object created from + this class can be used to update that information. + + When created for the first time, takes a connection object obtained + from outside InVesalius (see the main entrypoint in app.py). + + If connection object is not given or it is None, skip doing the updates. + """ + def __init__(self, connection=None): + if connection is not None: + assert self._hasmethod(connection, 'update_coil_pose') + + self.connection = connection + + def _hasmethod(self, obj, name): + return hasattr(obj, name) and callable(getattr(obj, name)) + + def update_coil_pose(self, position, orientation): + if self.connection is not None: + self.connection.update_coil_pose( + position=position, + orientation=orientation, + ) -- libgit2 0.21.2