Commit 0f775c4e8b37f4de8d9b9acc4e62e3af298d20f7

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

ADD: Send pulse via serial port when pedal triggered and coil at target (#326)

- Do more specific error handling in SerialPortConnection class,
  distinguishing between the error caused by the queue becoming full
  from the error caused by reading from or writing into the serial port.

- Also, add a comment explaining why additional sleeping is needed inside
  the thread in SerialPortConnection class.
invesalius/data/serial_port_connection.py
... ... @@ -17,6 +17,7 @@
17 17 # detalhes.
18 18 #--------------------------------------------------------------------------
19 19  
  20 +import queue
20 21 import threading
21 22 import time
22 23  
... ... @@ -25,6 +26,7 @@ from invesalius.pubsub import pub as Publisher
25 26  
26 27  
27 28 class SerialPortConnection(threading.Thread):
  29 + BINARY_PULSE = b'\x01'
28 30  
29 31 def __init__(self, port, serial_port_queue, event, sleep_nav):
30 32 """
... ... @@ -54,25 +56,49 @@ class SerialPortConnection(threading.Thread):
54 56 except:
55 57 print("Serial port init error: Connecting to port {} failed.".format(self.port))
56 58  
  59 + def SendPulse(self):
  60 + success = False
  61 + try:
  62 + self.connection.write(self.BINARY_PULSE)
  63 + success = True
  64 + except:
  65 + print("Error: Serial port could not be written into.")
  66 +
  67 + return success
  68 +
57 69 def run(self):
58 70 while not self.event.is_set():
59 71 trigger_on = False
60 72 try:
61   - self.connection.write(b'0')
62   - time.sleep(0.3)
63   -
64 73 lines = self.connection.readlines()
65   - if lines:
66   - trigger_on = True
  74 + except:
  75 + print("Error: Serial port could not be read.")
67 76  
68   - if self.stylusplh:
69   - trigger_on = True
70   - self.stylusplh = False
  77 + if lines:
  78 + trigger_on = True
71 79  
  80 + if self.stylusplh:
  81 + trigger_on = True
  82 + self.stylusplh = False
  83 +
  84 + try:
72 85 self.serial_port_queue.put_nowait(trigger_on)
73   - time.sleep(self.sleep_nav)
74   - except:
75   - print("Trigger not read, error")
  86 + except queue.Full:
  87 + print("Error: Serial port queue full.")
  88 +
  89 + time.sleep(self.sleep_nav)
  90 +
  91 + # XXX: This is needed here because the serial port queue has to be read
  92 + # at least as fast as it is written into, otherwise it will eventually
  93 + # become full. Reading is done in another thread, which has the same
  94 + # sleeping parameter sleep_nav between consecutive runs as this thread.
  95 + # However, a single run of that thread takes longer to finish than a
  96 + # single run of this thread, causing that thread to lag behind. Hence,
  97 + # the additional sleeping here to ensure that this thread lags behind the
  98 + # other thread and not the other way around. However, it would be nice to
  99 + # handle the timing dependencies between the threads in a more robust way.
  100 + #
  101 + time.sleep(0.3)
76 102 else:
77 103 if self.connection:
78 104 self.connection.close()
... ...
invesalius/gui/task_navigator.py
... ... @@ -310,6 +310,8 @@ class InnerFoldPanel(wx.Panel):
310 310  
311 311 class Navigation():
312 312 def __init__(self):
  313 + self.pedal_connection = PedalConnection()
  314 +
313 315 self.image_fiducials = np.full([3, 3], np.nan)
314 316 self.correg = None
315 317 self.current_coord = 0, 0, 0
... ... @@ -344,6 +346,17 @@ class Navigation():
344 346 self.serial_port = None
345 347 self.serial_port_connection = None
346 348  
  349 + # During navigation
  350 + self.coil_at_target = False
  351 +
  352 + self.__bind_events()
  353 +
  354 + def __bind_events(self):
  355 + Publisher.subscribe(self.CoilAtTarget, 'Coil at target')
  356 +
  357 + def CoilAtTarget(self, state):
  358 + self.coil_at_target = state
  359 +
347 360 def UpdateSleep(self, sleep):
348 361 self.sleep_nav = sleep
349 362 self.serial_port_connection.sleep_nav = sleep
... ... @@ -371,6 +384,12 @@ class Navigation():
371 384 fre = icp.icp_fre if icp.use_icp else self.fre
372 385 return fre, fre <= const.FIDUCIAL_REGISTRATION_ERROR_THRESHOLD
373 386  
  387 + def PedalStateChanged(self, state):
  388 + if state is True and self.coil_at_target and self.SerialPortEnabled():
  389 + success = self.serial_port_connection.SendPulse()
  390 + if success:
  391 + Publisher.sendMessage('Pulse triggered', state=True)
  392 +
374 393 def StartNavigation(self, tracker):
375 394 tracker_fiducials, tracker_fiducials_raw = tracker.GetTrackerFiducials()
376 395 ref_mode_id = tracker.GetReferenceMode()
... ... @@ -466,9 +485,13 @@ class Navigation():
466 485 jobs.start()
467 486 # del jobs
468 487  
  488 + self.pedal_connection.add_callback('navigation', self.PedalStateChanged)
  489 +
469 490 def StopNavigation(self):
470 491 self.event.set()
471 492  
  493 + self.pedal_connection.remove_callback('navigation')
  494 +
472 495 self.coord_queue.clear()
473 496 self.coord_queue.join()
474 497  
... ...