Part 3: Output System#

The output system is the third component of a visual interface. It processes model predictions (classification or regression) and sends them to the external VI application.

Multi-VI Architecture#

MyoGestic supports multiple active visual interfaces simultaneously. When a model is loaded in the Online tab, the OnlineProtocol creates one output system per active VI:

active_vis = self._main_window.active_visual_interfaces  # dict[str, VI]
self._output_systems = {
    vi_name: CONFIG_REGISTRY.output_systems_map[vi_name](
        self._main_window, model.is_classifier
    )
    for vi_name in active_vis
    if vi_name in CONFIG_REGISTRY.output_systems_map
}

Each prediction is then sent to all active output systems:

for output_system in self._output_systems.values():
    output_system.send_prediction(prediction)

Accessing Your VI#

From within an output system, access the target VI via the active_visual_interfaces dict:

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

Required Methods#

See Output System Template for the full base class API. You must implement:

Classification Prediction Map#

For classifiers, you typically map integer labels to output strings. The VHI maps 10 gestures (labels 0-9, plus -1 for rejected samples) to 9-element float vectors representing finger joint positions:

PREDICTION2INTERFACE_MAP = {
    -1: "Rejected Sample",
     0: "[0, 0, 0, 0, 0, 0, 0, 0, 0]",       # rest
     1: "[0, 0, 1, 0, 0, 0, 0, 0, 0]",         # index
     2: "[1, 0, 0, 0, 0, 0, 0, 0, 0]",         # thumb
     ...
     9: "[0, 0, 1, 0, 0, 0, 0, 1, 0]",         # pointing
}

Step 1: Define Your Output System#

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

# Prediction label to output string mapping (for classifiers)
PREDICTION_MAP = {
    -1: "Rejected",
    0: "[0, 0, 0]",  # rest
    1: "[1, 0, 0]",  # action_a
    2: "[0, 1, 0]",  # action_b
}


class MyInterface_OutputSystem(OutputSystemTemplate):
    """Output system for a custom visual interface.

    Validates that the target VI is active, then routes predictions
    to it via the outgoing signal.
    """

    def __init__(self, main_window, prediction_is_classification: bool) -> None:
        super().__init__(main_window, prediction_is_classification)

        # Validate that our target VI is active
        vi = self._main_window.active_visual_interfaces.get("MYI")
        if vi is None:
            raise ValueError("MYI (My Interface) is not active.")

        # Grab the outgoing signal for sending predictions
        self._outgoing_signal = vi.outgoing_message_signal

    def _process_prediction__classification(self, prediction: Any) -> bytes:
        """Map a classification label to an output string."""
        return PREDICTION_MAP.get(prediction, "Unknown").encode("utf-8")

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

    def send_prediction(self, prediction: Any) -> None:
        """Send the processed prediction to the VI via UDP."""
        processed = self.process_prediction(prediction)
        self._outgoing_signal.emit(processed)

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

Step 2: Register the Output System#

from myogestic.utils.config import CONFIG_REGISTRY

CONFIG_REGISTRY.register_output_system(
    name="MYI", output_system=MyInterface_OutputSystem
)

Reference: VHI Output System#

The VHI output system validates that "VHI" is among the active visual interfaces, maps 10 classification labels to hand poses, and sends predictions via UDP.

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