From 9d79c88d21c6b09ec296a4ceab1e4a4d12ab1c78 Mon Sep 17 00:00:00 2001 From: okahilak <49641103+okahilak@users.noreply.github.com> Date: Wed, 28 Jul 2021 17:18:32 +0300 Subject: [PATCH] ADD: Optional use of trigger pedal (#297) --- app.py | 9 +++++++++ invesalius/gui/task_navigator.py | 41 ++++++++++++++++++++++++++++++++++++++--- invesalius/net/pedal_connection.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ optional-requirements.txt | 2 ++ 4 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 invesalius/net/pedal_connection.py create mode 100644 optional-requirements.txt diff --git a/app.py b/app.py index 4b4bb4f..1a6a559 100644 --- a/app.py +++ b/app.py @@ -342,6 +342,9 @@ def parse_comand_line(): dest="save_masks", default=True, help="Make InVesalius not export mask when exporting project.") + parser.add_option("--use-pedal", action="store_true", dest="use_pedal", + help="Use an external trigger pedal") + options, args = parser.parse_args() return options, args @@ -353,6 +356,12 @@ def use_cmd_optargs(options, args): session = ses.Session() session.debug = 1 + # If use-pedal argument... + if options.use_pedal: + from invesalius.net.pedal_connection import PedalConnection + + PedalConnection().start() + # If import DICOM argument... if options.dicom_dir: import_dir = options.dicom_dir diff --git a/invesalius/gui/task_navigator.py b/invesalius/gui/task_navigator.py index 271dedf..74096f9 100644 --- a/invesalius/gui/task_navigator.py +++ b/invesalius/gui/task_navigator.py @@ -64,6 +64,12 @@ import invesalius.gui.dialogs as dlg import invesalius.project as prj from invesalius import utils +HAS_PEDAL_CONNECTION = True +try: + from invesalius.net.pedal_connection import PedalConnection +except ImportError: + HAS_PEDAL_CONNECTION = False + BTN_NEW = wx.NewId() BTN_IMPORT_LOCAL = wx.NewId() @@ -308,6 +314,7 @@ class NeuronavigationPanel(wx.Panel): self.__bind_events() # Initialize global variables + self.pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None self.fiducials = np.full([6, 3], np.nan) self.fiducials_raw = np.zeros((6, 6)) self.correg = None @@ -394,6 +401,11 @@ class NeuronavigationPanel(wx.Panel): txt_fre = wx.StaticText(self, -1, _('FRE:')) txt_icp = wx.StaticText(self, -1, _('Refine:')) + if HAS_PEDAL_CONNECTION and self.pedal_connection.in_use: + txt_pedal_pressed = wx.StaticText(self, -1, _('Pedal pressed:')) + else: + txt_pedal_pressed = None + # Fiducial registration error text box tooltip = wx.ToolTip(_("Fiducial registration error")) txtctrl_fre = wx.TextCtrl(self, value="", size=wx.Size(60, -1), style=wx.TE_CENTRE) @@ -417,6 +429,23 @@ class NeuronavigationPanel(wx.Panel): checkicp.SetToolTip(tooltip) self.checkicp = checkicp + # An indicator for pedal trigger + if HAS_PEDAL_CONNECTION and self.pedal_connection.in_use: + tooltip = wx.ToolTip(_(u"Is the pedal pressed")) + checkbox_pedal_pressed = wx.CheckBox(self, -1, _(' ')) + checkbox_pedal_pressed.SetValue(False) + checkbox_pedal_pressed.Enable(False) + checkbox_pedal_pressed.SetToolTip(tooltip) + + def handle_pedal_value_changed(value): + checkbox_pedal_pressed.SetValue(value) + + self.pedal_connection.set_callback(handle_pedal_value_changed) + + self.checkbox_pedal_pressed = checkbox_pedal_pressed + else: + self.checkbox_pedal_pressed = None + # Image and tracker coordinates number controls for m in range(len(self.btns_coord)): for n in range(3): @@ -442,9 +471,14 @@ class NeuronavigationPanel(wx.Panel): (txtctrl_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), (btn_nav, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), (txt_icp, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), - (checkicp, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) + (checkicp, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)]) + + pedal_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5) + if HAS_PEDAL_CONNECTION and self.pedal_connection.in_use: + pedal_sizer.AddMany([(txt_pedal_pressed, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), + (checkbox_pedal_pressed, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) - group_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5) + group_sizer = wx.FlexGridSizer(rows=10, cols=1, hgap=5, vgap=5) group_sizer.AddGrowableCol(0, 1) group_sizer.AddGrowableRow(0, 1) group_sizer.AddGrowableRow(1, 1) @@ -452,7 +486,8 @@ class NeuronavigationPanel(wx.Panel): group_sizer.SetFlexibleDirection(wx.BOTH) group_sizer.AddMany([(choice_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL), (coord_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL), - (nav_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)]) + (nav_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL), + (pedal_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)]) main_sizer = wx.BoxSizer(wx.HORIZONTAL) main_sizer.Add(group_sizer, 1)# wx.ALIGN_CENTER_HORIZONTAL, 10) diff --git a/invesalius/net/pedal_connection.py b/invesalius/net/pedal_connection.py new file mode 100644 index 0000000..8cd172c --- /dev/null +++ b/invesalius/net/pedal_connection.py @@ -0,0 +1,95 @@ +#-------------------------------------------------------------------------- +# 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 time +from threading import Thread + +import mido + +from invesalius.utils import Singleton + +class PedalConnection(Thread, metaclass=Singleton): + """ + Connect to the trigger pedal via MIDI, and allow setting a callback for the pedal + being pressed or released. + + Started by calling PedalConnection().start() + """ + def __init__(self): + Thread.__init__(self) + self.daemon = True + + self.in_use = False + + self._midi_in = None + self._active_inputs = None + self._callback = None + + def _midi_to_pedal(self, msg): + # TODO: At this stage, interpret all note_on messages as the pedal being pressed, + # and note_off messages as the pedal being released. Later, use the correct + # message types and be more stringent about the messages. + # + if msg.type == 'note_on': + if self._callback is None: + print("Pedal pressed, no callback registered") + else: + self._callback(True) + + elif msg.type == 'note_off': + if self._callback is None: + print("Pedal released, no callback registered") + else: + self._callback(False) + + else: + print("Unknown message type received from MIDI device") + + def _connect_if_disconnected(self): + if self._midi_in is None and len(self._midi_inputs) > 0: + self._active_input = self._midi_inputs[0] + self._midi_in = mido.open_input(self._active_input) + self._midi_in._rt.ignore_types(False, False, False) + self._midi_in.callback = self._midi_to_pedal + + print("Connected to MIDI device") + + def _check_disconnected(self): + if self._midi_in is not None: + if self._active_input not in self._midi_inputs: + self._midi_in = None + + print("Disconnected from MIDI device") + + def _update_midi_inputs(self): + self._midi_inputs = mido.get_input_names() + + def is_connected(self): + return self._midi_in is not None + + def set_callback(self, callback): + self._callback = callback + + def run(self): + self.in_use = True + while True: + self._update_midi_inputs() + self._check_disconnected() + self._connect_if_disconnected() + time.sleep(1.0) diff --git a/optional-requirements.txt b/optional-requirements.txt new file mode 100644 index 0000000..e4c8931 --- /dev/null +++ b/optional-requirements.txt @@ -0,0 +1,2 @@ +mido==1.2.10 +python-rtmidi==1.4.9 -- libgit2 0.21.2