Add an Output System#

This example demonstrates how to build and register a new output system in MyoGestic. An output system receives model predictions and routes them to an external destination – a visual interface, prosthetic hardware, game engine, or anything else that consumes EMG-decoded outputs.

Output systems are created per active visual interface when a trained model is loaded in the Online tab. Each output system is registered with a name that matches the short name of its target VI (e.g., "VHI", "KHI", "VCI").

Multi-VI Architecture#

MyoGestic supports multiple active visual interfaces simultaneously. When a model is loaded, the Online protocol creates output systems only for the VIs that are currently active:

# Inside OnlineProtocol._load_model():
active_vis = self._main_window.active_visual_interfaces  # dict[str, VisualInterface]
for vi_name, vi in active_vis.items():
    output_system = CONFIG_REGISTRY.output_systems_map[vi_name](
        self._main_window, model.is_classifier
    )

To access a specific VI from within your output system:

vi = self._main_window.active_visual_interfaces.get("VHI")

Required Methods#

Your output system must inherit from Output System Template and implement:

The base class automatically selects the right processing method based on prediction_is_classification and exposes it as process_prediction.

Add your output system in user_config

Keep custom output system registrations in user_config to stay separate from core MyoGestic code.

Step 1: Define Your Custom Output System#

from typing import Any
from myogestic.gui.widgets.templates.output_system import OutputSystemTemplate


class MyCustomOutputSystem(OutputSystemTemplate):
    """A simple example output system for demonstration.

    This class shows how to inherit from :class:`~myogestic.gui.widgets.templates.OutputSystemTemplate` and
    implement the required abstract methods.  It sends predictions to
    the application logger for demonstration purposes.
    """

    def __init__(self, main_window, prediction_is_classification: bool) -> None:
        super().__init__(main_window, prediction_is_classification)
        # Initialize sockets, hardware connections, timers, etc. here.
        #
        # To access the outgoing signal of a specific VI:
        #   vi = self._main_window.active_visual_interfaces.get("VHI")
        #   self._outgoing_signal = vi.outgoing_message_signal

    def _process_prediction__classification(self, prediction: Any) -> bytes:
        """Convert a classification label to bytes for transmission."""
        return f"Class: {prediction}".encode("utf-8")

    def _process_prediction__regression(self, prediction: Any) -> bytes:
        """Convert regression values to bytes for transmission."""
        return str([float(x) for x in prediction]).encode("utf-8")

    def send_prediction(self, prediction: Any) -> None:
        """Send the processed prediction to the output destination."""
        processed = self.process_prediction(prediction)
        # Replace with your actual send logic (UDP, serial, etc.)
        self._main_window.logger.print(f"Sending: {processed}")

    def close_event(self, event) -> None:
        """Clean up resources (sockets, timers, etc.)."""
        pass

Step 2: Register the Output System in CONFIG_REGISTRY#

The name must match the short name of the target VI. For a standalone output system (not tied to a specific VI), use any unique name – it will be instantiated when a model is loaded.

from myogestic.utils.config import CONFIG_REGISTRY

CONFIG_REGISTRY.register_output_system(
    name="MyCustomOutputSystem", output_system=MyCustomOutputSystem
)

Reference: Virtual Hand Interface Output System#

The VHI output system demonstrates a real-world implementation that:

  • Validates that the VHI is among the active visual interfaces.

  • Maps classification labels (0-8) to predefined hand poses.

  • Converts regression predictions to a float list string.

  • Sends the processed prediction via the VI’s outgoing UDP signal.

VHI Output System – full implementation#
 1from typing import Any
 2
 3from myogestic.gui.widgets.logger import LoggerLevel
 4from myogestic.gui.widgets.templates.output_system import OutputSystemTemplate
 5from myogestic.gui.widgets.visual_interfaces.virtual_hand_interface.setup_interface import (
 6    VirtualHandInterface_SetupInterface,
 7)
 8
 9PREDICTION2INTERFACE_MAP = {
10    -1: "Rejected Sample",
11    0: "[0, 0, 0, 0, 0, 0, 0, 0, 0]",
12    1: "[0, 0, 1, 0, 0, 0, 0, 0, 0]",
13    2: "[1, 0, 0, 0, 0, 0, 0, 0, 0]",
14    3: "[0, 0, 0, 1, 0, 0, 0, 0, 0]",
15    4: "[0, 0, 0, 0, 1, 0, 0, 0, 0]",
16    5: "[0, 0, 0, 0, 0, 1, 0, 0, 0]",
17    6: "[0.67, 1, 1, 1, 1, 1, 0, 0, 0]",
18    7: "[0.45, 1, 0.6, 0, 0, 0, 0, 0, 0]",
19    8: "[0.55, 1, 0.65, 0.65, 0, 0, 0, 0, 0]",
20}
21
22
23class VirtualHandInterface_OutputSystem(OutputSystemTemplate):
24    """Output system for the Virtual Hand Interface.
25
26    Parameters
27    ----------
28    main_window : MainWindow
29        The main window object.
30    prediction_is_classification : bool
31        Whether the prediction is a classification or regression.
32    """
33
34    def __init__(self, main_window, prediction_is_classification: bool) -> None:
35        super().__init__(main_window, prediction_is_classification)
36
37        # Check if VHI is among the active VIs
38        active_vis = self._main_window.active_visual_interfaces
39        vi = active_vis.get("VHI")
40        
41        if vi is None:
42            self._main_window.logger.print(
43                "VHI (Virtual Hand Interface) is not active.", level=LoggerLevel.ERROR
44            )
45            raise ValueError("VHI (Virtual Hand Interface) is not active.")
46
47        if not isinstance(
48            vi.setup_interface_ui,
49            VirtualHandInterface_SetupInterface,
50        ):
51            raise ValueError(
52                "The visual interface must be the Virtual Hand Interface."
53                f"Got {type(vi)}."
54            )
55
56        self._outgoing_message_signal = vi.outgoing_message_signal
57
58    def _process_prediction__classification(self, prediction: Any) -> bytes:
59        """Process the prediction for classification."""
60        return PREDICTION2INTERFACE_MAP[prediction].encode("utf-8")
61
62    def _process_prediction__regression(self, prediction: Any) -> bytes:
63        """Process the prediction for regression."""
64        return str([float(x) for x in prediction]).encode("utf-8")
65
66    def send_prediction(self, prediction: Any) -> None:
67        """Send the prediction to the visual interface."""
68        processed = self.process_prediction(prediction)
69        self._main_window.logger.print(f"VHI sending: {processed[:50]}...")  # Debug
70        self._outgoing_message_signal.emit(processed)
71
72    def close_event(self, event):
73        """Close the output system."""
74        pass

Gallery generated by Sphinx-Gallery