Commit d133ced30f965e07402c3c96120888cdd616b02c

Authored by okahilak
Committed by GitHub
1 parent db47444c
Exists in master

MOD: Allow selecting COM port when enabling serial port communication (#325)

* CLP: Clean up naming in task_navigator.py

Namely, be more specific about the type of trigger input (in this
case, the serial port), as nowadays the MIDI input can be used for
triggering, as well.

* CLP: Remove unused variable 'trigger' from Navigation class

* CLP: More clean-up related to serial port communication

- Change invesalius.data.trigger to invesalius.data.serial_port_connection

- Rename the class TriggerNew to SerialPortConnection

- Remove the old, unused Trigger class

- Rename a couple of variables in SerialPortConnection class

- Replace the incorrect docstring for SerialPortConnection class with a
  brief but more accurate docstring.

* CLP: More clean-up in SerialPortConnection

- Remove commented out code

- Remove some blank lines from function definitions

- Do not import sleep from time library, but instead use time.sleep

* MOD: Allow selecting COM port when enabling serial port communication
invesalius/data/serial_port_connection.py 0 → 100644
... ... @@ -0,0 +1,79 @@
  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 +import threading
  21 +import time
  22 +
  23 +import wx
  24 +from invesalius.pubsub import pub as Publisher
  25 +
  26 +
  27 +class SerialPortConnection(threading.Thread):
  28 +
  29 + def __init__(self, port, serial_port_queue, event, sleep_nav):
  30 + """
  31 + Thread created to communicate using the serial port to interact with software during neuronavigation.
  32 + """
  33 + threading.Thread.__init__(self, name='Serial port')
  34 +
  35 + self.connection = None
  36 + self.stylusplh = False
  37 +
  38 + self.port = port
  39 + self.serial_port_queue = serial_port_queue
  40 + self.event = event
  41 + self.sleep_nav = sleep_nav
  42 +
  43 + def Enabled(self):
  44 + return self.port is not None
  45 +
  46 + def Connect(self):
  47 + if self.port is None:
  48 + print("Serial port init error: COM port is unset.")
  49 + return
  50 + try:
  51 + import serial
  52 + self.connection = serial.Serial(self.port, baudrate=115200, timeout=0)
  53 + print("Connection to port {} opened.".format(self.port))
  54 + except:
  55 + print("Serial port init error: Connecting to port {} failed.".format(self.port))
  56 +
  57 + def run(self):
  58 + while not self.event.is_set():
  59 + trigger_on = False
  60 + try:
  61 + self.connection.write(b'0')
  62 + time.sleep(0.3)
  63 +
  64 + lines = self.connection.readlines()
  65 + if lines:
  66 + trigger_on = True
  67 +
  68 + if self.stylusplh:
  69 + trigger_on = True
  70 + self.stylusplh = False
  71 +
  72 + self.serial_port_queue.put_nowait(trigger_on)
  73 + time.sleep(self.sleep_nav)
  74 + except:
  75 + print("Trigger not read, error")
  76 + else:
  77 + if self.connection:
  78 + self.connection.close()
  79 + print("Connection to port {} closed.".format(self.port))
... ...
invesalius/data/trigger.py
... ... @@ -1,168 +0,0 @@
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   -import threading
21   -from time import sleep
22   -
23   -import wx
24   -from invesalius.pubsub import pub as Publisher
25   -
26   -
27   -class Trigger(threading.Thread):
28   - """
29   - Thread created to use external trigger to interact with software during neuronavigation
30   - """
31   -
32   - def __init__(self, nav_id):
33   - threading.Thread.__init__(self)
34   - self.trigger_init = None
35   - self.stylusplh = False
36   - self.COM = False
37   - self.nav_id = nav_id
38   - self.__bind_events()
39   - try:
40   - import serial
41   -
42   - self.trigger_init = serial.Serial('COM5', baudrate=115200, timeout=0)
43   - self.COM = True
44   -
45   - except:
46   - #wx.MessageBox(_('Connection with port COM1 failed'), _('Communication error'), wx.OK | wx.ICON_ERROR)
47   - print("Trigger init error: Connection with port COM1 failed")
48   - self.COM = False
49   -
50   - self._pause_ = False
51   - self.start()
52   -
53   - def __bind_events(self):
54   - Publisher.subscribe(self.OnStylusPLH, 'PLH Stylus Button On')
55   -
56   - def OnStylusPLH(self):
57   - self.stylusplh = True
58   -
59   - def stop(self):
60   - self._pause_ = True
61   -
62   - def run(self):
63   - while self.nav_id:
64   - if self.COM:
65   - self.trigger_init.write(b'0')
66   - sleep(0.3)
67   - lines = self.trigger_init.readlines()
68   - # Following lines can simulate a trigger in 3 sec repetitions
69   - # sleep(3)
70   - # lines = True
71   - if lines:
72   - wx.CallAfter(Publisher.sendMessage, 'Create marker')
73   - sleep(0.5)
74   -
75   - if self.stylusplh:
76   - wx.CallAfter(Publisher.sendMessage, 'Create marker')
77   - sleep(0.5)
78   - self.stylusplh = False
79   -
80   - sleep(0.175)
81   - if self._pause_:
82   - if self.trigger_init:
83   - self.trigger_init.close()
84   - return
85   -
86   -
87   -class TriggerNew(threading.Thread):
88   -
89   - def __init__(self, trigger_queue, event, sle):
90   - """Class (threading) to compute real time tractography data for visualization in a single loop.
91   -
92   - Different than ComputeTractsThread because it does not keep adding tracts to the bundle until maximum,
93   - is reached. It actually compute all requested tracts at once. (Might be deleted in the future)!
94   - Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/)
95   - For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one
96   - vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as
97   - bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor.
98   - Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene.
99   -
100   - Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation
101   -
102   - :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads
103   - :type inp: list
104   - :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene
105   - :type affine_vtk: vtkMatrix4x4
106   - :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered
107   - :type coord_queue: queue.Queue
108   - :param visualization_queue: Queue instance that manage coordinates to be visualized
109   - :type visualization_queue: queue.Queue
110   - :param event: Threading event to coordinate when tasks as done and allow UI release
111   - :type event: threading.Event
112   - :param sle: Sleep pause in seconds
113   - :type sle: float
114   - """
115   -
116   - threading.Thread.__init__(self, name='Trigger')
117   -
118   - self.trigger_init = None
119   - self.stylusplh = False
120   - # self.COM = False
121   - # self.__bind_events()
122   - try:
123   - import serial
124   -
125   - self.trigger_init = serial.Serial('COM5', baudrate=115200, timeout=0)
126   - # self.COM = True
127   -
128   - except:
129   - #wx.MessageBox(_('Connection with port COM1 failed'), _('Communication error'), wx.OK | wx.ICON_ERROR)
130   - print("Trigger init error: Connection with port COM failed")
131   - # self.COM = False
132   - pass
133   -
134   - # self.coord_queue = coord_queue
135   - self.trigger_queue = trigger_queue
136   - self.event = event
137   - self.sle = sle
138   -
139   - def run(self):
140   -
141   - while not self.event.is_set():
142   - trigger_on = False
143   - try:
144   - self.trigger_init.write(b'0')
145   - sleep(0.3)
146   - lines = self.trigger_init.readlines()
147   - # Following lines can simulate a trigger in 3 sec repetitions
148   - # sleep(3)
149   - # lines = True
150   - if lines:
151   - trigger_on = True
152   - # wx.CallAfter(Publisher.sendMessage, 'Create marker')
153   -
154   - if self.stylusplh:
155   - trigger_on = True
156   - # wx.CallAfter(Publisher.sendMessage, 'Create marker')
157   - self.stylusplh = False
158   -
159   - self.trigger_queue.put_nowait(trigger_on)
160   - sleep(self.sle)
161   -
162   - except:
163   - print("Trigger not read, error")
164   - pass
165   -
166   - else:
167   - if self.trigger_init:
168   - self.trigger_init.close()
invesalius/gui/task_navigator.py
... ... @@ -54,11 +54,11 @@ if has_trekker:
54 54  
55 55 import invesalius.data.coordinates as dco
56 56 import invesalius.data.coregistration as dcr
  57 +import invesalius.data.serial_port_connection as spc
57 58 import invesalius.data.slice_ as sl
58 59 import invesalius.data.trackers as dt
59 60 import invesalius.data.tractography as dti
60 61 import invesalius.data.transformations as tr
61   -import invesalius.data.trigger as trig
62 62 import invesalius.data.record_coords as rec
63 63 import invesalius.data.vtk_utils as vtk_utils
64 64 import invesalius.gui.dialogs as dlg
... ... @@ -215,13 +215,13 @@ class InnerFoldPanel(wx.Panel):
215 215 checkcamera.Bind(wx.EVT_CHECKBOX, self.OnVolumeCamera)
216 216 self.checkcamera = checkcamera
217 217  
218   - # Check box for trigger monitoring to create markers from serial port
219   - tooltip = wx.ToolTip(_("Enable external trigger for creating markers"))
220   - checktrigger = wx.CheckBox(self, -1, _('Ext. trigger'))
221   - checktrigger.SetToolTip(tooltip)
222   - checktrigger.SetValue(False)
223   - checktrigger.Bind(wx.EVT_CHECKBOX, partial(self.OnExternalTrigger, ctrl=checktrigger))
224   - self.checktrigger = checktrigger
  218 + # Check box to create markers from serial port
  219 + tooltip = wx.ToolTip(_("Enable serial port communication for creating markers"))
  220 + checkbox_serial_port = wx.CheckBox(self, -1, _('Serial port'))
  221 + checkbox_serial_port.SetToolTip(tooltip)
  222 + checkbox_serial_port.SetValue(False)
  223 + checkbox_serial_port.Bind(wx.EVT_CHECKBOX, partial(self.OnEnableSerialPort, ctrl=checkbox_serial_port))
  224 + self.checkbox_serial_port = checkbox_serial_port
225 225  
226 226 # Check box for object position and orientation update in volume rendering during navigation
227 227 tooltip = wx.ToolTip(_("Show and track TMS coil"))
... ... @@ -234,12 +234,12 @@ class InnerFoldPanel(wx.Panel):
234 234  
235 235 # if sys.platform != 'win32':
236 236 self.checkcamera.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
237   - checktrigger.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
  237 + checkbox_serial_port.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
238 238 checkobj.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
239 239  
240 240 line_sizer = wx.BoxSizer(wx.HORIZONTAL)
241 241 line_sizer.Add(checkcamera, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5)
242   - line_sizer.Add(checktrigger, 0, wx.ALIGN_CENTER)
  242 + line_sizer.Add(checkbox_serial_port, 0, wx.ALIGN_CENTER)
243 243 line_sizer.Add(checkobj, 0, wx.RIGHT | wx.LEFT, 5)
244 244 line_sizer.Fit(self)
245 245  
... ... @@ -270,15 +270,22 @@ class InnerFoldPanel(wx.Panel):
270 270  
271 271 def OnCheckStatus(self, nav_status, vis_status):
272 272 if nav_status:
273   - self.checktrigger.Enable(False)
  273 + self.checkbox_serial_port.Enable(False)
274 274 self.checkobj.Enable(False)
275 275 else:
276   - self.checktrigger.Enable(True)
  276 + self.checkbox_serial_port.Enable(True)
277 277 if self.track_obj:
278 278 self.checkobj.Enable(True)
279 279  
280   - def OnExternalTrigger(self, evt, ctrl):
281   - Publisher.sendMessage('Update trigger state', trigger_state=ctrl.GetValue())
  280 + def OnEnableSerialPort(self, evt, ctrl):
  281 + com_port = None
  282 + if ctrl.GetValue():
  283 + from wx import ID_OK
  284 + dlg_port = dlg.SetCOMport()
  285 + if dlg_port.ShowModal() == ID_OK:
  286 + com_port = dlg_port.GetValue()
  287 +
  288 + Publisher.sendMessage('Update serial port', serial_port=com_port)
282 289  
283 290 def OnShowObject(self, evt=None, flag=None, obj_name=None, polydata=None):
284 291 if not evt:
... ... @@ -307,8 +314,6 @@ class Navigation():
307 314 self.correg = None
308 315 self.current_coord = 0, 0, 0
309 316 self.target = None
310   - self.trigger = None
311   - self.trigger_state = False
312 317 self.obj_reg = None
313 318 self.track_obj = False
314 319 self.m_change = None
... ... @@ -318,7 +323,7 @@ class Navigation():
318 323 self.coord_queue = QueueCustom(maxsize=1)
319 324 self.icp_queue = QueueCustom(maxsize=1)
320 325 # self.visualization_queue = QueueCustom(maxsize=1)
321   - self.trigger_queue = QueueCustom(maxsize=1)
  326 + self.serial_port_queue = QueueCustom(maxsize=1)
322 327 self.coord_tracts_queue = QueueCustom(maxsize=1)
323 328 self.tracts_queue = QueueCustom(maxsize=1)
324 329  
... ... @@ -335,6 +340,17 @@ class Navigation():
335 340 self.seed_radius = const.SEED_RADIUS
336 341 self.sleep_nav = const.SLEEP_NAVIGATION
337 342  
  343 + # Serial port
  344 + self.serial_port = None
  345 + self.serial_port_connection = None
  346 +
  347 + def UpdateSleep(self, sleep):
  348 + self.sleep_nav = sleep
  349 + self.serial_port_connection.sleep_nav = sleep
  350 +
  351 + def SerialPortEnabled(self):
  352 + return self.serial_port is not None
  353 +
338 354 def SetImageFiducial(self, fiducial_index, coord):
339 355 self.image_fiducials[fiducial_index, :] = coord
340 356  
... ... @@ -365,8 +381,8 @@ class Navigation():
365 381 if self.event.is_set():
366 382 self.event.clear()
367 383  
368   - vis_components = [self.trigger_state, self.view_tracts, self.peel_loaded]
369   - vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue]
  384 + vis_components = [self.SerialPortEnabled(), self.view_tracts, self.peel_loaded]
  385 + vis_queues = [self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue]
