### Run Dockable Layout Reference Example Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md This example showcases a dockable UI layout using `App(docking=True)`. All panels, including the Prediction and training log, can be torn off into separate pop-out windows, serving as a reference for multi-monitor setups. ```bash uv run python examples/synthetic/emg_popout_layout.py ``` -------------------------------- ### Install MyoGestic Dependencies Source: https://github.com/nsquaredlab/myogestic/blob/main/README.md Installs core dependencies or includes extras for examples, development, or documentation. Use `uv sync` with appropriate flags like `--extra examples`, `--extra dev`, or `--extra docs`. ```bash uv sync uv sync --extra examples uv sync --extra dev ``` -------------------------------- ### Initialize and Run MyoGestic App Source: https://context7.com/nsquaredlab/myogestic/llms.txt Basic setup for a MyoGestic application. Register streams and define a UI callback. The acquisition threads start when app.run() is called. ```python from myogestic import App, Stream from myogestic.sources import LSLSource from myogestic.widgets import signal_viewer, recording_controls CLASSES = ["Rest", "Fist"] app = App( "My EMG Experiment", theme=True, # apply built-in ImGui theme docking=False, # set True for tear-off dockable panels ui_scale=0.9, # or set $MYOGESTIC_UI_SCALE env var ) # Register streams — acquisition threads start at app.run(), not here app.streams( Stream("emg", source=LSLSource("TestEMG1"), window_seconds=0.2, buffer_seconds=60) ) @app.ui def my_ui(ctx): signal_viewer(ctx, "emg") recording_controls( ctx, CLASSES, on_record=app.start_recording, on_stop=app.stop_recording, ) app.run(window_size=(1280, 800)) # app.ctx.state is "idle" | "recording" (| "training" | "predicting" with Pipeline) ``` -------------------------------- ### Quick Start: Hello EMG App Source: https://github.com/nsquaredlab/myogestic/blob/main/README.md A minimal MyoGestic application demonstrating basic setup with LSL source, signal viewer, and recording controls. This forms the core loop for a live experiment. ```python from myogestic import App, Stream from myogestic.sources import LSLSource from myogestic.widgets import recording_controls, signal_viewer app = App("Hello EMG") app.streams(Stream("emg", source=LSLSource("EMG"), window_seconds=1.0)) @app.ui def ui(ctx): signal_viewer(ctx, "emg") recording_controls(ctx, ["Rest", "Fist"], on_record=app.start_recording, on_stop=app.stop_recording) app.run() ``` -------------------------------- ### Complete MyoGestic App Structure Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/anatomy.md This is a full example of a MyoGestic application, demonstrating the setup of streams, an ML pipeline with custom extraction, training, and prediction functions, and the UI components. It requires `myogestic`, `sklearn`, and `numpy` to be installed. ```python from myogestic import App, Stream, TrainingData from myogestic.ml import Pipeline, save_pickle, load_pickle from myogestic.ml.widgets import pipeline_panel from myogestic.session import iter_labeled_windows from myogestic.sources import LSLSource from myogestic.widgets import recording_controls, session_manager, signal_viewer from sklearn.linear_model import LogisticRegression import numpy as np app = App("EMG Demo") app.streams(Stream("emg", source=LSLSource("EMG"), window_seconds=0.2)) pipeline = Pipeline(app, predict_hz=20) pipeline.save_model = save_pickle pipeline.load_model = load_pickle @pipeline.extract def extract(windows): return windows["emg"].mean(axis=1) @pipeline.train def train(data: TrainingData): X, y = [], [] for sw, _ts, c in iter_labeled_windows(data.paths, "emg", 0.2, 0.1, classes=data.classes): X.append(extract({"emg": sw.data})) y.append(c) return LogisticRegression().fit(np.array(X), np.array(y)) @pipeline.predict def predict(model, features): return {"class": int(model.predict(features.reshape(1, -1))[0])} @app.ui def ui(ctx): signal_viewer(ctx, "emg") recording_controls( ctx, ["Rest", "Fist"], on_record=app.start_recording, on_stop=app.stop_recording ) pipeline.training_data = session_manager("sessions", class_names=["Rest", "Fist"]) pipeline_panel(pipeline) app.run() ``` -------------------------------- ### Basic MyoGestic App Setup Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/index.md This snippet demonstrates the fundamental structure of a MyoGestic application. It initializes the app, defines a data stream from an LSL source, and sets up a user interface with a signal viewer and recording controls. Use this as a starting point for your own biosignal experiments. ```python from myogestic import App, Stream from myogestic.sources import LSLSource from myogestic.widgets import recording_controls, signal_viewer app = App("Hello EMG") app.streams(Stream("emg", source=LSLSource("EMG"), window_seconds=1.0)) @app.ui def ui(ctx): signal_viewer(ctx, "emg") recording_controls( ctx, ["Rest", "Fist"], on_record=app.start_recording, on_stop=app.stop_recording ) app.run() ``` -------------------------------- ### Custom GUI Integration for Recording Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/enable-recording.md Integrate recording controls into a custom GUI using imgui-bundle. This example shows how to start and stop recording based on button clicks and display the current recording path. ```python from imgui_bundle import imgui from mne_lsl.lsl import local_clock @app.ui def ui(ctx): if ctx.state == "idle": if imgui.button("Start trial"): app.start_recording(base_path="experiments/trial5") # Optional: stamp an initial label so first samples are # labeled before any user input. app.ctx.session.add_label(class_index=0, timestamp=local_clock()) elif ctx.state == "recording": imgui.text(f"Recording: {app.ctx.session.path}") if imgui.button("Stop trial"): app.stop_recording() ``` -------------------------------- ### Run Core Demos with uv sync Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md Use `uv sync --extra examples` to download and prepare the core demonstration scripts. For gRPC-enabled examples, include `--extra grpc`. ```bash uv sync --extra examples # core demos uv sync --extra examples --extra grpc # adds the gRPC-control examples ``` -------------------------------- ### Initialize Virtual Hand Interface Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/integrate-vhi.md Instantiate the VHI interface, which resolves installation paths and gRPC endpoints. This setup is boilerplate-free due to the InterfaceSpec knowing all necessary details. ```python from myogestic.interfaces import virtual_hand vhi = virtual_hand() # resolves install path + gRPC endpoint vhi_outlet = vhi.outlet() # 9-ch LSL outlet @ 32 Hz vhi_client = vhi.control_client() # fire-and-forget gRPC client ``` -------------------------------- ### Development Commands Source: https://github.com/nsquaredlab/myogestic/blob/main/README.md Installs development dependencies and runs tests or code checks. Use `uv sync --extra dev` for setup, then `uv run pytest` for tests and `uv run ruff` for linting. ```bash uv sync --extra dev uv run pytest -q uv run ruff check . ``` -------------------------------- ### Build and Serve Local Documentation Source: https://github.com/nsquaredlab/myogestic/blob/main/README.md Installs documentation and gRPC dependencies, then serves the local ProperDocs site. Access the documentation at http://127.0.0.1:8000/MyoGestic/. ```bash uv sync --extra docs --extra grpc --extra serial uv run properdocs serve ``` -------------------------------- ### Install MyoGestic with uv Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/getting-started.md Clone the repository and sync dependencies using uv. Use the `--extra` flag to install additional packages for specific functionalities like running demos. ```bash git clone https://github.com/NsquaredLab/MyoGestic.git cd MyoGestic uv sync ``` ```bash uv sync --extra examples ``` -------------------------------- ### Install Virtual Hand Interface (VHI) Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/troubleshooting.md Use these commands to install the VHI packaged binary. The first command installs the latest release, while the second is available after installing the myogestic package. ```bash python -m myogestic.tools.install_vhi # latest release ``` ```bash myogestic-install-vhi ``` -------------------------------- ### Run a Specific Example with uv run Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md Execute a specific MyoGestic example script using `uv run python examples/synthetic/.py`. ```bash uv run python examples/synthetic/.py ``` -------------------------------- ### Worked Example: ROS Pose Publisher Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/add-an-output.md This example demonstrates how to create a custom output that publishes a 9-DoF pose as a ROS Float32MultiArray. It includes a lazy import for `rclpy` to keep ROS optional. ```python import numpy as np from myogestic.outputs import Output class ROSPoseOutput(Output): """Publish a 9-DoF pose as a ROS Float32MultiArray.""" def __init__(self, topic: str, hz: float = 50.0): super().__init__(hz=hz) import rclpy # lazy import - keeps ROS optional from rclpy.node import Node from std_msgs.msg import Float32MultiArray rclpy.init(args=None) self._node = Node("myogestic_pose") self._pub = self._node.create_publisher(Float32MultiArray, topic, 10) self._Float32MultiArray = Float32MultiArray def _send(self, data: np.ndarray) -> None: msg = self._Float32MultiArray() msg.data = data.astype(np.float32).tolist() self._pub.publish(msg) ``` -------------------------------- ### Install MyoGestic Wheel Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/playground/index.html Fetches the MyoGestic wheel URL from a manifest and installs it, ensuring that dependencies are not re-installed. ```javascript const manifest = await (await fetch( MYOGESTIC_MANIFEST + "?ts=" + Date.now() )).json(); const wheelUrl = "./wheels/" + manifest.wheel + "?ts=" + Date.now(); await micropip.install.callKwargs(wheelUrl, { deps: false }); ``` -------------------------------- ### Install Zarrs Extra for Speedup Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/enable-recording.md Install the Rust-accelerated Zarrs extra for faster reading and writing of large recording sessions. No code changes are required after installation. ```bash uv sync --extra zarrs ``` -------------------------------- ### Run Pipeline in Headless Mode Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/headless-mode.md Load a pre-trained model and start prediction in headless mode. This setup is ideal for driving external systems like robots or LSL consumers. ```python from myogestic import App, Stream from myogestic.ml import Pipeline, load_pickle from myogestic.outputs import LSLOutlet from myogestic.sources import LSLSource app = App("Headless predictor") app.streams(Stream("emg", source=LSLSource("EMG"), window_seconds=0.2)) out = LSLOutlet("Predictions", n_channels=1, hz=20) pipeline = Pipeline(app, predict_hz=20) pipeline.model = load_pickle("models/my_model.pkl") @pipeline.extract def extract(windows): return windows["emg"].mean(axis=1) @pipeline.predict def predict(model, features): cls = int(model.predict(features.reshape(1, -1))[0]) out.push([float(cls)]) return {"class": cls} # Skip the train decorator; we loaded a model, not training one. pipeline.start_predicting() app.run(mode="headless") ``` -------------------------------- ### Quick-start: Enable Recording with GUI Controls Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/enable-recording.md This snippet demonstrates the minimal code required to set up an application, register a stream, and integrate recording controls into the user interface. It enables users to start and stop recording via GUI buttons. ```python from myogestic import App, Stream from myogestic.sources import LSLSource from myogestic.widgets import recording_controls app = App("My recording") # 1. construct App app.streams(Stream("emg", source=LSLSource("EMG"), window_seconds=1.0)) # 2. register stream(s) @app.ui def ui(ctx): recording_controls(ctx, ["Rest", "Fist"], on_record=app.start_recording, # 3. wire Record button on_stop=app.stop_recording) # 4. wire Stop button app.run() ``` -------------------------------- ### Install MyoGestic and Dependencies Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/playground/index.html Installs necessary Python packages for MyoGestic, including imgui-bundle and pure-Python dependencies, followed by browser shims. ```javascript setStatus("Calling imgui-bundle (~5 MB)..."); await micropip.install(IMGUI_BUNDLE_WHL); setStatus("Installing pure-Python deps..."); await micropip.install(PURE_PY_DEPS); setStatus("Writing browser shims (mne_lsl, tsdownsample)..."); await installShims(pyodide); ``` -------------------------------- ### Run EMG Regression Example Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md This example uses a CatBoost regressor to map EMG features to a 5-DoF kinematic target. It's configured with `cycle=False` for VHI to hold target end poses, suitable for training where physical holding is required. ```bash uv run python examples/synthetic/emg_regression.py ``` -------------------------------- ### Trigger Recording Programmatically Source: https://context7.com/nsquaredlab/myogestic/llms.txt This snippet shows how to programmatically start recording using `before_run_hooks`. Ensure streams are connected before starting the recording. ```python def _start(a): time.sleep(0.5) # wait for streams to connect a.start_recording("sessions_headless") app.before_run_hooks.append(_start) try: app.run(mode="headless") # blocks until Ctrl-C except KeyboardInterrupt: app.stop_recording() ``` -------------------------------- ### Headless Mode Application Setup Source: https://context7.com/nsquaredlab/myogestic/llms.txt Configures and runs the Myogestic application in headless mode using a ReplaySource for offline processing. ```python import time import numpy as np from myogestic import App, Stream from myogestic.ml import Pipeline from myogestic.sources import ReplaySource app = App("Headless") app.streams( Stream( "emg", source=ReplaySource("sessions/20240601_120000.session.zip", "emg", speed=2.0), window_seconds=0.2, ) ) pipeline = Pipeline(app) @pipeline.extract def extract(windows): from myogestic.contrib.features import rms return rms(windows["emg"]) # No @pipeline.predict needed for headless recording replay # No @app.ui needed for headless mode app.run(mode="headless") ``` -------------------------------- ### Run 32-Channel EMG Multi-Model Comparison Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md This example allows for live comparison of multiple classifiers (e.g., CatBoost, LDA, SVM) with 32-channel EMG data. It also includes features for saving/loading models and mapping gestures to poses. ```bash uv run python examples/synthetic/emg_32ch_multi_model.py ``` -------------------------------- ### Worked Example: Bluetooth Haptic Actuator Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/add-an-output.md This example demonstrates creating a custom output for a Bluetooth haptic actuator. It converts a 3-vector of intensities into a byte payload for BLE write operations. ```python class HapticOutput(Output): def __init__(self, ble_client, characteristic_uuid: str, hz: float = 30.0): super().__init__(hz=hz) self._ble = ble_client self._uuid = characteristic_uuid def _send(self, data: np.ndarray) -> None: # data: 3-vec of intensities in [0, 1] → 3 bytes payload = (np.clip(data, 0, 1) * 255).astype(np.uint8).tobytes() self._ble.write(self._uuid, payload, response=False) ``` -------------------------------- ### Install VHI using myogestic tools Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/install-vhi.md Use this command to download and install the latest VHI release for your operating system and architecture. It verifies the download and places it in the correct location. ```bash python -m myogestic.tools.install_vhi ``` ```bash myogestic-install-vhi ``` -------------------------------- ### Data Source Initialization Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/reference/api-cheatsheet.md Configure and initialize different types of data sources for the application. Ensure necessary extras like `[serial]` are installed if using `SerialSource`. ```python LSLSource(stream_name) ``` ```python ReplaySource(session_path, stream_name, speed=1.0) # accepts .session.zip ``` ```python SerialSource(port, baud, n_channels, fs) # extras: [serial] ``` -------------------------------- ### Session Recording and Iterators Source: https://context7.com/nsquaredlab/myogestic/llms.txt Demonstrates starting and stopping session recordings using `app.start_recording` and `app.stop_recording`. Shows how to reconstruct training data using `iter_labeled_windows` for classification and `iter_aligned_windows` for regression. ```python import numpy as np from myogestic.session import iter_labeled_windows, iter_aligned_windows, open_session_store SESSION_PATHS = [ "sessions/20240601_120000.session.zip", "sessions/20240601_130000.session.zip", ] ``` -------------------------------- ### Reading Sessions Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/enable-recording.md Provides examples of how to open and read data from recorded sessions using `open_session_store` and iterate through labeled or aligned windows. ```APIDOC ## Reading sessions back ```python from myogestic.session import open_session_store # Works for both packed zips AND unpacked folders. sess = open_session_store("sessions/2026-05-17_14-23-05.session.zip") emg = sess.stores["emg"] # zarr.Array, shape (n_samples, n_channels) emg_ts = sess.ts_stores["emg"] # zarr.Array, shape (n_samples,) float64 labels = sess.label_track # list[LabelEvent] info = sess.stream_info("emg") # StreamInfo(fs, n_channels, dtype) ``` For windowed training iteration, use the helpers in `myogestic.session`: ```python from myogestic.session import iter_labeled_windows, iter_aligned_windows # Classification: one (window, ts, class_index) per hop step. for window, ts, cls in iter_labeled_windows( [sess.path], stream_name="emg", win_seconds=0.2, hop_seconds=0.1, classes={0, 1}, ): ... # Regression: align a primary stream with one or more aligned streams. for window, aligned, ts in iter_aligned_windows( [sess.path], primary_stream="emg", aligned_streams=["vhi_control"], win_seconds=0.2, hop_seconds=0.05, ): target = aligned["vhi_control"] ... ``` These skip windows that straddle a label boundary and handle the window/hop math for you. See [Record and replay](record-and-replay.md) and the [Recording concept page](../concepts/recording.md). ``` -------------------------------- ### Registering a Widget with @app.ui Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/concepts/widgets.md Demonstrates how to register a widget function to be rendered every frame by decorating it with @app.ui. Includes an example of an interactive button. ```python @app.ui def ui(ctx): signal_viewer(ctx, "emg") # draws this frame if imgui.button("Click me"): print("clicked") # only true on the frame the click landed ``` -------------------------------- ### Launch VHI Process with Process Launcher Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/integrate-vhi.md Integrate VHI launching into a process launcher panel, providing a Start/Stop button for users. This example also includes launching an EMG generator. ```python import sys from myogestic.widgets import process_launcher PROCESSES = [ ("EMG Generator", [sys.executable, "-m", "myogestic.tools.emg_generator", "--name", "TestEMG1", "--channels", "8", "--fs", "2048"]), *vhi.launcher(), ] @app.ui def ui(ctx): with grid[0, 0]: process_launcher(PROCESSES) ``` -------------------------------- ### Cycle-Style Recording Example Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/record-good-training-data.md Demonstrates the recommended cycle-style recording pattern for capturing multiple activations of a gesture within a single session. This method provides sufficient data for classification models. ```text [Record] [Rest] 3 s "rest" [Fist] 3 s "first activation" [Rest] 3 s [Fist] 3 s "second activation" [Rest] 3 s [Fist] 3 s "third activation" [Rest] 3 s [Stop] ``` -------------------------------- ### Start and Stop Recording Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/concepts/recording.md Initiate and terminate data recording sessions. `start_recording` begins the process, and `stop_recording` finalizes it. Labels can be added during recording via a callback. ```python recording_controls( ctx, ["Rest", "Fist", "Open"], on_record=app.start_recording, on_stop=app.stop_recording, on_gesture=lambda idx: ctrl_outlet.push_sample([float(idx)]), ) ``` -------------------------------- ### Source-mode setup for VHI development Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/install-vhi.md When developing VHI itself, point `$VHI_PATH` to the Godot project checkout and set `$GODOT_BIN` to the Godot executable. This allows `virtual_hand()` to launch from source. ```bash export VHI_PATH=$HOME/code/Virtual-Hand-Interface export GODOT_BIN=/Applications/Godot.app/Contents/MacOS/Godot # macOS # or export GODOT_BIN=/usr/bin/godot4 # Linux uv run python examples/synthetic/emg_classification.py ``` -------------------------------- ### Setting Training Data from UI Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/concepts/pipeline.md Example of how to set the pipeline's training_data attribute, typically from a UI component like session_manager. ```python with grid[2, 0]: pipeline.training_data = session_manager(str(Path("sessions")), class_names=CLASSES) ``` -------------------------------- ### ML Pipeline Setup and Control Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/reference/api-cheatsheet.md Define and manage machine learning pipelines for feature extraction, training, and prediction. Model saving/loading can be enabled via buttons. ```python Pipeline(app, predict_hz=50) @pipeline.extract(windows: dict[str, np.ndarray]) # channels-first @pipeline.train(data: TrainingData) @pipeline.predict(model, features) -> dict .training_data: TrainingData | None .start_training() / .start_predicting() / .stop_predicting() .save_model / .load_model # set to enable buttons .model / .predictions / .train_log ``` ```python save_pickle(model, path) / load_pickle(path) ``` -------------------------------- ### CSV Replay Source Implementation Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/add-a-source.md A concrete example of a `Source` that replays data from a CSV file at its original sample rate. Useful for testing or replaying recorded data. ```python from pathlib import Path import numpy as np import time from mne_lsl.lsl import local_clock from myogestic import StreamInfo class CSVSource: def __init__(self, path: str, fs: float, channel_names: list[str] | None = None): self.path = Path(path) self.fs = fs self.channel_names = channel_names self._data: np.ndarray | None = None self._idx = 0 self._t0 = 0.0 def connect(self) -> StreamInfo: arr = np.loadtxt(self.path, delimiter=",", dtype=np.float32) if arr.ndim == 1: arr = arr[:, None] self._data = arr self._idx = 0 self._t0 = local_clock() return StreamInfo( n_channels=arr.shape[1], fs=self.fs, dtype=np.dtype("float32"), channel_names=self.channel_names, ) def read(self) -> tuple[np.ndarray | None, np.ndarray | None]: if self._data is None or self._idx >= self._data.shape[0]: return None, None # How many samples should have arrived by now (real-time pacing)? elapsed = local_clock() - self._t0 target = int(elapsed * self.fs) if target <= self._idx: return None, None end = min(target, self._data.shape[0]) chunk = self._data[self._idx : end] ts = (np.arange(self._idx, end) / self.fs + self._t0).astype(np.float64) self._idx = end return chunk, ts def disconnect(self) -> None: self._data = None ``` -------------------------------- ### Implement Custom Data Source with SineWave Source: https://context7.com/nsquaredlab/myogestic/llms.txt Create a custom data source by implementing the `connect()`, `read()`, and `disconnect()` methods. The `read()` method should return data in a `(n_samples, n_channels)` float32 format and LSL timestamps. This example generates a synthetic sine wave. ```python import numpy as np from myogestic import App, Stream from myogestic.stream import StreamInfo class SineSource: """Synthetic 8-channel 2048 Hz sine wave source.""" def __init__(self): self._t = 0.0 self._fs = 2048.0 self._n_ch = 8 self._chunk = 64 # samples per read call def connect(self) -> StreamInfo: return StreamInfo(n_channels=self._n_ch, fs=self._fs) def read(self): import time t = np.arange(self._chunk) / self._fs + self._t data = np.sin(2 * np.pi * 10 * t)[:, None] * np.ones(self._n_ch) ts = time.time() + t - t[0] self._t += self._chunk / self._fs return data.astype(np.float32), ts.astype(np.float64) def disconnect(self): self._t = 0.0 app = App("Custom Source") app.streams(Stream("sine", source=SineSource(), window_seconds=0.5)) @app.ui def ui(ctx): from myogestic.widgets import signal_viewer signal_viewer(ctx, "sine") app.run() ``` -------------------------------- ### ML Pipeline Setup and Callbacks Source: https://context7.com/nsquaredlab/myogestic/llms.txt Defines an ML pipeline with extract, train, and predict callbacks. Use `@pipeline.extract` for feature extraction, `@pipeline.train` for model training, and `@pipeline.predict` for inference. The `pipeline.predictions` dict is read-only from the UI thread. ```python import numpy as np from myogestic import App, Stream, TrainingData from myogestic.ml import Pipeline from myogestic.ml.widgets import pipeline_panel from myogestic.session import iter_labeled_windows from myogestic.sources import LSLSource from myogestic.widgets import recording_controls, session_manager, signal_viewer from myogestic.contrib.features import rms, mav CLASSES = ["Rest", "Fist"] WIN = 0.2 # seconds HOP = 0.1 app = App("Pipeline Demo") app.streams(Stream("emg", source=LSLSource("TestEMG1"), window_seconds=WIN, buffer_seconds=60)) pipeline = Pipeline(app, predict_hz=50) @pipeline.extract def extract(windows: dict[str, np.ndarray]) -> np.ndarray: # windows["emg"] is (n_channels, n_samples) emg = windows["emg"] return np.concatenate([rms(emg), mav(emg)]) # flat feature vector @pipeline.train def train(data: TrainingData): from sklearn.discriminant_analysis import LinearDiscriminantAnalysis X, y = [], [] for win, _ts, label in iter_labeled_windows(data.paths, "emg", WIN, HOP, classes=data.classes): X.append(extract({"emg": win})) y.append(label) clf = LinearDiscriminantAnalysis() clf.fit(np.stack(X), np.array(y)) return clf # stored as pipeline.model @pipeline.predict def predict(model, features): proba = model.predict_proba(features.reshape(1, -1))[0] return {"class": int(np.argmax(proba)), "proba": proba} @app.ui def ui(ctx): signal_viewer(ctx, "emg") recording_controls(ctx, CLASSES, on_record=app.start_recording, on_stop=app.stop_recording) pipeline.training_data = session_manager("sessions", class_names=CLASSES) pipeline_panel(pipeline) app.run() # After training and clicking Predict: # pipeline.predictions == {"class": 0, "proba": array([0.9, 0.1])} ``` -------------------------------- ### Run EMG Classification with gRPC Control Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md This example extends the EMG classification by incorporating a gRPC control plane using `VhiControlClient`. It demonstrates sending discrete gRPC events alongside continuous LSL pose data. ```bash uv run python examples/synthetic/emg_classification_grpc.py ``` -------------------------------- ### MyoGestic Pipeline Setup Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/anatomy.md Initialize a `Pipeline` object to manage the machine learning lifecycle within your MyoGestic app. You can configure prediction frequency and set custom model saving/loading functions. ```python pipeline = Pipeline(app, predict_hz=20) pipeline.save_model = save_pickle pipeline.load_model = load_pickle ``` -------------------------------- ### Run EMG Regression with RaulNet Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/examples-index.md This example utilizes RaulNetV17, a PyTorch Lightning CNN, for EMG regression. It processes a sliding-RMS feature stack to predict 5-DoF kinematics and demonstrates training with specific PyTorch Lightning configurations. ```bash uv run python examples/synthetic/emg_regression_raulnet.py ``` -------------------------------- ### Set up App, Stream, and Pipeline Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/tutorials/emg-classification.md Configure the main application, define the EMG data stream with its source and windowing parameters, and initialize the processing pipeline. ```python WIN_SECONDS = 0.2 HOP_SECONDS = 0.1 # 50% overlap app = App("EMG Classification") app.streams( Stream("emg", source=LSLSource("TestEMG1"), window_seconds=WIN_SECONDS, buffer_seconds=60) ) pipeline = Pipeline(app) ``` -------------------------------- ### Initialize Pyodide and Load Packages Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/playground/index.html Initializes Pyodide, loads the micropip package, and sets up the canvas for rendering. This is the foundational step for running Python in the browser. ```javascript const loaderEl = document.getElementById("loader"); const setStatus = m => loaderEl.textContent = m; const hideLoader = () => loaderEl.remove(); const showError = e => { loaderEl.style.background = "#5a1f1f"; setStatus("Error: " + (e?.message || e)); console.error(e); }; // imgui-bundle's canonical Pyodide wheel. CORS-permissive; pinned to // a known-good build so the playground always knows what it loaded. const IMGUI_BUNDLE_WHL = "https://imgui-bundle.pages.dev/local_wheels/" + "imgui_bundle-1.92.705-cp313-cp313-pyemscripten_2025_0_wasm32.whl"; // Locally built MyoGestic wheel (pure Python, no native ext). // CI builds it fresh each deploy and writes the actual filename // into wheels/manifest.json — we can't hardcode a stable name // because micropip parses wheel filenames as PEP 440 versions and // rejects anything that isn't a real version segment. The loader // resolves the manifest first and then asks micropip for the // versioned wheel pyproject just produced. const MYOGESTIC_MANIFEST = "./wheels/manifest.json"; // Pure-Python deps MyoGestic needs at runtime, plus scikit-learn for // the LDA classifier the playground app uses. micropip pulls these // from PyPI (or, for the ones Pyodide pre-builds, its package index). // MyoGestic itself is installed with deps=False afterwards because // its METADATA also lists mne-lsl / tsdownsample (native; shimmed // below) and pyobjc-framework-Cocoa (macOS-marker; harmless on // emscripten but micropip is conservative about markers). const PURE_PY_DEPS = [ "dvg-ringbuffer", "platformdirs", "zarr", "joblib", "numpy", // already bundled in Pyodide but ensures resolution "scikit-learn", // for the playground app's LDA classifier ]; // Shim modules written into Pyodide's FS so MyoGestic's lazy // `from mne_lsl.lsl import local_clock` and // `from tsdownsample import M4Downsampler` find browser-safe stubs. const SHIM_FILES = [ "shims/mne_lsl/__init__.py", "shims/mne_lsl/lsl.py", "shims/tsdownsample/__init__.py", ]; async function fetchText(url) { const r = await fetch(url); if (!r.ok) throw new Error(`fetch ${url} -> ${r.status}`); return await r.text(); } async function installShims(pyodide) { // site-packages root depends on the Pyodide Python version. // 0.29.x ships Python 3.13. Hardcoding rather than introspecting // keeps the loader trivial; bump if Pyodide moves. const SP = "/lib/python3.13/site-packages"; for (const rel of SHIM_FILES) { const text = await fetchText(rel); // mne_lsl/lsl.py -> /lib/python3.13/site-packages/mne_lsl/lsl.py const dest = SP + "/" + rel.replace(/^shims\//, ""); const dir = dest.substring(0, dest.lastIndexOf("/")); pyodide.FS.mkdirTree(dir); pyodide.FS.writeFile(dest, text); } } /* No special touch handling: keep the canvas viewport-sized (matching every other imgui-bundle Pyodide app), and let ImGui handle scrolling via its own scrollbar. The Python side bumps scrollbar_size on touch devices so the bar is wide enough to grab with a fingertip. */ async function main() { const canvas = document.getElementById("canvas"); canvas.addEventListener('contextmenu', e => e.preventDefault()); setStatus("Loading Pyodide..."); const pyodide = await loadPyodide(); pyodide.canvas.setCanvas2D(canvas); pyodide._api._skip_unwind_fatal_error = true; setStatus("Loading micropip..."); await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); setStatus("Inst ``` -------------------------------- ### Find VHI Installation Directory Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/install-vhi.md Use this Python command to determine the root directory where VHI was installed. ```bash # Find where VHI was installed: python -c "from myogestic.interfaces import _default_install_root; print(_default_install_root())" ``` -------------------------------- ### Initialize and Configure Pipeline Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/concepts/pipeline.md Initializes the Pipeline with an app instance and sets custom model saving and loading functions. ```python from myogestic.ml import Pipeline, save_pickle, load_pickle pipeline = Pipeline(app, predict_hz=20) pipeline.save_model = save_pickle pipeline.load_model = load_pickle ``` -------------------------------- ### Remove VHI Installation Directory Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/install-vhi.md After finding the installation path, use this command to forcefully remove the VHI directory and its contents. ```bash # Then remove it: rm -rf "$(python -c 'from myogestic.interfaces import _default_install_root; print(_default_install_root())')" ``` -------------------------------- ### Handle FileNotFoundError for VHI Launcher Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/integrate-vhi.md Gracefully handle cases where VHI is not installed by catching FileNotFoundError. This ensures the demo can still run while informing the user about the missing VHI installation. ```python try: PROCESSES = [*base, *vhi.launcher()] except FileNotFoundError as e: print(f"[demo] {e}", file=sys.stderr) PROCESSES = base # demo still runs; VHI button just absent ``` -------------------------------- ### Direct Instantiation and Usage of Filters Source: https://context7.com/nsquaredlab/myogestic/llms.txt Demonstrates direct instantiation of OneEuroFilter and GaussianFilter for data smoothing. Shows how to initialize the filter state on the first call and apply it to subsequent data points. Includes resetting the filter state. ```python from myogestic.ml import Pipeline from myogestic.outputs import LSLOutlet vhi_outlet = LSLOutlet("MyoGestic_Output", n_channels=9, hz=32) def predict(model, features): raw = model.predict(features.reshape(1, -1))[0].astype(np.float32) smooth = f_euro(raw) # adaptive smoothing before actuator push vhi_outlet.push(smooth) return {"pred": smooth} # Reset state between experiments f_euro.reset() ``` -------------------------------- ### Initialize VHI Components and App Configuration Source: https://context7.com/nsquaredlab/myogestic/llms.txt Sets up the LSL outlet for VHI, initializes the gRPC control client, and configures the Myogestic application with streams and a processing pipeline. ```python vhi_outlet = vhi.outlet() # gRPC control client — set_movement, set_session_active, etc. vhi_client = vhi.control_client() HAND_REST = np.zeros(9, dtype=np.float32) HAND_FIST = np.array([-1, 0, -1, -1, -1, -1, 0, 0, 0], dtype=np.float32) CLASSES = ["Rest", "Fist"] app = App("VHI Demo") app.streams( Stream("emg", source=LSLSource("TestEMG1"), window_seconds=0.2), Stream("vhi_control", source=LSLSource(vhi.control_stream), window_seconds=0.2), ) pipeline = Pipeline(app) ``` -------------------------------- ### Pin VHI version for reproducibility Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/install-vhi.md To ensure consistent behavior over time, pin the VHI installation to a specific release tag. The installer verifies the download against the SHA-256 digest from the GitHub release API. ```bash python -m myogestic.tools.install_vhi --tag v1.0.0 ``` -------------------------------- ### Core App and Stream Management Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/reference/api-cheatsheet.md Instantiate and manage the main application, its UI, and data streams. Use `App.ctx` to access the application context. ```python App(name, theme=True, docking=False) .streams(*streams) .ui(fn) # decorator .popout(title, gui_fn) # App(docking=True) .start_recording(base_path="sessions") .stop_recording() .run(mode="gui" | "headless") .ctx # Context ``` ```python Stream(name, source, window_seconds, buffer_seconds=10) .start() / .stop() / .reconnect(target=None) .get_window() -> (data, ts) # data is channels-first .get_display(n_pixels) -> (env_min, env_max) .get_raw_snapshot() -> (ts, data) ``` ```python Context # flat dataclass .streams: dict[str, Stream] .state: str # "idle" / "recording" / ... .session: Session | None # active recording .class_names: list[str] .status_message: str ``` ```python Grid(rows, cols) # @app.ui layout helper ``` ```python StreamInfo(n_channels, fs, dtype=float32, channel_names=None) ``` ```python TrainingData(paths=[], class_names=[], classes=set()) .is_empty ``` -------------------------------- ### Test Custom Source Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/add-a-source.md Connect to a custom source, retrieve its information, and read data in a loop. Ensure to replace `MySource(...)` with your actual source implementation. ```python import time src = MySource(...) info = src.connect() print(f"{info.n_channels} ch @ {info.fs} Hz") for _ in range(20): data, ts = src.read() if data is not None: print(f"got {data.shape[0]} samples") time.sleep(0.05) src.disconnect() ``` -------------------------------- ### Run Myogestic Application Source: https://context7.com/nsquaredlab/myogestic/llms.txt Starts the Myogestic application and ensures the gRPC client is stopped upon exit. ```python try: app.run() finally: vhi_client.stop() ``` -------------------------------- ### Pushing Predictions to Output Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/concepts/pipeline.md Example of pushing processed predictions to an output outlet within the @pipeline.predict function. ```python @pipeline.predict def predict(model, features): pose = model.predict(features) pose_smooth = pose_filter(pose, t=time.monotonic()) vhi_outlet.push(pose_smooth) return {"pose": pose_smooth} ``` -------------------------------- ### SerialOutput Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/api/outputs.md The SerialOutput class provides functionality for sending data over a serial port. It requires the 'serial' extra to be installed. ```APIDOC ## `SerialOutput` Opt-in: lives at `myogestic.outputs.serial_output.SerialOutput`. Direct import only (requires the `serial` extra for `pyserial`). ::: myogestic.outputs.serial_output.SerialOutput ``` -------------------------------- ### MyoGestic App with UI Widgets Source: https://context7.com/nsquaredlab/myogestic/llms.txt Demonstrates building a MyoGestic application using various built-in UI widgets for signal viewing, recording, pipeline management, and process launching. Ensure necessary streams are configured. ```python from myogestic import App, Stream from myogestic.ml import Pipeline from myogestic.ml.widgets import pipeline_panel from myogestic.sources import LSLSource from myogestic.widgets import ( app_logo, signal_viewer, recording_controls, session_manager, process_launcher, log_panel, prediction_label, stream_panel, popout_panel, heatmap, scatter2d, scatter3d, FilterControl, FeatureSelector, ) CLASSES = ["Rest", "Fist"] app = App("Widget Demo") app.streams(Stream("emg", source=LSLSource("TestEMG1"), window_seconds=0.2, buffer_seconds=60)) pipeline = Pipeline(app) @app.ui def ui(ctx): # Branding app_logo() # Real-time signal display — selectable=True lets user switch active stream signal_viewer(ctx, "emg", window_seconds=3.0, selectable=True) # Recording with label buttons; on_gesture fires on each class button click recording_controls( ctx, CLASSES, on_record=app.start_recording, on_stop=app.stop_recording, on_gesture=lambda i: print(f"Gesture {CLASSES[i]}"), ) # Session picker — assign result to pipeline.training_data each frame pipeline.training_data = session_manager("sessions", class_names=CLASSES) # Train / Predict / training log in one panel pipeline_panel(pipeline) # Current prediction: shows class name + probability bar prediction_label(pipeline, CLASSES) # Start/stop child processes process_launcher([ ("EMG Generator", ["python", "-m", "myogestic.tools.emg_generator", "--name", "TestEMG1", "--channels", "8", "--fs", "2048"]), ]) # App event log (ctx.log(...) from any thread) log_panel(ctx) app.run() ``` -------------------------------- ### SerialSource Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/api/sources.md The SerialSource allows reading data from serial ports. It requires the 'serial' extra to be installed and is available for direct import. ```APIDOC ## SerialSource ### Description Opt-in source for reading data from serial ports. ### Import Direct import only: `from myogestic.sources.serial_source import SerialSource` ### Requirements Requires the `serial` extra for `pyserial`. ``` -------------------------------- ### Initialize Grid with Rows and Columns Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/concepts/grid-layout.md Initialize a Grid with a specified number of rows and columns. By default, all tracks are set to Fr(1) for equal distribution of remaining space. ```python from myogestic.grid import Grid, Px, Fr grid = Grid(3, 4) # 3 rows × 4 cols, every track Fr(1) ``` ```python grid = Grid( 8, 3, row_height=[Px(200), Fr(1), Fr(1), Fr(1), Fr(1), Fr(1), Fr(1), Fr(1)], col_width =[Px(300), Fr(1), Fr(1)], ) ``` -------------------------------- ### Data Output Configuration Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/reference/api-cheatsheet.md Set up various output methods for streaming data. The `push` method is common across all output types. ```python LSLOutlet(name, n_channels, hz=50) ``` ```python UDPOutput(host, port, hz=50) ``` ```python SerialOutput(port, baud=115200, hz=10) # extras: [serial] ``` ```python .push(data) -> None # all outputs ``` -------------------------------- ### Stateless tick example Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/how-to/inter-stage-state.md This is the base case where no state needs to be maintained between pipeline ticks. The extract and predict functions operate independently on each tick. ```python @pipeline.extract def extract(windows): return rms(windows["emg"]) # returns a fresh feature vector each tick @pipeline.predict def predict(model, features): return {"class": int(model.predict(features.reshape(1, -1))[0])} ``` -------------------------------- ### Run Widget Screenshot Tool Source: https://github.com/nsquaredlab/myogestic/blob/main/docs/widget-gallery.md Command to capture screenshots of all widgets for documentation and styling verification. ```bash uv run python tools/widget_screenshot.py --all ``` -------------------------------- ### Replay Recorded Session with ReplaySource Source: https://context7.com/nsquaredlab/myogestic/llms.txt Utilize `ReplaySource` to replay a recorded session from a `.session.zip` file or a folder. This allows for offline development and testing of algorithms in a way that mimics real-time usage. Supports speed multipliers for playback. ```python from myogestic import App, Stream from myogestic.sources import ReplaySource from myogestic.widgets import signal_viewer app = App("Replay Demo") app.streams( Stream( "emg", source=ReplaySource( "sessions/20240101_120000.session.zip", stream_name="emg", speed=1.0, # 2.0 = twice real-time, 0.5 = half ), window_seconds=0.2, ) ) @app.ui def ui(ctx): signal_viewer(ctx, "emg") app.run() ```