Architecture¶
MyoGestic is built around three orthogonal concerns: acquisition (sources feeding ring buffers), decision (the predict thread), and rendering (the main thread drawing widgets). Outputs are a fourth, owned by user code.
flowchart TB
subgraph SRC["Sources - one daemon acquisition thread each"]
direction LR
S1[LSLSource]
S2[ReplaySource]
S3[SerialSource]
S5[user Source]
end
SRC -->|sample-major chunks<br/>+ LSL clock timestamps| CTX
subgraph CTX["Context (shared state)"]
direction TB
CS["streams: dict[str, Stream]<br/>(dvg-ringbuffer + Lock)"]
ST["state: 'idle' | 'recording'<br/>'training' | 'predicting'"]
SE["session: Session | None"]
end
CTX -->|get_window<br/>channels-first| PT
CTX -->|get_display<br/>min/max envelope| RT
subgraph THREADS["Threads consuming the buffer"]
direction LR
PT["Predict thread @ predict_hz<br/>extract → predict<br/>writes pipeline.predictions"]
RT["Render thread (main)<br/>Dear ImGui + ImPlot<br/>draws widgets"]
OUT["Output threads<br/>LSLOutlet / UDPOutput / SerialOutput<br/>each @ its own hz"]
end
PT -.->|push| OUT
Data flow¶
A typical tick of the system:
- Acquisition thread for stream
"emg"reads a chunk fromLSLSource, appends to the ring buffer, refreshes the display snapshot, and (ifctx.sessionis non-None) appends to the active Zarr array. - Predict thread, once per
1/predict_hz, pulls a window viaStream.get_window()(channels-first), forwards it to@pipeline.extract, then to@pipeline.predict(model, features). The returneddict[str, Any]is stored inpipeline.predictions. - Render thread (main) draws widgets from
ctx.signal_viewercallsStream.get_display(n_pixels)for a min/max envelope. The pose-output filter (FilterControl) renders its panel. - Output thread for
LSLOutletchecks its atomic latest-value slot every1/hz, sends if changed.
Every box runs on its own daemon thread. The shared Context is the only synchronisation surface.
Module map¶
| Module | Responsibility |
|---|---|
myogestic.core |
App, Context, lifecycle hooks, run loops |
myogestic.stream |
Stream, ring buffer, acquisition thread, display snapshots |
myogestic.sources |
LSLSource, ReplaySource, SerialSource |
myogestic.outputs |
Output base + LSLOutlet, UDPOutput, SerialOutput |
myogestic.session |
Recording, label tracks, .session.zip, window iterators |
myogestic.ml |
Pipeline, train/predict lifecycle, ML widgets |
myogestic.models |
CatBoost / scikit-learn constructor recipes + persistence helpers |
myogestic.widgets |
Stateless ImGui function widgets |
myogestic.filters |
OneEuro / Gaussian / Identity output smoothers |
myogestic.bridges |
Subprocess pattern for heavy-data sources (webcam, ultrasound) |
Public API boundary¶
Keep these import paths stable. The internal modules (myogestic.core, myogestic.stream) are subject to change; user code should import from the package root or named subpackages:
from myogestic import App, Grid, Stream, TrainingData
from myogestic.sources import LSLSource, ReplaySource
from myogestic.outputs import LSLOutlet, UDPOutput
from myogestic.ml import Pipeline
from myogestic.session import open_session_store, iter_labeled_windows
from myogestic.widgets import signal_viewer, recording_controls, session_manager
See Public API cheatsheet for the full surface.