370 386  
371 387 Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components)
372 388  
... ... @@ -413,10 +429,16 @@ class Navigation():
413 429 self.event, self.sleep_nav))
414 430  
415 431 if not errors:
416   - #TODO: Test the trigger thread
417   - if self.trigger_state:
418   - # self.trigger = trig.Trigger(nav_id)
419   - jobs_list.append(trig.TriggerNew(self.trigger_queue, self.event, self.sleep_nav))
  432 + #TODO: Test the serial port thread
  433 + if self.SerialPortEnabled():
  434 + self.serial_port_connection = spc.SerialPortConnection(
  435 + self.serial_port,
  436 + self.serial_port_queue,
  437 + self.event,
  438 + self.sleep_nav,
  439 + )
  440 + self.serial_port_connection.Connect()
  441 + jobs_list.append(self.serial_port_connection)
420 442  
421 443 if self.view_tracts:
422 444 # initialize Trekker parameters
... ... @@ -450,9 +472,12 @@ class Navigation():
450 472 self.coord_queue.clear()
451 473 self.coord_queue.join()
452 474  
453   - if self.trigger_state:
454   - self.trigger_queue.clear()
455   - self.trigger_queue.join()
  475 + if self.SerialPortEnabled():
  476 + self.serial_port_connection.join()
  477 +
  478 + self.serial_port_queue.clear()
  479 + self.serial_port_queue.join()
  480 +
