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