diff --git a/invesalius/data/serial_port_connection.py b/invesalius/data/serial_port_connection.py new file mode 100644 index 0000000..356c8e2 --- /dev/null +++ b/invesalius/data/serial_port_connection.py @@ -0,0 +1,79 @@ +#-------------------------------------------------------------------------- +# 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. +#-------------------------------------------------------------------------- + +import threading +import time + +import wx +from invesalius.pubsub import pub as Publisher + + +class SerialPortConnection(threading.Thread): + + def __init__(self, port, serial_port_queue, event, sleep_nav): + """ + Thread created to communicate using the serial port to interact with software during neuronavigation. + """ + threading.Thread.__init__(self, name='Serial port') + + self.connection = None + self.stylusplh = False + + self.port = port + self.serial_port_queue = serial_port_queue + self.event = event + self.sleep_nav = sleep_nav + + def Enabled(self): + return self.port is not None + + def Connect(self): + if self.port is None: + print("Serial port init error: COM port is unset.") + return + try: + import serial + self.connection = serial.Serial(self.port, baudrate=115200, timeout=0) + print("Connection to port {} opened.".format(self.port)) + except: + print("Serial port init error: Connecting to port {} failed.".format(self.port)) + + def run(self): + while not self.event.is_set(): + trigger_on = False + try: + self.connection.write(b'0') + time.sleep(0.3) + + lines = self.connection.readlines() + if lines: + trigger_on = True + + if self.stylusplh: + trigger_on = True + self.stylusplh = False + + self.serial_port_queue.put_nowait(trigger_on) + time.sleep(self.sleep_nav) + except: + print("Trigger not read, error") + else: + if self.connection: + self.connection.close() + print("Connection to port {} closed.".format(self.port)) diff --git a/invesalius/data/trigger.py b/invesalius/data/trigger.py deleted file mode 100644 index ba13634..0000000 --- a/invesalius/data/trigger.py +++ /dev/null @@ -1,168 +0,0 @@ -#-------------------------------------------------------------------------- -# 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. -#-------------------------------------------------------------------------- - -import threading -from time import sleep - -import wx -from invesalius.pubsub import pub as Publisher - - -class Trigger(threading.Thread): - """ - Thread created to use external trigger to interact with software during neuronavigation - """ - - def __init__(self, nav_id): - threading.Thread.__init__(self) - self.trigger_init = None - self.stylusplh = False - self.COM = False - self.nav_id = nav_id - self.__bind_events() - try: - import serial - - self.trigger_init = serial.Serial('COM5', baudrate=115200, timeout=0) - self.COM = True - - except: - #wx.MessageBox(_('Connection with port COM1 failed'), _('Communication error'), wx.OK | wx.ICON_ERROR) - print("Trigger init error: Connection with port COM1 failed") - self.COM = False - - self._pause_ = False - self.start() - - def __bind_events(self): - Publisher.subscribe(self.OnStylusPLH, 'PLH Stylus Button On') - - def OnStylusPLH(self): - self.stylusplh = True - - def stop(self): - self._pause_ = True - - def run(self): - while self.nav_id: - if self.COM: - self.trigger_init.write(b'0') - sleep(0.3) - lines = self.trigger_init.readlines() - # Following lines can simulate a trigger in 3 sec repetitions - # sleep(3) - # lines = True - if lines: - wx.CallAfter(Publisher.sendMessage, 'Create marker') - sleep(0.5) - - if self.stylusplh: - wx.CallAfter(Publisher.sendMessage, 'Create marker') - sleep(0.5) - self.stylusplh = False - - sleep(0.175) - if self._pause_: - if self.trigger_init: - self.trigger_init.close() - return - - -class TriggerNew(threading.Thread): - - def __init__(self, trigger_queue, event, sle): - """Class (threading) to compute real time tractography data for visualization in a single loop. - - Different than ComputeTractsThread because it does not keep adding tracts to the bundle until maximum, - is reached. It actually compute all requested tracts at once. (Might be deleted in the future)! - Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) - For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one - vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as - bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. - Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene. - - Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation - - :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads - :type inp: list - :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene - :type affine_vtk: vtkMatrix4x4 - :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered - :type coord_queue: queue.Queue - :param visualization_queue: Queue instance that manage coordinates to be visualized - :type visualization_queue: queue.Queue - :param event: Threading event to coordinate when tasks as done and allow UI release - :type event: threading.Event - :param sle: Sleep pause in seconds - :type sle: float - """ - - threading.Thread.__init__(self, name='Trigger') - - self.trigger_init = None - self.stylusplh = False - # self.COM = False - # self.__bind_events() - try: - import serial - - self.trigger_init = serial.Serial('COM5', baudrate=115200, timeout=0) - # self.COM = True - - except: - #wx.MessageBox(_('Connection with port COM1 failed'), _('Communication error'), wx.OK | wx.ICON_ERROR) - print("Trigger init error: Connection with port COM failed") - # self.COM = False - pass - - # self.coord_queue = coord_queue - self.trigger_queue = trigger_queue - self.event = event - self.sle = sle - - def run(self): - - while not self.event.is_set(): - trigger_on = False - try: - self.trigger_init.write(b'0') - sleep(0.3) - lines = self.trigger_init.readlines() - # Following lines can simulate a trigger in 3 sec repetitions - # sleep(3) - # lines = True - if lines: - trigger_on = True - # wx.CallAfter(Publisher.sendMessage, 'Create marker') - - if self.stylusplh: - trigger_on = True - # wx.CallAfter(Publisher.sendMessage, 'Create marker') - self.stylusplh = False - - self.trigger_queue.put_nowait(trigger_on) - sleep(self.sle) - - except: - print("Trigger not read, error") - pass - - else: - if self.trigger_init: - self.trigger_init.close() diff --git a/invesalius/gui/task_navigator.py b/invesalius/gui/task_navigator.py index 4ffe8c1..83b1116 100644 --- a/invesalius/gui/task_navigator.py +++ b/invesalius/gui/task_navigator.py @@ -54,11 +54,11 @@ if has_trekker: import invesalius.data.coordinates as dco import invesalius.data.coregistration as dcr +import invesalius.data.serial_port_connection as spc import invesalius.data.slice_ as sl import invesalius.data.trackers as dt import invesalius.data.tractography as dti import invesalius.data.transformations as tr -import invesalius.data.trigger as trig import invesalius.data.record_coords as rec import invesalius.data.vtk_utils as vtk_utils import invesalius.gui.dialogs as dlg @@ -215,13 +215,13 @@ class InnerFoldPanel(wx.Panel): checkcamera.Bind(wx.EVT_CHECKBOX, self.OnVolumeCamera) self.checkcamera = checkcamera - # Check box for trigger monitoring to create markers from serial port - tooltip = wx.ToolTip(_("Enable external trigger for creating markers")) - checktrigger = wx.CheckBox(self, -1, _('Ext. trigger')) - checktrigger.SetToolTip(tooltip) - checktrigger.SetValue(False) - checktrigger.Bind(wx.EVT_CHECKBOX, partial(self.OnExternalTrigger, ctrl=checktrigger)) - self.checktrigger = checktrigger + # Check box to create markers from serial port + tooltip = wx.ToolTip(_("Enable serial port communication for creating markers")) + checkbox_serial_port = wx.CheckBox(self, -1, _('Serial port')) + checkbox_serial_port.SetToolTip(tooltip) + checkbox_serial_port.SetValue(False) + checkbox_serial_port.Bind(wx.EVT_CHECKBOX, partial(self.OnEnableSerialPort, ctrl=checkbox_serial_port)) + self.checkbox_serial_port = checkbox_serial_port # Check box for object position and orientation update in volume rendering during navigation tooltip = wx.ToolTip(_("Show and track TMS coil")) @@ -234,12 +234,12 @@ class InnerFoldPanel(wx.Panel): # if sys.platform != 'win32': self.checkcamera.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) - checktrigger.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + checkbox_serial_port.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) checkobj.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) line_sizer = wx.BoxSizer(wx.HORIZONTAL) line_sizer.Add(checkcamera, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5) - line_sizer.Add(checktrigger, 0, wx.ALIGN_CENTER) + line_sizer.Add(checkbox_serial_port, 0, wx.ALIGN_CENTER) line_sizer.Add(checkobj, 0, wx.RIGHT | wx.LEFT, 5) line_sizer.Fit(self) @@ -270,15 +270,22 @@ class InnerFoldPanel(wx.Panel): def OnCheckStatus(self, nav_status, vis_status): if nav_status: - self.checktrigger.Enable(False) + self.checkbox_serial_port.Enable(False) self.checkobj.Enable(False) else: - self.checktrigger.Enable(True) + self.checkbox_serial_port.Enable(True) if self.track_obj: self.checkobj.Enable(True) - def OnExternalTrigger(self, evt, ctrl): - Publisher.sendMessage('Update trigger state', trigger_state=ctrl.GetValue()) + def OnEnableSerialPort(self, evt, ctrl): + com_port = None + if ctrl.GetValue(): + from wx import ID_OK + dlg_port = dlg.SetCOMport() + if dlg_port.ShowModal() == ID_OK: + com_port = dlg_port.GetValue() + + Publisher.sendMessage('Update serial port', serial_port=com_port) def OnShowObject(self, evt=None, flag=None, obj_name=None, polydata=None): if not evt: @@ -307,8 +314,6 @@ class Navigation(): self.correg = None self.current_coord = 0, 0, 0 self.target = None - self.trigger = None - self.trigger_state = False self.obj_reg = None self.track_obj = False self.m_change = None @@ -318,7 +323,7 @@ class Navigation(): self.coord_queue = QueueCustom(maxsize=1) self.icp_queue = QueueCustom(maxsize=1) # self.visualization_queue = QueueCustom(maxsize=1) - self.trigger_queue = QueueCustom(maxsize=1) + self.serial_port_queue = QueueCustom(maxsize=1) self.coord_tracts_queue = QueueCustom(maxsize=1) self.tracts_queue = QueueCustom(maxsize=1) @@ -335,6 +340,17 @@ class Navigation(): self.seed_radius = const.SEED_RADIUS self.sleep_nav = const.SLEEP_NAVIGATION + # Serial port + self.serial_port = None + self.serial_port_connection = None + + def UpdateSleep(self, sleep): + self.sleep_nav = sleep + self.serial_port_connection.sleep_nav = sleep + + def SerialPortEnabled(self): + return self.serial_port is not None + def SetImageFiducial(self, fiducial_index, coord): self.image_fiducials[fiducial_index, :] = coord @@ -365,8 +381,8 @@ class Navigation(): if self.event.is_set(): self.event.clear() - vis_components = [self.trigger_state, self.view_tracts, self.peel_loaded] - vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue] + vis_components = [self.SerialPortEnabled(), self.view_tracts, self.peel_loaded] + vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue] Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) @@ -413,10 +429,16 @@ class Navigation(): self.event, self.sleep_nav)) if not errors: - #TODO: Test the trigger thread - if self.trigger_state: - # self.trigger = trig.Trigger(nav_id) - jobs_list.append(trig.TriggerNew(self.trigger_queue, self.event, self.sleep_nav)) + #TODO: Test the serial port thread + if self.SerialPortEnabled(): + self.serial_port_connection = spc.SerialPortConnection( + self.serial_port, + self.serial_port_queue, + self.event, + self.sleep_nav, + ) + self.serial_port_connection.Connect() + jobs_list.append(self.serial_port_connection) if self.view_tracts: # initialize Trekker parameters @@ -450,9 +472,12 @@ class Navigation(): self.coord_queue.clear() self.coord_queue.join() - if self.trigger_state: - self.trigger_queue.clear() - self.trigger_queue.join() + if self.SerialPortEnabled(): + self.serial_port_connection.join() + + self.serial_port_queue.clear() + self.serial_port_queue.join() + if self.view_tracts: self.coord_tracts_queue.clear() self.coord_tracts_queue.join() @@ -460,7 +485,7 @@ class Navigation(): self.tracts_queue.clear() self.tracts_queue.join() - vis_components = [self.trigger_state, self.view_tracts, self.peel_loaded] + vis_components = [self.serial_port_connection.Enabled(), self.view_tracts, self.peel_loaded] Publisher.sendMessage("Navigation status", nav_status=False, vis_status=vis_components) @@ -800,7 +825,7 @@ class NeuronavigationPanel(wx.Panel): Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') Publisher.subscribe(self.SetImageFiducial, 'Set image fiducial') Publisher.subscribe(self.SetTrackerFiducial, 'Set tracker fiducial') - Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') + Publisher.subscribe(self.UpdateSerialPort, 'Update serial port') Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state') Publisher.subscribe(self.UpdateImageCoordinates, 'Set cross focal point') Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker') @@ -887,7 +912,7 @@ class NeuronavigationPanel(wx.Panel): self.navigation.seed_radius = data def UpdateSleep(self, data): - self.navigation.sleep_nav = data + self.navigation.UpdateSleep(data) def UpdateNumberThreads(self, data): self.navigation.n_threads = data @@ -919,8 +944,8 @@ class NeuronavigationPanel(wx.Panel): def UpdateTrackObjectState(self, evt=None, flag=None, obj_name=None, polydata=None): self.navigation.track_obj = flag - def UpdateTriggerState(self, trigger_state): - self.navigation.trigger_state = trigger_state + def UpdateSerialPort(self, serial_port): + self.navigation.serial_port = serial_port def ResetICP(self): self.icp.ResetICP() @@ -2281,8 +2306,8 @@ class UpdateNavigationScene(threading.Thread): """ threading.Thread.__init__(self, name='UpdateScene') - self.trigger_state, self.view_tracts, self.peel_loaded = vis_components - self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue = vis_queues + self.serial_port_enabled, self.view_tracts, self.peel_loaded = vis_components + self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue = vis_queues self.sle = sle self.event = event @@ -2306,11 +2331,11 @@ class UpdateNavigationScene(threading.Thread): # wx.CallAfter(Publisher.sendMessage, 'Update marker offset', coord_offset=coord_offset) self.tracts_queue.task_done() - if self.trigger_state: - trigger_on = self.trigger_queue.get_nowait() + if self.serial_port_enabled: + trigger_on = self.serial_port_queue.get_nowait() if trigger_on: wx.CallAfter(Publisher.sendMessage, 'Create marker') - self.trigger_queue.task_done() + self.serial_port_queue.task_done() #TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user # see the red cross in the position of the offset marker -- libgit2 0.21.2