456 481 if self.view_tracts:
457 482 self.coord_tracts_queue.clear()
458 483 self.coord_tracts_queue.join()
... ... @@ -460,7 +485,7 @@ class Navigation():
460 485 self.tracts_queue.clear()
461 486 self.tracts_queue.join()
462 487  
463   - vis_components = [self.trigger_state, self.view_tracts, self.peel_loaded]
  488 + vis_components = [self.serial_port_connection.Enabled(), self.view_tracts, self.peel_loaded]
464 489 Publisher.sendMessage("Navigation status", nav_status=False, vis_status=vis_components)
465 490  
466 491  
... ... @@ -800,7 +825,7 @@ class NeuronavigationPanel(wx.Panel):
800 825 Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials')
801 826 Publisher.subscribe(self.SetImageFiducial, 'Set image fiducial')
802 827 Publisher.subscribe(self.SetTrackerFiducial, 'Set tracker fiducial')
803   - Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state')
  828 + Publisher.subscribe(self.UpdateSerialPort, 'Update serial port')
804 829 Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state')
805 830 Publisher.subscribe(self.UpdateImageCoordinates, 'Set cross focal point')
806 831 Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker')
... ... @@ -887,7 +912,7 @@ class NeuronavigationPanel(wx.Panel):
887 912 self.navigation.seed_radius = data
888 913  
889 914 def UpdateSleep(self, data):
890   - self.navigation.sleep_nav = data
  915 + self.navigation.UpdateSleep(data)
