Source code for myogestic.gui.widgets.templates.visual_interface

import pickle
from datetime import datetime
from abc import abstractmethod
from typing import Optional, Type

import numpy as np
from PySide6.QtCore import QObject, Signal, QByteArray, SignalInstance
from PySide6.QtGui import QCloseEvent
from PySide6.QtWidgets import QMessageBox, QMainWindow

from myogestic.gui.widgets.templates.meta_qobject import MetaQObjectABC
from myogestic.utils.constants import RECORDING_DIR_PATH


[docs] class SetupInterfaceTemplate(QObject, metaclass=MetaQObjectABC): """ Base class for the setup interface of a visual interface. This class contains the logic and the UI elements of the setup interface of a visual interface. Attributes ---------- _main_window : Optional[QObject] The _main_window widget of the visual interface. name : str The name of the visual interface. ui : object The UI layout of the setup interface. outgoing_message_signal : PySide6.SignalInstance The outgoing message signal of the visual interface. incoming_message_signal : PySide6.SignalInstance The incoming message signal of the visual interface. """ outgoing_message_signal = Signal(QByteArray) incoming_message_signal = Signal(np.ndarray)
[docs] def __init__(self, main_window, name: str = "SetupUI", ui: object = None): super().__init__() from myogestic.gui.myogestic import MyoGestic self._main_window: MyoGestic = main_window self.name = name if not ui: raise ValueError("The UI object must be provided.") self.ui = ui self.ui.setupUi(self._main_window) from myogestic.gui.protocols.record import RecordProtocol from myogestic.gui.protocols.training import TrainingProtocol from myogestic.gui.protocols.online import OnlineProtocol self._record_protocol: RecordProtocol = self._main_window.protocols[0] self._training_protocol: TrainingProtocol = self._main_window.protocols[1] self._online_protocol: OnlineProtocol = self._main_window.protocols[2]
[docs] @abstractmethod def initialize_ui_logic(self) -> None: """Initialize the logic of the UI elements.""" pass
[docs] @abstractmethod def start_interface(self) -> None: """Start the visual interface. This method should be called when the visual interface is started. .. tip:: It is generally a good idea to also start the connection to it here. """ pass
[docs] @abstractmethod def stop_interface(self) -> None: """Stop the visual interface. This method should be called when the visual interface is stopped. .. tip:: It is generally a good idea to also stop the connection to it here. """ pass
[docs] @abstractmethod def interface_was_killed(self) -> None: """Kill the visual interface. This method should be called when the visual interface is killed. .. tip:: It is generally a good idea to also kill the connection to it here. """ pass
[docs] @abstractmethod def close_event(self, event: QCloseEvent) -> None: """Close the interface and stop necessary processes.""" pass
[docs] def enable_ui(self): """Enable the UI elements. .. important:: This method assumes that the UI elements are in a `groupBox` widget. """ self.ui.groupBox.setEnabled(True)
[docs] def disable_ui(self): """Disable the UI elements. .. important:: This method assumes that the UI elements are in a `groupBox` widget. """ self.ui.groupBox.setEnabled(False)
[docs] def _log_error(self, message: str) -> None: """Log an error message.""" if self.parent: QMessageBox.critical(self.parent, "Error", message)
[docs] @abstractmethod def connect_custom_signals(self): """Connect custom signals to slots.""" pass
[docs] @abstractmethod def disconnect_custom_signals(self): """Disconnect custom signals from slots.""" pass
[docs] def get_custom_save_data(self) -> dict: """Get custom data to save. Returns ------- dict The custom data to save. If no custom data is available, an empty dictionary must be returned. """ return {}
[docs] @abstractmethod def clear_custom_signal_buffers(self): """Clear the buffers of the custom signals.""" pass
[docs] class RecordingInterfaceTemplate(QObject, metaclass=MetaQObjectABC): """ Base class for the recording interface of a visual interface. This class contains the logic and the UI elements of the recording interface of a visual interface. Attributes ---------- _main_window : Optional[QObject] The _main_window widget of the visual interface. name : str The name of the visual interface. ui : object The UI layout of the recording interface. incoming_message_signal : PySide6.QtCore.SignalInstance The incoming message signal of the visual interface. ground_truth__nr_of_recording_values : int The number of recording values the visual interface sends to MyoGestic. ground_truth__task_map : dict[str, int] The task map. The keys are the task names and the values are the task indices. """
[docs] def __init__( self, main_window, name: str = "RecordingUI", ui: object | None = None, incoming_message_signal: SignalInstance | None = None, ground_truth__nr_of_recording_values: int = -1, ground_truth__task_map: dict[str, int] | None = None, ): super().__init__() from myogestic.gui.myogestic import MyoGestic self._main_window: MyoGestic = main_window self.name = name if not ui: raise ValueError("The UI object must be provided.") self.ui = ui self.ui.setupUi(self._main_window) self._record_emg_progress__bar = self._main_window.ui.recordEMGProgressBar self._record_emg_progress__bar.setValue(0) if ground_truth__nr_of_recording_values == -1: raise ValueError("The number of recording values must be provided.") self.ground_truth__nr_of_recording_values = ground_truth__nr_of_recording_values if not ground_truth__task_map: raise ValueError("The task map must be provided.") self.ground_truth__task_map = ground_truth__task_map # check if groundTruthProgressBar is in the UI if hasattr(self.ui, "groundTruthProgressBar"): self.ground_truth_recording_time = 0 self.record_ground_truth_progress_bar = self.ui.groundTruthProgressBar self.record_ground_truth_progress_bar.setValue(0) else: raise ValueError( "A UI element named 'groundTruthProgressBar' must be provided in the ui file!" ) if not incoming_message_signal: raise ValueError("The incoming message signal must be provided.") self.incoming_message_signal = incoming_message_signal
[docs] def save_recording( self, biosignal: np.ndarray, biosignal_timings: np.ndarray, ground_truth: np.ndarray, ground_truth_timings: np.ndarray, record_duration: int | float, use_as_classification: bool, recording_label: str, task: str, ground_truth_sampling_frequency: int | float, **kwargs, ) -> None: """Save the recording. Parameters ---------- biosignal : numpy.ndarray The recorded biosignal data. biosignal_timings : numpy.ndarray The recorded biosignal timings. ground_truth : numpy.ndarray The recorded ground truth data. ground_truth_timings : numpy.ndarray The recorded ground truth timings. record_duration : int | float The duration of the recording in seconds. use_as_classification : bool Whether to use the recording as classification data. recording_label : str The label of the recording. task : str The task of the recording. ground_truth_sampling_frequency : int | float The sampling frequency of the ground truth data. kwargs : dict Additional custom data to save. """ save_pickle_dict = { "biosignal": biosignal, "biosignal_timings": biosignal_timings, "ground_truth": ground_truth, "ground_truth_timings": ground_truth_timings, "recording_label": recording_label, "task": task, "ground_truth_sampling_frequency": ground_truth_sampling_frequency, "device_information": self._main_window.device__widget.get_device_information(), "bad_channels": self._main_window.current_bad_channels__list, "recording_time": record_duration, "use_as_classification": use_as_classification, "visual_interface": self._main_window.selected_visual_interface.name, } save_pickle_dict.update(kwargs) file_name = f"{save_pickle_dict['visual_interface']}_Recording_{datetime.now().strftime('%Y%m%d_%H%M%S%f')}_{task.lower()}_{recording_label.lower()}.pkl" with (RECORDING_DIR_PATH / file_name).open("wb") as f: pickle.dump(save_pickle_dict, f)
[docs] @abstractmethod def initialize_ui_logic(self) -> None: """Initialize the logic of the UI elements.""" pass
[docs] @abstractmethod def enable(self) -> None: """Enable all UI elements.""" pass
[docs] @abstractmethod def disable(self) -> None: """Disable all UI elements.""" pass
[docs] @abstractmethod def close_event(self, event: QCloseEvent) -> None: """Close the interface and stop necessary processes.""" pass
[docs] @staticmethod def _set_progress_bar(progress_bar, value: int, total: int) -> None: """Set the value of a progress bar.""" progress_bar.setValue(min(value / total * 100, 100))
[docs] class VisualInterface(QObject): """ Base class for visual interfaces in the MyoGestic application. This class is the base class for visual interfaces in the MyoGestic application. Parameters ---------- main_window : QMainWindow The main window of the visual interface. name : str The name of the visual interface. Default is "VisualInterface". .. important:: The name is used to identify the visual interface in the application. It should be unique. setup_interface_ui : Type[SetupInterfaceTemplate] The setup interface of the visual interface. recording_interface_ui : Type[RecordingInterfaceTemplate] The recording interface of the visual interface. Attributes ---------- _main_window : QObject The main_window widget of the visual interface. setup_interface_ui : SetupInterfaceTemplate The setup interface of the visual interface. recording_interface_ui : RecordingInterfaceTemplate The recording interface of the visual interface. incoming_message_signal : PySide6.SignalInstance The incoming message signal of the visual interface. outgoing_message_signal : PySide6.SignalInstance The outgoing message signal of the visual interface. """
[docs] def __init__( self, main_window: QMainWindow, name: str = "VisualInterface", setup_interface_ui: Type[SetupInterfaceTemplate] = None, recording_interface_ui: Type[RecordingInterfaceTemplate] = None, ) -> None: super().__init__() self._main_window = main_window self.name = name if not setup_interface_ui: raise ValueError("The setup interface must be provided.") self.setup_interface_ui : SetupInterfaceTemplate = setup_interface_ui(main_window, name) try: self.incoming_message_signal = ( self.setup_interface_ui.incoming_message_signal ) self.outgoing_message_signal = ( self.setup_interface_ui.outgoing_message_signal ) except AttributeError: raise ValueError( "The setup interface must have incoming and outgoing message signals." ) if not recording_interface_ui: raise ValueError("The recording interface must be provided.") self.recording_interface_ui : RecordingInterfaceTemplate = recording_interface_ui( main_window, name, incoming_message_signal=self.incoming_message_signal, )
[docs] def enable_ui(self) -> None: """Enable all UI elements.""" if self.setup_interface_ui: self.setup_interface_ui.enable_ui() if self.recording_interface_ui: self.recording_interface_ui.enable()
[docs] def disable_ui(self) -> None: """Disable all UI elements.""" if self.setup_interface_ui: self.setup_interface_ui.disable_ui() if self.recording_interface_ui: self.recording_interface_ui.disable()
[docs] def close_event(self, event: QCloseEvent) -> None: """Close the interface and stop necessary processes.""" if self.setup_interface_ui: self.setup_interface_ui.close_event(event) if self.recording_interface_ui: self.recording_interface_ui.close_event(event)
[docs] def _log_error(self, message: str) -> None: """Log an error message.""" if self.parent: QMessageBox.critical(self.parent, "Error", message)