Filtered signals
Zero-phase bandpass + notch filtering, ready to use as features or to drive the deployable causal filter with the same parameters.
An end-to-end AutoML framework for surface-EMG: filtering, event detection, feature extraction, model selection across gradient boosting, random forests and linear models, and deployable C++/Arduino code for microcontrollers such as the ESP32.
pip install emg2mcu
EMG2MCU wires the whole loop with configuration dataclasses (DataConfig,
PreprocessingConfig, EventsConfig, WindowingConfig,
FeatureConfig, NormalizeConfig, ClassificationConfig) and a single
automl() entry point. Filtering is available in zero-phase (benchmark) and causal-SOS (deploy)
modes so the same parameters carry over to the on-device filter. The deploy step generates the biquad
coefficients the firmware needs and scaffolds the C++/Arduino code that runs them on the chip.
Zero-phase bandpass + notch filtering, ready to use as features or to drive the deployable causal filter with the same parameters.
Onset/offset events from an envelope smoother, with a configurable sustain window to avoid spurious triggers between repetitions.
Per-channel, per-window time-domain features (RMS, MAV, ZC, SSC, …) optionally normalised before classification.
Cross-validated accuracy, F1 and ROC AUC for every algorithm in ClassificationConfig, sorted by
the metric you choose.
The figures on this page are produced by running the pipeline in EMG2MCU and rendering the results with Plotly. Open the Sandbox to run the same example in your browser.
pip install emg2mcu
Pulls in NumPy, pandas, SciPy, scikit-learn, LightGBM, XGBoost, CatBoost, joblib and tsfresh.
pip install "emg2mcu[docs]"
Adds Sphinx, pydata-sphinx-theme, autoapi, myst-parser, copybutton and sphinx-design for the Read the Docs site.
from pathlib import Path
import tempfile
import numpy as np
import EMG2MCU
# 1. Build a deterministic synthetic three-class recording (no real data needed)
fs = 1000
rng = np.random.default_rng(42)
signal_blocks, label_blocks = [], []
def add_segment(label, duration_s, amplitudes):
n = int(duration_s * fs)
t = np.arange(n) / fs
seg = rng.normal(0, 0.015, size=(3, n))
if label >= 0:
fade = np.minimum(1, np.minimum(t / 0.15, (duration_s - t) / 0.15))
freqs = np.array([83, 117, 151]) + 7 * label
carriers = np.sin(2 * np.pi * freqs[:, None] * t)
seg += np.asarray(amplitudes)[:, None] * fade * carriers
signal_blocks.append(seg.T)
label_blocks.append(np.full(n, label, dtype=int))
for label, amps in enumerate([[0.8, 0.15, 0.15], [0.15, 0.8, 0.15], [0.15, 0.15, 0.8]]):
add_segment(label, 3.0, amps)
add_segment(-1, 0.5, [0, 0, 0])
signals = np.concatenate(signal_blocks, axis=0)
labels = np.concatenate(label_blocks)
# 2. Configure and run the AutoML pipeline
state = EMG2MCU.automl(
data_cfg=EMG2MCU.DataConfig(data_file="synthetic.txt", label_names=["A", "B", "C"]),
preprocessing_cfg=EMG2MCU.PreprocessingConfig(fs=fs, lowcut=30, highcut=450),
feature_cfg=EMG2MCU.FeatureConfig(features=["RMS"]),
output_dir=Path("output_emg2mcu"),
)
# 3. The ranked classifiers live in state["deploy"]["td_results"]
print(state["deploy"]["td_results"].sort_values("accuracy", ascending=False).head())
The full example
notebook writes the synthetic recording to a temporary file, configures every stage and runs the
full pipeline end-to-end. Generated artifacts land in the ignored output_emg2mcu/ directory.
EMG2MCU is developed by the LENeR-UEL lab at the State University of Londrina (UEL). It is open source under the MIT license; contributions, bug reports and feature requests are welcome on GitHub.