891 916  
892 917 def UpdateNumberThreads(self, data):
893 918 self.navigation.n_threads = data
... ... @@ -919,8 +944,8 @@ class NeuronavigationPanel(wx.Panel):
919 944 def UpdateTrackObjectState(self, evt=None, flag=None, obj_name=None, polydata=None):
920 945 self.navigation.track_obj = flag
921 946  
922   - def UpdateTriggerState(self, trigger_state):
923   - self.navigation.trigger_state = trigger_state
  947 + def UpdateSerialPort(self, serial_port):
  948 + self.navigation.serial_port = serial_port
924 949  
925 950 def ResetICP(self):
926 951 self.icp.ResetICP()
... ... @@ -2281,8 +2306,8 @@ class UpdateNavigationScene(threading.Thread):
2281 2306 """
2282 2307  
2283 2308 threading.Thread.__init__(self, name='UpdateScene')
2284   - self.trigger_state, self.view_tracts, self.peel_loaded = vis_components
2285   - self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue = vis_queues
  2309 + self.serial_port_enabled, self.view_tracts, self.peel_loaded = vis_components
  2310 + self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue = vis_queues
2286 2311 self.sle = sle
2287 2312 self.event = event
2288 2313  
... ... @@ -2306,11 +2331,11 @@ class UpdateNavigationScene(threading.Thread):
2306 2331 # wx.CallAfter(Publisher.sendMessage, 'Update marker offset', coord_offset=coord_offset)
2307 2332 self.tracts_queue.task_done()
2308 2333  
2309   - if self.trigger_state:
2310   - trigger_on = self.trigger_queue.get_nowait()
  2334 + if self.serial_port_enabled:
  2335 + trigger_on = self.serial_port_queue.get_nowait()
2311 2336 if trigger_on:
2312 2337 wx.CallAfter(Publisher.sendMessage, 'Create marker')
2313   - self.trigger_queue.task_done()
  2338 + self.serial_port_queue.task_done()
2314 2339  
2315 2340 #TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user
2316 2341 # see the red cross in the position of the offset marker
... ...