Bridges¶
A Bridge is a subprocess MyoGestic spawns alongside the app for heavy-data acquisition that doesn't fit the LSL pull model - typically a webcam decoder that writes frames straight to Zarr and publishes an LSL clock outlet so the rest of the app can align timestamps.
Bridges are registered via app.bridges(...). Registering does not spawn anything — you call start() when you want the subprocess up — and App.run() calls stop() on every registered bridge during cleanup.
Base class¶
Bridge
¶
A subprocess MyoGestic spawns alongside the app and tears down on exit.
The escape hatch for heavy-data acquisition that doesn't fit the LSL pull model - a webcam decoder that writes frames straight to Zarr, an ultrasound capture daemon, a custom script that owns its own buffer. The bridge subprocess runs whatever it wants; MyoGestic only cares that it stays alive and exits cleanly.
There is no IPC contract beyond "the subprocess exists, is alive, and stops on terminate". Data flows back into the app the way it already travels: the subprocess publishes an LSL outlet, or writes a Zarr file the app reads.
Registered via app.bridges(...). Registering does not spawn
anything - you call start when you want it up - and
App.run() calls stop on every registered bridge on cleanup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human label, used in log messages about this bridge. |
required |
command
|
list[str]
|
argv passed to |
required |
Examples:
>>> import sys
>>> from myogestic.bridges import Bridge
>>> bridge = Bridge("capture", [sys.executable, "capture.py"])
>>> bridge.start()
>>> bridge.stop()
status
property
¶
status: str
"running" or "stopped", read from the subprocess on every read.
Derived rather than assigned at each transition: a stored copy goes stale the moment the subprocess exits on its own.
Two values only. A signalled child carries a non-zero return code, so any
"code != 0 means crashed" rule would report every successful stop as
a failure. Whether an exit was clean is on process.returncode, which
stop leaves in place.
start
¶
Spawn the subprocess, unless one is already alive.
Idempotent: overwriting a live handle drops the only reference to a running process, and the next call then stacks a second one on top of it.
stop
¶
Terminate the subprocess - SIGTERM, then SIGKILL if it ignores that.
Returns while still holding the handle if neither signal lands: a process
parked in an uninterruptible kernel wait ignores SIGKILL too. alive and
status then keep saying it is running and start refuses. Call this
again to retry.
SIGTERM first, so a bridge script's own handler and cleanup get to run.
Built-in bridges¶
WebCamBridge
¶
Bases: Bridge
Bridge that runs the built-in webcam decoder subprocess.
Wraps python -m myogestic.bridges.webcam (the main below):
captures frames from an OpenCV device, writes them to a Zarr array, and
publishes the per-frame LSL clock so the rest of the app can align webcam
time with EMG time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Bridge label. The published LSL clock outlet is named
|
required |
device
|
int
|
OpenCV device index. |
0
|
zarr_path
|
str
|
Where to write the frame array. Created if missing. |
'session/cam.zarr'
|
Examples:
>>> from myogestic.bridges import WebCamBridge
>>> camera = WebCamBridge("cam", device=0, zarr_path="session/cam.zarr")
>>> camera.start()
>>> camera.stop()
CustomBridge
¶
Bases: Bridge
Bridge that runs an arbitrary user Python script as a subprocess.
The script runs with the same Python interpreter as the app
(sys.executable); the rest is up to you (publish LSL, write
Zarr, talk to a custom message bus, ...). For the structured
alternative see
WebCamBridge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Bridge label. |
required |
script
|
str
|
Path to the Python script to spawn (e.g.
|
required |
Examples:
>>> from myogestic.bridges import CustomBridge
>>> bridge = CustomBridge("ultrasound", "capture/ultrasound.py")
>>> bridge.start()
>>> bridge.stop()
The webcam runner¶
WebCamBridge invokes python -m myogestic.bridges.webcam as a subprocess. The same runner can be launched directly for testing:
uv run python -m myogestic.bridges.webcam --device 0 --zarr session/cam.zarr --lsl-name webcam_clock
Flags:
--device N- OpenCV device index (default0).--zarr PATH- where to write the Zarr array. Frames are appended one chunk per capture.--lsl-name NAME- LSL outlet name for the per-frame timestamp clock; the app subscribes to this to align webcam frames with EMG.