EMG Analysis: Preprocessing, Amplitude, and Onset Detection
Source:vignettes/emg-analysis.Rmd
emg-analysis.RmdIntroduction
PhysioEMG provides a suite of functions for surface electromyography (EMG) analysis built on the PhysioExperiment data model. This vignette demonstrates the core workflow for EMG signal processing: creating EMG data objects, extracting amplitude envelopes, normalizing amplitudes, and detecting muscle activation onsets.
Creating EMG Data
PhysioEMG includes synthetic data generators for testing and
demonstration. The make_emg() function creates a
multi-channel EMG PhysioExperiment with simulated muscle activity
bursts.
library(PhysioEMG)
# Create a 4-channel EMG recording at 1000 Hz
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000)
pe
# View the channel metadata
SummarizedExperiment::colData(pe)For testing onset detection, make_emg_contraction()
generates a single-channel signal with a known contraction window:
pe_contraction <- make_emg_contraction(
n_time = 5000,
sr = 1000,
contraction_start = 0.3,
contraction_end = 0.7
)Amplitude Envelope Extraction
The emgEnvelope() function extracts the amplitude
envelope using one of three methods:
- RMS (root mean square): Computes RMS over a sliding window. Best for general-purpose amplitude tracking.
- Hilbert: Uses the analytic signal via the Hilbert transform. Provides instantaneous amplitude without windowing artifacts.
- Lowpass: Rectifies the signal and applies a moving-average lowpass filter. A simple and computationally efficient approach.
# RMS envelope with 50 ms window
pe_env <- emgEnvelope(pe, method = "rms", window_ms = 50)
# The envelope is stored as a new assay
SummarizedExperiment::assayNames(pe_env)
# [1] "raw" "envelope"
# Hilbert envelope for instantaneous amplitude
pe_hilbert <- emgEnvelope(pe, method = "hilbert", output_assay = "hilbert_env")
# Lowpass envelope with 6 Hz cutoff
pe_lp <- emgEnvelope(pe, method = "lowpass", cutoff = 6, output_assay = "lp_env")Amplitude Normalization
EMG amplitudes vary across subjects and recording sessions.
Normalization enables meaningful comparisons. PhysioEMG supports two
normalization methods via emgAmplitudeNormalize():
- MVC (maximum voluntary contraction): Divides by the peak of a separate MVC trial, yielding percentage-of-MVC units.
- Peak: Divides by the within-trial peak so each channel ranges from 0 to 1.
# Peak normalization (no external reference needed)
pe_norm <- emgAmplitudeNormalize(pe_env, method = "peak", assay_name = "envelope")
# Check the normalized values range from 0 to 1
range(SummarizedExperiment::assay(pe_norm, "normalized"))
# MVC normalization requires a separate MVC trial, enveloped the same way as the
# working data so both carry the assay named in `assay_name` ("envelope").
mvc_trial <- emgEnvelope(make_emg(n_time = 1000, n_channels = 4, sr = 1000))
pe_mvc <- emgAmplitudeNormalize(
pe_env,
method = "mvc",
mvc_data = mvc_trial,
assay_name = "envelope"
)Muscle Activation Onset Detection
Detecting when a muscle becomes active is fundamental to many biomechanics analyses. PhysioEMG implements two onset detection methods:
Hodges-Bui Method
The Hodges-Bui method (1996) thresholds the rectified EMG signal at a multiple of baseline standard deviations. It is widely used due to its simplicity and robustness.
pe_contraction <- make_emg_contraction(n_time = 5000, sr = 1000)
# Detect onset using Hodges-Bui method
result_hb <- emgOnsetDetect(
pe_contraction,
method = "hodges_bui",
threshold_sd = 3,
baseline_sec = 0.2,
min_duration_ms = 50
)
# View detected onsets
result_hb$onsets
# Expected: onset near 30% of signal (1.5 seconds at 1000 Hz)Teager-Kaiser Energy Operator
The Teager-Kaiser method applies the Teager-Kaiser energy operator before thresholding. This can be more sensitive to sudden onsets because the TKEO responds to both amplitude and frequency changes.
result_tk <- emgOnsetDetect(
pe_contraction,
method = "teager_kaiser",
threshold_sd = 3,
baseline_sec = 0.2
)
result_tk$onsetsComparing Methods
Both methods return onset and offset data.frames with channel, sample, and time information:
# Multi-channel onset detection
pe_multi <- make_emg(n_time = 5000, n_channels = 4, sr = 1000)
onsets_hb <- emgOnsetDetect(pe_multi, method = "hodges_bui")
onsets_tk <- emgOnsetDetect(pe_multi, method = "teager_kaiser")
# Compare onset times across methods
merge(
onsets_hb$onsets, onsets_tk$onsets,
by = "channel", suffixes = c("_hb", "_tk")
)Complete Workflow Example
A typical EMG analysis pipeline chains these steps together:
library(PhysioEMG)
# 1. Load or create EMG data
pe <- make_emg(n_time = 5000, n_channels = 4, sr = 1000)
# 2. Extract amplitude envelope
pe <- emgEnvelope(pe, method = "rms", window_ms = 50)
# 3. Normalize amplitudes
pe <- emgAmplitudeNormalize(pe, method = "peak", assay_name = "envelope")
# 4. Detect muscle activation onsets
onsets <- emgOnsetDetect(pe, method = "hodges_bui", threshold_sd = 3)
# 5. Review results
print(onsets$onsets)
print(onsets$offsets)References
- De Luca, C.J. (1997). “The use of surface electromyography in biomechanics.” Journal of Applied Biomechanics, 13(2), 135-163.
- Hodges, P.W. & Bui, B.H. (1996). “A comparison of computer-based methods for the determination of onset of muscle contraction using electromyography.” Electroencephalography and Clinical Neurophysiology, 101(6), 511-519.
- Merletti, R. & Parker, P.A. (2004). “Electromyography: Physiology, Engineering, and Non-Invasive Applications.” Wiley-IEEE Press.