Note
Go to the end to download the full example code.
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.
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