| Title: | EMG Analysis Functions for PhysioExperiment Objects |
|---|---|
| Description: | Provides electromyography (EMG) analysis functions for PhysioExperiment objects. Includes envelope extraction (RMS, Hilbert), muscle activation onset detection (Hodges-Bui, Teager-Kaiser), fatigue analysis (median frequency shift), and muscle synergy decomposition (NMF, PCA, ICA). |
| Authors: | Yusuke Matsui [aut, cre] |
| Maintainer: | Yusuke Matsui <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.4.2 |
| Built: | 2026-10-03 13:42:06 UTC |
| Source: | https://github.com/x-biosignal/PhysioEMG |
Extracts shift-invariant spatiotemporal muscle synergies by convolutive NMF.
Each of the n_synergies components is a muscle x L template that the model
can place at any time with any amplitude, so a synergy captures a fixed
spatiotemporal pattern of muscle activation (d'Avella time-varying
synergies), not just a spatial weighting.
convolutiveSynergy( emg, n_synergies, L, max_iter = 300, tol = 1e-06, restarts = 3, seed = NULL )convolutiveSynergy( emg, n_synergies, L, max_iter = 300, tol = 1e-06, restarts = 3, seed = NULL )
emg |
A non-negative |
n_synergies |
Number of synergies. |
L |
Temporal length of each synergy (samples). |
max_iter |
Maximum multiplicative-update iterations (default 300). |
tol |
Relative-error convergence tolerance (default 1e-6). |
restarts |
Random restarts; the best is kept (default 3). |
seed |
Optional integer seed. |
An object of class "convolutive_synergy": a list with synergies
(a muscle x synergy x L array), activations (synergy x time), vaf,
iterations, n_synergies, and L.
d'Avella A, Saltiel P, Bizzi E (2003). Combinations of muscle synergies in the construction of a natural motor behavior. Nat Neurosci 6:300-308. Smaragdis P (2004). Non-negative matrix factor deconvolution. ICA.
set.seed(1) M <- 5; Tt <- 200; N <- 2; L <- 15 W0 <- array(0, c(M, N, L)) for (nn in 1:N) W0[, nn, ] <- outer(runif(M), dnorm(1:L, L / 2, 3)) H0 <- matrix(0, N, Tt); H0[1, c(30, 120)] <- 1; H0[2, c(70, 160)] <- 1 V <- matrix(0, M, Tt) for (tau in 0:(L - 1)) V <- V + W0[, , tau + 1] %*% cbind(matrix(0, N, tau), H0[, 1:(Tt - tau), drop = FALSE]) fit <- convolutiveSynergy(V, n_synergies = 2, L = 15, seed = 1) fit$vafset.seed(1) M <- 5; Tt <- 200; N <- 2; L <- 15 W0 <- array(0, c(M, N, L)) for (nn in 1:N) W0[, nn, ] <- outer(runif(M), dnorm(1:L, L / 2, 3)) H0 <- matrix(0, N, Tt); H0[1, c(30, 120)] <- 1; H0[2, c(70, 160)] <- 1 V <- matrix(0, M, Tt) for (tau in 0:(L - 1)) V <- V + W0[, , tau + 1] %*% cbind(matrix(0, N, tau), H0[, 1:(Tt - tau), drop = FALSE]) fit <- convolutiveSynergy(V, n_synergies = 2, L = 15, seed = 1) fit$vaf
A single entry point over the four inter-muscle connectivity estimators
(emgCoherenceNetwork(), emgPartialCoherenceNetwork(), emgWPLINetwork(),
emgDirectedGCNetwork()): builds the coordination network of a multi-muscle
EMG recording with the chosen method and routes the relevant parameters to
the underlying estimator. Use it (with splitHalfStability()) to answer
"which muscles co-activate, and is that structure real?".
coordinationNetwork( x, method = c("coherence", "partial_coherence", "wpli", "directed_gc"), freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, max_lag = 10L, debiased = FALSE, assay_name = NULL, ... )coordinationNetwork( x, method = c("coherence", "partial_coherence", "wpli", "directed_gc"), freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, max_lag = 10L, debiased = FALSE, assay_name = NULL, ... )
x |
A |
method |
Estimator: |
freq_band |
Optional |
channels |
Optional integer channel indices to include (default all). |
nperseg, noverlap
|
Welch segment length / overlap for the spectral estimators (coherence / partial coherence / wPLI). |
max_lag |
Model order for directed Granger causality. |
debiased |
Use the debiased wPLI estimator. |
assay_name |
Assay to build the network from (default: the default assay). |
... |
Passed through to the underlying estimator. |
The estimator's network object – a list whose $network is the
inter-muscle adjacency matrix.
splitHalfStability(), emgCoordinationStructure(),
emgCoherenceNetwork()
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) net <- coordinationNetwork(pe, method = "coherence", freq_band = c(20, 150)) net$networkpe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) net <- coordinationNetwork(pe, method = "coherence", freq_band = c(20, 150)) net$network
Summarises how an agonist-antagonist muscle pair drives one ADL task from an
EMG envelope: the co-contraction index (via emgCoContraction()), each
muscle's peak and mean activation (as percent of a maximal reference when
mvc_data is given, else raw envelope units), and the fraction of the task
each muscle is active. Higher antagonist co-contraction means less efficient,
more guarded movement.
emgADLActivation( x, agonist, antagonist, mvc_data = NULL, task = c("reaching", "drinking", "feeding", "dressing", "grooming"), cc_method = c("falconer_winter", "rudolph", "frost"), active_frac = 0.2, assay_name = NULL )emgADLActivation( x, agonist, antagonist, mvc_data = NULL, task = c("reaching", "drinking", "feeding", "dressing", "grooming"), cc_method = c("falconer_winter", "rudolph", "frost"), active_frac = 0.2, assay_name = NULL )
x |
A |
agonist, antagonist
|
Channel index or |
mvc_data |
Optional |
task |
ADL task realised: |
cc_method |
Co-contraction index method (see |
active_frac |
Activation threshold as a fraction of a muscle's own peak (default 0.2) for the active-time summary. |
assay_name |
EMG assay to use (default: the first assay). |
an emg_adl_activation list: task, icf_code,
cocontraction_index, unit ("%MVC" or "envelope"), per-muscle
agonist_peak/agonist_mean/antagonist_peak/antagonist_mean and
agonist_active_frac/antagonist_active_frac.
emgCoContraction(), emgAmplitudeNormalize()
set.seed(1) n <- 1000; t <- seq_len(n) ag <- exp(-((t - 500) / 150)^2) + rnorm(n, 0, 0.02) # agonist burst an <- 0.2 * exp(-((t - 500) / 150)^2) + rnorm(n, 0, 0.02) # low antagonist pe <- PhysioExperiment::PhysioExperiment( assays = S4Vectors::SimpleList(envelope = cbind(BIC = ag, TRI = an)), colData = S4Vectors::DataFrame(label = c("BIC", "TRI"), type = "EMG"), samplingRate = 1000) emgADLActivation(pe, "BIC", "TRI", task = "drinking")$cocontraction_indexset.seed(1) n <- 1000; t <- seq_len(n) ag <- exp(-((t - 500) / 150)^2) + rnorm(n, 0, 0.02) # agonist burst an <- 0.2 * exp(-((t - 500) / 150)^2) + rnorm(n, 0, 0.02) # low antagonist pe <- PhysioExperiment::PhysioExperiment( assays = S4Vectors::SimpleList(envelope = cbind(BIC = ag, TRI = an)), colData = S4Vectors::DataFrame(label = c("BIC", "TRI"), type = "EMG"), samplingRate = 1000) emgADLActivation(pe, "BIC", "TRI", task = "drinking")$cocontraction_index
Computes time-domain amplitude features of an EMG signal over a sliding window. The signal is divided into overlapping windows and, for each window and channel, the requested features are computed:
arvAverage rectified value, .
mavMean absolute value,
(identical to ARV for an unweighted window).
iemgIntegrated EMG, with
; the time integral of the rectified signal,
in signal-units times seconds.
rmsRoot mean square, .
The window geometry (window and step sizes in samples) matches
emgFatigue() and emgSpectralMoments() for identical window_sec
and overlap, so amplitude and spectral features align window-for-window.
emgAmplitudeFeatures( x, features = c("arv", "mav", "iemg", "rms"), window_sec = 1, overlap = 0.5, assay_name = NULL )emgAmplitudeFeatures( x, features = c("arv", "mav", "iemg", "rms"), window_sec = 1, overlap = 0.5, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
features |
Character vector selecting which features to return; any of
|
window_sec |
Analysis window in seconds (default: 1.0). |
overlap |
Overlap fraction between consecutive windows, in
|
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per analysis window per channel, with columns:
Integer channel index.
Integer window index within the channel (1-based).
Window start time in seconds.
The requested amplitude features (only the
columns named in features are present).
If no full window fits in the signal, a 0-row data.frame with these columns is returned.
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
Hermens, H.J. et al. (2000). "Development of recommendations for SEMG sensors and sensor placement procedures (SENIAM)." Journal of Electromyography and Kinesiology, 10(5), 361-374. doi:10.1016/S1050-6411(00)00027-4
emgEnvelope() for a per-sample amplitude envelope,
emgFatigue() for windowed spectral fatigue features,
emgSpectralMoments() for spectral moments on the same window grid
# 2 s of two-channel EMG at 1000 Hz set.seed(1) m <- matrix(rnorm(2000 * 2, sd = 0.3), nrow = 2000, ncol = 2) pe <- PhysioExperiment( assays = list(raw = m), colData = S4Vectors::DataFrame(label = c("EMG1", "EMG2"), type = c("EMG", "EMG")), samplingRate = 1000) feats <- emgAmplitudeFeatures(pe, window_sec = 0.5, overlap = 0.5) head(feats)# 2 s of two-channel EMG at 1000 Hz set.seed(1) m <- matrix(rnorm(2000 * 2, sd = 0.3), nrow = 2000, ncol = 2) pe <- PhysioExperiment( assays = list(raw = m), colData = S4Vectors::DataFrame(label = c("EMG1", "EMG2"), type = c("EMG", "EMG")), samplingRate = 1000) feats <- emgAmplitudeFeatures(pe, window_sec = 0.5, overlap = 0.5) head(feats)
Normalizes EMG amplitude data by a per-channel reference value.
"mvc" divides by the per-channel maximum of a maximum-voluntary-
contraction trial (percentage-of-MVC). "peak" divides by the
within-trial peak so each channel ranges from 0 to 1. "rvc" divides by
the per-channel mean amplitude of a sub-maximal reference voluntary
contraction trial (percentage-of-RVC). "dynamic_peak" divides by a
centered moving maximum, tracking a time-varying peak within the trial.
emgAmplitudeNormalize( x, method = c("mvc", "peak", "rvc", "dynamic_peak"), mvc_data = NULL, rvc_data = NULL, rvc_window = NULL, assay_name = NULL, output_assay = "normalized" )emgAmplitudeNormalize( x, method = c("mvc", "peak", "rvc", "dynamic_peak"), mvc_data = NULL, rvc_data = NULL, rvc_window = NULL, assay_name = NULL, output_assay = "normalized" )
x |
A PhysioExperiment object (amplitude data). |
method |
Normalization method: "mvc" (maximum voluntary contraction), "peak" (within-trial peak), "rvc" (sub-maximal reference voluntary contraction), or "dynamic_peak" (centered moving maximum). |
mvc_data |
A PhysioExperiment containing MVC trial data (required for
"mvc"). Must have the same number of channels as |
rvc_data |
A PhysioExperiment containing the sub-maximal reference-task
data (required for "rvc"). Must have the same number of channels as
|
rvc_window |
Optional window. For "rvc", a length-2 numeric
|
assay_name |
Assay to normalize (default: first assay). |
output_assay |
Output assay name (default: "normalized"). |
Input is assumed to be amplitude data (a rectified signal or an
emgEnvelope() output). MVC and RVC normalization are scale-invariant to a
gain applied to both the signal and its reference trial.
A PhysioExperiment object with an additional assay named
output_assay containing normalized amplitude values. For "peak" and
"dynamic_peak", values are scaled to a peak. For "mvc"/"rvc", values are
proportions of the reference (multiply by 100 for percentage-of-MVC /
percentage-of-RVC).
De Luca, C.J. (1997). "The use of surface electromyography in biomechanics." Journal of Applied Biomechanics, 13(2), 135-163. doi:10.1123/jab.13.2.135
Yang, J.F. & Winter, D.A. (1984). "Electromyographic amplitude normalization methods: improving their sensitivity as diagnostic tools in gait analysis." Archives of Physical Medicine and Rehabilitation, 65(9), 517-521.
Burden, A. & Bartlett, R. (1999). "Normalisation of EMG amplitude: an evaluation and comparison of old and new methods." Medical Engineering & Physics, 21(4), 247-257. doi:10.1016/S1350-4533(99)00054-5
emgEnvelope() for computing amplitude envelopes prior to
normalization, emgAmplitudeFeatures() for windowed amplitude features,
emgFatigue() for fatigue analysis,
emgOnsetDetect() for muscle activation onset detection
set.seed(1) amp <- matrix(abs(rnorm(1000 * 2, sd = 0.3)), nrow = 1000, ncol = 2) pe <- PhysioExperiment( assays = list(raw = amp), colData = S4Vectors::DataFrame(label = c("EMG1", "EMG2"), type = c("EMG", "EMG")), samplingRate = 1000) # percentage-of-RVC using a sub-maximal reference (here, the trial itself) pe_rvc <- emgAmplitudeNormalize(pe, method = "rvc", rvc_data = pe) colMeans(SummarizedExperiment::assay(pe_rvc, "normalized")) # ~ 1set.seed(1) amp <- matrix(abs(rnorm(1000 * 2, sd = 0.3)), nrow = 1000, ncol = 2) pe <- PhysioExperiment( assays = list(raw = amp), colData = S4Vectors::DataFrame(label = c("EMG1", "EMG2"), type = c("EMG", "EMG")), samplingRate = 1000) # percentage-of-RVC using a sub-maximal reference (here, the trial itself) pe_rvc <- emgAmplitudeNormalize(pe, method = "rvc", rvc_data = pe) colMeans(SummarizedExperiment::assay(pe_rvc, "normalized")) # ~ 1
Quantifies simultaneous activation of an agonist-antagonist muscle pair from
their amplitude envelopes, using one of three established indices. Input
channels are treated as (non-negative) amplitude envelopes; with
normalize = TRUE each is first scaled to its own peak so the two
muscles are amplitude-comparable.
emgCoContraction( x, agonist, antagonist, method = c("falconer_winter", "rudolph", "frost"), normalize = TRUE, window_sec = NULL, assay_name = NULL )emgCoContraction( x, agonist, antagonist, method = c("falconer_winter", "rudolph", "frost"), normalize = TRUE, window_sec = NULL, assay_name = NULL )
x |
A PhysioExperiment object of EMG amplitude envelopes. |
agonist, antagonist
|
Channel index (integer) or channel label (character) of the two muscles. The indices are interchangeable for all three methods (the indices are symmetric in the pair). |
method |
Co-contraction index: "falconer_winter" (default), "rudolph", or "frost". |
normalize |
If TRUE (default), peak-normalize each channel to [0, 1] before computing the index. |
window_sec |
Optional non-overlapping window length in seconds for a time-resolved index. If NULL (default) only the whole-signal summary is returned. |
assay_name |
Input assay name (default: first assay). |
"falconer_winter"Falconer & Winter (1985) percent
co-contraction: . 100 when the
two envelopes are identical, 0 when they never overlap.
"rudolph"Rudolph et al. (2000) co-contraction index: the mean
over time of with ,
. Symmetric in the two muscles and non-negative.
"frost"Frost et al. (1997) variant: the time-average of the
instantaneous co-activation ratio , as a percentage.
A list with:
The method used.
The whole-signal co-contraction index (a scalar).
If window_sec is given, a data.frame with columns
window, time_sec and cci (one row per window);
otherwise NULL.
Falconer, K. & Winter, D.A. (1985). "Quantitative assessment of co-contraction at the ankle joint in walking." Electromyography and Clinical Neurophysiology, 25(2-3), 135-149.
Rudolph, K.S., Axe, M.J. & Snyder-Mackler, L. (2000). "Dynamic stability after ACL injury: who can hop?" Knee Surgery, Sports Traumatology, Arthroscopy, 8(5), 262-269. doi:10.1007/s001670000130
Frost, G., Dowling, J., Dyson, K. & Bar-Or, O. (1997). "Cocontraction in three age groups of children during treadmill locomotion." Journal of Electromyography and Kinesiology, 7(3), 179-186. doi:10.1016/S1050-6411(97)84626-3
emgEnvelope() for computing amplitude envelopes,
emgAmplitudeNormalize() for reference normalization,
emgOnsetDetect() for activation timing
set.seed(1) t <- seq_len(1000) a <- exp(-((t - 400) / 120)^2) # agonist envelope b <- exp(-((t - 600) / 120)^2) # antagonist envelope pe <- PhysioExperiment( assays = list(env = cbind(a, b)), colData = S4Vectors::DataFrame(label = c("TA", "GAS"), type = c("EMG", "EMG")), samplingRate = 1000) emgCoContraction(pe, agonist = "TA", antagonist = "GAS")$summaryset.seed(1) t <- seq_len(1000) a <- exp(-((t - 400) / 120)^2) # agonist envelope b <- exp(-((t - 600) / 120)^2) # antagonist envelope pe <- PhysioExperiment( assays = list(env = cbind(a, b)), colData = S4Vectors::DataFrame(label = c("TA", "GAS"), type = c("EMG", "EMG")), samplingRate = 1000) emgCoContraction(pe, agonist = "TA", antagonist = "GAS")$summary
Builds a static muscle coordination network from pairwise magnitude-squared coherence between EMG channels.
emgCoherenceNetwork( x, freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL )emgCoherenceNetwork( x, freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL )
x |
A PhysioExperiment object. |
freq_band |
Optional numeric vector |
channels |
Integer vector of channel indices to include. If NULL, uses all. |
nperseg |
Segment length for Welch estimation (default: 256). |
noverlap |
Overlap length (default: |
assay_name |
Input assay name. If NULL, uses default assay. |
aggregate |
Aggregation across frequency bins: "mean", "max", or "median". |
threshold |
Optional threshold for binary adjacency matrix. |
A list with:
Numeric matrix (channel x channel) of coherence strength.
Logical matrix after thresholding, or NULL.
3D array (freq x channel x channel).
Frequency vector (Hz) corresponding to coherence.
Channel labels used in the network.
emgDynamicWaveletNetwork() for time-varying networks,
emgInterpretNetworkKG() for annotation-aware interpretation.
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgCoherenceNetwork(pe, freq_band = c(20, 150)) res$networkpe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgCoherenceNetwork(pe, freq_band = c(20, 150)) res$network
Quantifies higher-order muscle coordination structure from a weighted network matrix, including module structure, efficiency, and node roles.
emgCoordinationStructure( network, threshold = NULL, n_modules = NULL, max_modules = 6L, directed = FALSE, symmetrize = c("mean", "max", "min"), normalize = TRUE )emgCoordinationStructure( network, threshold = NULL, n_modules = NULL, max_modules = 6L, directed = FALSE, symmetrize = c("mean", "max", "min"), normalize = TRUE )
network |
Numeric square matrix (channels x channels) or a list
containing |
threshold |
Optional edge-weight threshold. Values below threshold are set to zero before topology estimation. |
n_modules |
Optional number of modules. If NULL, chooses a value automatically by maximizing weighted modularity over candidates. |
max_modules |
Maximum number of candidate modules for automatic search. |
directed |
Logical; set TRUE if |
symmetrize |
Method to convert directed matrices to undirected form: "mean", "max", or "min". |
normalize |
Logical; if TRUE, rescales weights to |
A list with:
Processed undirected weighted network matrix.
Data.frame of node-level topology features.
Named integer vector of module assignments.
Global network topology summary.
emgCoherenceNetwork(), emgDynamicWaveletNetwork()
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) net <- emgCoherenceNetwork(pe, freq_band = c(20, 150)) cs <- emgCoordinationStructure(net) cs$summarype <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) net <- emgCoherenceNetwork(pe, freq_band = c(20, 150)) cs <- emgCoordinationStructure(net) cs$summary
Computes spectral moments for
over sliding windows and the Dimitrov fatigue index
per window. FInsm5 rises steeply with fatigue
because spectral compression toward low frequencies simultaneously increases
the low-frequency moment and decreases the high-frequency moment
. The per-channel FInsm5-vs-time regression slope is attached as the
"finsm5_slope" attribute.
emgDimitrovIndex(x, window_sec = 1, overlap = 0.5, assay_name = NULL)emgDimitrovIndex(x, window_sec = 1, overlap = 0.5, assay_name = NULL)
x |
A PhysioExperiment object with EMG data. |
window_sec |
Analysis window in seconds (default: 1.0). |
overlap |
Overlap fraction between windows (default: 0.5). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per channel per window, containing columns
channel, window, time_sec, the moments
m_minus1, m0, m1, m2, m3, m4,
m5, and finsm5. The per-channel FInsm5 regression slope
(index per minute) is available via attr(result, "finsm5_slope").
Dimitrov, G.V. et al. (2006). "Muscle fatigue during dynamic contractions assessed by new spectral indices." Medicine & Science in Sports & Exercise, 38(11), 1971-1979. doi:10.1249/01.mss.0000233794.31659.6d
emgFatigueSlope() for MDF/MNF regression slopes,
emgSpectralMoments() for the M0/M1/M2 moments,
emgFatigue() for median/mean frequency tracking
sr <- 1000; n <- 6000 sig <- sin(2 * pi * 80 * seq_len(n) / sr) + rnorm(n, sd = 0.1) pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = sr) di <- emgDimitrovIndex(pe, window_sec = 1) head(di) attr(di, "finsm5_slope")sr <- 1000; n <- 6000 sig <- sin(2 * pi * 80 * seq_len(n) / sr) + rnorm(n, sd = 0.1) pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = sr) di <- emgDimitrovIndex(pe, window_sec = 1) head(di) attr(di, "finsm5_slope")
Estimates a directed muscle coordination network using pairwise Granger causality in the time domain.
emgDirectedGCNetwork( x, channels = NULL, assay_name = NULL, max_lag = 10L, score = c("f_stat", "delta_r2"), threshold = NULL, p_value_cutoff = NULL, standardize = TRUE )emgDirectedGCNetwork( x, channels = NULL, assay_name = NULL, max_lag = 10L, score = c("f_stat", "delta_r2"), threshold = NULL, p_value_cutoff = NULL, standardize = TRUE )
x |
A PhysioExperiment object. |
channels |
Integer vector of channel indices to include. If NULL, uses all. |
assay_name |
Input assay name. If NULL, uses default assay. |
max_lag |
Lag order (in samples) for autoregressive modeling. |
score |
Directed edge metric: "f_stat" or "delta_r2". |
threshold |
Optional threshold for adjacency based on selected score. |
p_value_cutoff |
Optional p-value threshold for adjacency. |
standardize |
Logical; if TRUE, z-score each channel before GC. |
A list with:
Directed numeric matrix (source x target).
Directed matrix of GC p-values.
Logical directed matrix, or NULL.
Channel labels used in the network.
Lag order used for modeling.
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgDirectedGCNetwork(pe, max_lag = 5) res$networkpe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgDirectedGCNetwork(pe, max_lag = 5) res$network
Builds a time-varying coordination network by computing wavelet coherence between channel pairs and aggregating coherence within sliding windows.
emgDynamicWaveletNetwork( x, frequencies = seq(5, 120, by = 5), freq_band = NULL, channels = NULL, window_sec = 0.5, step_sec = 0.1, n_cycles = 7, smoothing_cycles = 3, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL, respect_coi = TRUE )emgDynamicWaveletNetwork( x, frequencies = seq(5, 120, by = 5), freq_band = NULL, channels = NULL, window_sec = 0.5, step_sec = 0.1, n_cycles = 7, smoothing_cycles = 3, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL, respect_coi = TRUE )
x |
A PhysioExperiment object. |
frequencies |
Numeric vector of wavelet center frequencies (Hz). |
freq_band |
Optional numeric vector |
channels |
Integer vector of channel indices to include. If NULL, uses all. |
window_sec |
Sliding window length in seconds. |
step_sec |
Sliding window step in seconds. |
n_cycles |
Number of Morlet cycles (default: 7). |
smoothing_cycles |
Smoothing width in cycles (default: 3). |
assay_name |
Input assay name. If NULL, uses default assay. |
aggregate |
Aggregation across time-frequency bins: "mean", "max", or "median". |
threshold |
Optional threshold for binary adjacency network per window. |
respect_coi |
Logical; if TRUE, masks frequencies below COI before aggregation. |
A list with:
3D array (window x channel x channel).
Logical 3D array thresholded from network, or NULL.
Window center times in seconds.
Mean network across windows.
Frequency vector used for wavelet transform.
Cone-of-influence frequency at each time sample.
emgCoherenceNetwork() for static spectral network,
emgInterpretNetworkKG() for annotation-aware interpretation.
pe <- make_emg(n_time = 1000, n_channels = 3, sr = 1000) res <- emgDynamicWaveletNetwork(pe, frequencies = seq(20, 100, by = 20), window_sec = 0.25, step_sec = 0.1) dim(res$network) # windows x channels x channelspe <- make_emg(n_time = 1000, n_channels = 3, sr = 1000) res <- emgDynamicWaveletNetwork(pe, frequencies = seq(20, 100, by = 20), window_sec = 0.25, step_sec = 0.1) dim(res$network) # windows x channels x channels
Extracts the amplitude envelope from EMG signals using various methods. The RMS method computes root mean square over a sliding window. The Hilbert method uses the analytic signal via the Hilbert transform. The lowpass method rectifies the signal and applies a moving-average lowpass filter.
emgEnvelope( x, method = c("rms", "hilbert", "lowpass"), window_ms = 50, cutoff = 6, assay_name = NULL, output_assay = "envelope" )emgEnvelope( x, method = c("rms", "hilbert", "lowpass"), window_ms = 50, cutoff = 6, assay_name = NULL, output_assay = "envelope" )
x |
A PhysioExperiment object with EMG data. |
method |
Envelope method: "rms" (root mean square), "hilbert" (Hilbert transform), or "lowpass" (rectification + lowpass filter). |
window_ms |
Window size in milliseconds for RMS method (default: 50). |
cutoff |
Cutoff frequency in Hz for lowpass method (default: 6). |
assay_name |
Input assay name (default: first assay). |
output_assay |
Output assay name (default: "envelope"). |
A PhysioExperiment object with an additional assay named
output_assay containing the amplitude envelope. The envelope
matrix has the same dimensions as the input (time x channels) with
non-negative values representing instantaneous signal amplitude.
De Luca, C.J. (1997). "The use of surface electromyography in biomechanics." Journal of Applied Biomechanics, 13(2), 135-163. doi:10.1123/jab.13.2.135
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgAmplitudeNormalize() for normalizing envelope values,
emgOnsetDetect() for onset detection from envelope data,
emgFatigue() for fatigue analysis using spectral features
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) pe <- emgEnvelope(pe, method = "rms", window_ms = 50) dim(SummarizedExperiment::assay(pe, "envelope"))pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) pe <- emgEnvelope(pe, method = "rms", window_ms = 50) dim(SummarizedExperiment::assay(pe, "envelope"))
Tracks median and mean frequency over time to assess muscle fatigue. Decreasing median frequency indicates fatigue due to reduced motor unit conduction velocity. The signal is divided into overlapping windows and the power spectral density is computed via FFT for each window.
emgFatigue(x, window_sec = 1, overlap = 0.5, assay_name = NULL)emgFatigue(x, window_sec = 1, overlap = 0.5, assay_name = NULL)
x |
A PhysioExperiment object with EMG data. |
window_sec |
Analysis window in seconds (default: 1.0). |
overlap |
Overlap fraction between windows (default: 0.5). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per channel per window, containing columns:
Integer channel index.
Integer window number (1-indexed).
Start time of the window in seconds.
Median frequency (Hz) at which 50 percent of the spectral power is below.
Power-weighted mean frequency (Hz).
Root mean square amplitude of the window.
De Luca, C.J. (1984). "Myoelectrical manifestations of localized muscular fatigue in humans." Critical Reviews in Biomedical Engineering, 11(4), 251-279.
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigueIndex() for a summary fatigue metric,
emgSpectralMoments() for spectral moment analysis,
emgEnvelope() for amplitude envelope extraction
pe <- make_emg_fatigue(n_time = 10000, sr = 1000) fat <- emgFatigue(pe, window_sec = 1.0) head(fat)pe <- make_emg_fatigue(n_time = 10000, sr = 1000) fat <- emgFatigue(pe, window_sec = 1.0) head(fat)
Computes a fatigue index as the ratio of final to initial median frequency.
Values less than 1 indicate fatigue (frequency decrease). The signal is
internally analyzed with emgFatigue() using 0.5-second windows at 50
percent overlap, then the initial and final portions are compared.
emgFatigueIndex(x, initial_pct = 0.2, final_pct = 0.2, assay_name = NULL)emgFatigueIndex(x, initial_pct = 0.2, final_pct = 0.2, assay_name = NULL)
x |
A PhysioExperiment object with EMG data. |
initial_pct |
Percentage of signal used for initial estimate (default: 0.2). |
final_pct |
Percentage of signal used for final estimate (default: 0.2). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per channel, containing columns:
Integer channel index.
Ratio of final to initial median frequency. Values less than 1 indicate fatigue.
Mean median frequency (Hz) in the initial portion.
Mean median frequency (Hz) in the final portion.
De Luca, C.J. (1984). "Myoelectrical manifestations of localized muscular fatigue in humans." Critical Reviews in Biomedical Engineering, 11(4), 251-279.
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigue() for detailed windowed fatigue tracking,
emgSpectralMoments() for spectral moment analysis,
emgEnvelope() for amplitude envelope extraction
pe <- make_emg_fatigue(n_time = 10000, sr = 1000) emgFatigueIndex(pe)pe <- make_emg_fatigue(n_time = 10000, sr = 1000) emgFatigueIndex(pe)
Fits a linear regression of a spectral fatigue metric (median or mean frequency) against time across sliding windows and reports the fatigue slope per channel. A negative slope indicates myoelectric fatigue (spectral compression toward lower frequencies).
emgFatigueSlope( x, feature = c("mdf", "mnf"), normalize = TRUE, window_sec = 1, overlap = 0.5, assay_name = NULL )emgFatigueSlope( x, feature = c("mdf", "mnf"), normalize = TRUE, window_sec = 1, overlap = 0.5, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
feature |
Metric to regress: "mdf" (median frequency) or "mnf" (mean frequency). |
normalize |
If TRUE, also report the slope as a percentage of the initial (intercept) value per minute. |
window_sec |
Analysis window in seconds (default: 1.0). |
overlap |
Overlap fraction between windows (default: 0.5). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per channel, containing columns:
Integer channel index.
Regression slope in Hz per minute (negative under fatigue).
Slope as a percentage of the initial value
per minute (NA if normalize = FALSE).
Fitted value at time zero (initial frequency), Hz.
Coefficient of determination of the fit.
Two-sided p-value for the slope.
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigue() for the underlying windowed frequency estimates,
emgDimitrovIndex() for the spectral-moment fatigue index,
emgFatigueIndex() for the initial/final ratio metric
# decreasing-frequency (fatiguing) signal sr <- 1000; n <- 8000; t <- seq_len(n) / sr f <- 90 - 3 * t # 90 Hz falling to ~66 Hz sig <- sin(2 * pi * cumsum(f) / sr) + rnorm(n, sd = 0.1) pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = sr) emgFatigueSlope(pe, feature = "mdf")# decreasing-frequency (fatiguing) signal sr <- 1000; n <- 8000; t <- seq_len(n) / sr f <- 90 - 3 * t # 90 Hz falling to ~66 Hz sig <- sin(2 * pi * cumsum(f) / sr) + rnorm(n, sd = 0.1) pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = sr) emgFatigueSlope(pe, feature = "mdf")
Adds metadata/KG context to high-weight network edges. This function does not require a specific backend and works with user-provided tables exported from PhysioAnnotationHub/physioKG workflows.
emgInterpretNetworkKG( network, node_metadata = NULL, kg_edges = NULL, threshold = NULL, top_n = 20L, window = NULL )emgInterpretNetworkKG( network, node_metadata = NULL, kg_edges = NULL, threshold = NULL, top_n = 20L, window = NULL )
network |
A square matrix (channels x channels) or 3D array (window x channels x channels). |
node_metadata |
Optional data.frame containing at least |
kg_edges |
Optional data.frame of KG links. Expected columns are
|
threshold |
Optional edge threshold. Default is 75th percentile of upper-triangle weights. |
top_n |
Maximum number of edges returned after thresholding. |
window |
Optional window index when |
A list with:
Ranked edge table with optional metadata/KG annotations.
Applied threshold value.
Matrix used for interpretation.
List of summary statistics.
Relation counts from matched KG links, or NULL.
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) net <- emgCoherenceNetwork(pe, freq_band = c(20, 150)) interp <- emgInterpretNetworkKG(net$network, top_n = 5) interp$edge_tablepe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) net <- emgCoherenceNetwork(pe, freq_band = c(20, 150)) interp <- emgInterpretNetworkKG(net$network, top_n = 5) interp$edge_table
Estimates muscle-fiber conduction velocity from the propagation delay of the EMG signal between pairs of electrodes aligned along the fiber direction. The delay between the two channels of each pair is estimated either by cross-correlation (with parabolic sub-sample interpolation) or from the slope of the cross-spectrum phase, and the velocity is the inter-electrode distance divided by the delay.
emgMFCV( x, electrode_pairs, ied_mm, method = c("xcorr", "phase"), band = c(20, 250), max_lag_ms = 25, assay_name = NULL )emgMFCV( x, electrode_pairs, ied_mm, method = c("xcorr", "phase"), band = c(20, 250), max_lag_ms = 25, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
electrode_pairs |
Channel pairs aligned along the muscle fibres, given
as a list of length-2 integer vectors |
ied_mm |
Inter-electrode distance in millimetres. A single value applied to all pairs, or one value per pair. |
method |
Delay-estimation method: "xcorr" (cross-correlation, default) or "phase" (cross-spectrum phase slope). |
band |
Frequency band |
max_lag_ms |
Maximum delay searched by the "xcorr" method, in milliseconds (default: 25). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per electrode pair, containing columns:
Integer pair index.
Proximal and distal channel indices.
Estimated propagation delay in milliseconds.
Conduction velocity in metres per second
(ied_mm / delay).
Peak cross-correlation ("xcorr") or phase-fit R^2 ("phase").
Farina, D. & Merletti, R. (2000). "Comparison of algorithms for estimation of EMG variables during voluntary isometric contractions." Journal of Electromyography and Kinesiology, 10(5), 337-349. doi:10.1016/S1050-6411(00)00025-0
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigueSlope() and emgDimitrovIndex() for spectral fatigue
metrics, emgFatigue() for median/mean frequency tracking
# two electrodes 10 mm apart; distal is the proximal signal delayed 3 samples sr <- 2000 set.seed(1) prox <- as.numeric(stats::filter(rnorm(4000), rep(1, 5), sides = 2)) prox[is.na(prox)] <- 0 dist <- c(rep(0, 3), prox[seq_len(length(prox) - 3)]) pe <- PhysioExperiment(assays = list(raw = cbind(prox, dist)), samplingRate = sr) emgMFCV(pe, electrode_pairs = list(c(1, 2)), ied_mm = 10)# two electrodes 10 mm apart; distal is the proximal signal delayed 3 samples sr <- 2000 set.seed(1) prox <- as.numeric(stats::filter(rnorm(4000), rep(1, 5), sides = 2)) prox[is.na(prox)] <- 0 dist <- c(rep(0, 3), prox[seq_len(length(prox) - 3)]) pe <- PhysioExperiment(assays = list(raw = cbind(prox, dist)), samplingRate = sr) emgMFCV(pe, electrode_pairs = list(c(1, 2)), ied_mm = 10)
Identifies when muscle activation begins and ends. The Hodges-Bui method
rectifies the signal, smooths it with a centered moving average
(smooth_ms, as in the original method's low-pass conditioning), and
thresholds the smoothed trace at a multiple of baseline standard
deviations. The Teager-Kaiser method applies the Teager-Kaiser energy
operator, then the same rectify-and-smooth conditioning, before
thresholding. Without smoothing (smooth_ms = 0) the rectified
band-limited signal dips below threshold at every carrier zero-crossing, so
contiguous suprathreshold runs rarely reach min_duration_ms and
genuine bursts go undetected. The Bonato method uses a statistical
double-threshold on the (optionally whitened) signal, declaring activation
when at least r of m successive samples exceed a chi-square
threshold set from a target false-alarm probability. The AGLR method applies
Staude & Wolf's approximated generalized-likelihood-ratio change detector for
a variance increase and refines the onset to the estimated change point.
emgOnsetDetect( x, method = c("hodges_bui", "teager_kaiser", "bonato", "aglr"), threshold_sd = 3, baseline_sec = 0.2, min_duration_ms = 50, smooth_ms = 25, r = 5L, m = 10L, whiten = TRUE, false_alarm = 0.05, assay_name = NULL )emgOnsetDetect( x, method = c("hodges_bui", "teager_kaiser", "bonato", "aglr"), threshold_sd = 3, baseline_sec = 0.2, min_duration_ms = 50, smooth_ms = 25, r = 5L, m = 10L, whiten = TRUE, false_alarm = 0.05, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
method |
Detection method: "hodges_bui" (baseline SD threshold), "teager_kaiser" (Teager-Kaiser energy operator), "bonato" (Bonato double-threshold) or "aglr" (approximated generalized likelihood ratio). |
threshold_sd |
Number of baseline SDs above mean for the threshold in the "hodges_bui" and "teager_kaiser" methods (default: 3). |
baseline_sec |
Duration of the baseline period in seconds from the signal start, used to estimate baseline statistics (default: 0.2). |
min_duration_ms |
Minimum activation duration in ms to accept (default: 50). |
smooth_ms |
Width in ms of the centered moving-average window applied to the rectified signal ("hodges_bui") or rectified Teager-Kaiser trace ("teager_kaiser") before baseline estimation and thresholding (default: 25, in the 10–50 ms range examined by Hodges & Bui 1996). Set to 0 to disable smoothing (pre-0.2.1 behavior). Ignored by the "bonato" and "aglr" methods, which operate on unsmoothed samples by design. |
r, m
|
Bonato double-threshold parameters: activation requires at least
|
whiten |
If TRUE (default), whiten the signal with an AR model estimated from the baseline before applying the "bonato"/"aglr" statistics, so the baseline approximates white Gaussian noise. |
false_alarm |
Target false-alarm probability that sets the detection threshold for the "bonato" (per m-sample window) and "aglr" (per sample) methods (default: 0.05). |
assay_name |
Input assay name (default: first assay). |
A list with two data.frames:
A data.frame with columns channel (integer channel
index), sample (sample index of onset), and time_sec
(onset time in seconds). For "bonato"/"aglr" a stat column holds
the peak detector statistic of the activation.
A data.frame with the matching channel,
sample, time_sec (and stat) for each offset.
If no activations are detected, both data.frames have zero rows.
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. doi:10.1016/S0921-884X(96)95190-5
Bonato, P., D'Alessio, T. & Knaflitz, M. (1998). "A statistical method for the measurement of muscle activation intervals from surface myoelectric signal during gait." IEEE Transactions on Biomedical Engineering, 45(3), 287-299. doi:10.1109/10.661154
Staude, G. & Wolf, W. (1999). "Objective motor response onset detection in surface myoelectric signals." Medical Engineering & Physics, 21(6-7), 449-467. doi:10.1016/S1350-4533(99)00067-3
Solnik, S., Rider, P., Steinweg, K., DeVita, P. & Hortobagyi, T. (2010). "Teager-Kaiser energy operator signal conditioning improves EMG onset detection." European Journal of Applied Physiology, 110(3), 489-498. doi:10.1007/s00421-010-1521-8
emgEnvelope() for computing amplitude envelopes,
emgAmplitudeNormalize() for amplitude normalization,
emgFatigue() for fatigue analysis
pe <- make_emg_contraction() emgOnsetDetect(pe, method = "bonato")$onsetspe <- make_emg_contraction() emgOnsetDetect(pe, method = "bonato")$onsets
Builds a static network from pairwise partial coherence, estimated from the inverse cross-spectral density matrix at each frequency.
emgPartialCoherenceNetwork( x, freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL, ridge = 1e-06 )emgPartialCoherenceNetwork( x, freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL, ridge = 1e-06 )
x |
A PhysioExperiment object. |
freq_band |
Optional numeric vector |
channels |
Integer vector of channel indices to include. If NULL, uses all. |
nperseg |
Segment length for Welch estimation (default: 256). |
noverlap |
Overlap length (default: |
assay_name |
Input assay name. If NULL, uses default assay. |
aggregate |
Aggregation across frequency bins: "mean", "max", or "median". |
threshold |
Optional threshold for binary adjacency matrix. |
ridge |
Ridge regularization added to spectral matrix inversion. |
A list with:
Numeric matrix (channel x channel) of partial coherence.
Logical matrix after thresholding, or NULL.
3D array (freq x channel x channel).
Frequency vector (Hz).
Channel labels used in the network.
emgCoherenceNetwork(), emgWPLINetwork()
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgPartialCoherenceNetwork(pe, freq_band = c(20, 150)) res$networkpe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgPartialCoherenceNetwork(pe, freq_band = c(20, 150)) res$network
Applies pass/fail thresholds to the emgQualityCheck() metrics and reports,
per channel, whether the signal is acceptable and, if not, why.
emgQCgate( x, min_snr = 6, max_powerline = 0.1, max_clipping = 1, reject_ecg = TRUE, assay_name = NULL, ... )emgQCgate( x, min_snr = 6, max_powerline = 0.1, max_clipping = 1, reject_ecg = TRUE, assay_name = NULL, ... )
x |
A PhysioExperiment object with EMG data. |
min_snr |
Minimum acceptable SNR in dB (default: 6). |
max_powerline |
Maximum acceptable power-line ratio (default: 0.10). |
max_clipping |
Maximum acceptable clipping percentage (default: 1). |
reject_ecg |
If TRUE (default), ECG-contaminated channels fail the gate. |
assay_name |
Input assay name (default: first assay). |
... |
Passed to |
A list with pass (TRUE if every channel passes) and
channels, a data.frame with columns channel, pass and
reasons (a comma-separated string of failed criteria, or "").
McManus, L., De Vito, G. & Lowery, M.M. (2020). "Analysis and biophysics of surface EMG for physiotherapists and kinesiologists." Frontiers in Neurology, 11, 576729. doi:10.3389/fneur.2020.576729
emgQualityCheck(), emgRemoveECG()
set.seed(1) sig <- rnorm(4000, sd = 0.02); sig[1500:2500] <- rnorm(1001, sd = 0.4) pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = 1000) emgQCgate(pe)$passset.seed(1) sig <- rnorm(4000, sd = 0.02); sig[1500:2500] <- rnorm(1001, sd = 0.4) pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = 1000) emgQCgate(pe)$pass
Computes per-channel surface-EMG quality metrics: signal-to-noise ratio, resting baseline noise, power-line interference ratio at 50 and 60 Hz, clipping/saturation percentage, and an ECG-contamination flag based on the periodicity of the QRS-band envelope. When the PhysioECG package is available the R-peak count it detects is reported as an additional diagnostic.
emgQualityCheck( x, ecg_score_threshold = 0.5, rms_window_ms = 100, assay_name = NULL )emgQualityCheck( x, ecg_score_threshold = 0.5, rms_window_ms = 100, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
ecg_score_threshold |
QRS-band periodicity score above which a channel is flagged as ECG-contaminated (default: 0.5). |
rms_window_ms |
Window (ms) for the moving-RMS used to estimate signal and rest levels (default: 100). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per channel:
Integer channel index.
Signal-to-noise ratio in dB (active vs rest RMS).
Resting baseline RMS (noise floor).
Fraction of total power within +/-1 Hz of 50 and 60 Hz.
The larger of powerline_50/powerline_60.
Percentage of samples at the signal extremes (ADC saturation).
QRS-band periodicity score from 0 (none) to 1 (strong).
TRUE if ecg_score exceeds the threshold.
McManus, L., De Vito, G. & Lowery, M.M. (2020). "Analysis and biophysics of surface EMG for physiotherapists and kinesiologists." Frontiers in Neurology, 11, 576729. doi:10.3389/fneur.2020.576729
emgRemoveECG() to remove ECG contamination,
emgQCgate() for pass/fail gating, emgEnvelope()
set.seed(1) sig <- rnorm(4000, sd = 0.02) sig[1500:2500] <- rnorm(1001, sd = 0.4) # a contraction pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = 1000) emgQualityCheck(pe)set.seed(1) sig <- rnorm(4000, sd = 0.02) sig[1500:2500] <- rnorm(1001, sd = 0.4) # a contraction pe <- PhysioExperiment(assays = list(raw = matrix(sig, ncol = 1)), samplingRate = 1000) emgQualityCheck(pe)
Removes cardiac (ECG/QRS) contamination from surface EMG using a high-pass filter, QRS template subtraction, or gating (blanking) of the QRS complexes.
emgRemoveECG( x, method = c("highpass", "template", "gating"), ecg_channel = NULL, hp_cutoff = 30, qrs_ms = 60, assay_name = NULL, output_assay = "ecg_removed" )emgRemoveECG( x, method = c("highpass", "template", "gating"), ecg_channel = NULL, hp_cutoff = 30, qrs_ms = 60, assay_name = NULL, output_assay = "ecg_removed" )
x |
A PhysioExperiment object with EMG data. |
method |
Removal method: "highpass" (default; FFT high-pass at
|
ecg_channel |
Optional channel index of a dedicated ECG reference used for R-peak detection in the "template"/"gating" methods; if NULL, R-peaks are detected from each EMG channel itself. |
hp_cutoff |
High-pass cutoff in Hz for the "highpass" method (default: 30). |
qrs_ms |
Half-width in ms of the QRS window for "template"/"gating" (default: 60). |
assay_name |
Input assay name (default: first assay). |
output_assay |
Output assay name (default: "ecg_removed"). |
A PhysioExperiment object with an added assay output_assay
containing the cleaned signal.
Drake, J.D.M. & Callaghan, J.P. (2006). "Elimination of electrocardiogram contamination from electromyogram signals: An evaluation of currently used removal techniques." Journal of Electromyography and Kinesiology, 16(2), 175-187. doi:10.1016/j.jelekin.2005.07.003
Willigenburg, N.W., Daffertshofer, A., Kingma, I. & van Dieen, J.H. (2012). "Removing ECG contamination from EMG recordings." Journal of Electromyography and Kinesiology, 22(3), 485-493. doi:10.1016/j.jelekin.2012.01.001
emgQualityCheck() to detect contamination, emgQCgate()
set.seed(1) sr <- 1000; n <- 5000 emg <- rnorm(n, sd = 0.1) qrs <- numeric(n) for (p in seq(500, n - 200, by = 1000)) qrs[p + -15:15] <- 3 * exp(-(-15:15)^2 / 20) pe <- PhysioExperiment(assays = list(raw = matrix(emg + qrs, ncol = 1)), samplingRate = sr) clean <- emgRemoveECG(pe, method = "highpass")set.seed(1) sr <- 1000; n <- 5000 emg <- rnorm(n, sd = 0.1) qrs <- numeric(n) for (p in seq(500, n - 200, by = 1000)) qrs[p + -15:15] <- 3 * exp(-(-15:15)^2 / 20) pe <- PhysioExperiment(assays = list(raw = matrix(emg + qrs, ncol = 1)), samplingRate = sr) clean <- emgRemoveECG(pe, method = "highpass")
Computes spectral moments (M0, M1, M2) over sliding windows. M0 is total power, M1 is the first spectral moment (related to mean frequency), and M2 is the second moment (related to bandwidth). These can be combined to derive the mean frequency (M1/M0) and spectral bandwidth.
emgSpectralMoments(x, window_sec = 1, overlap = 0.5, assay_name = NULL)emgSpectralMoments(x, window_sec = 1, overlap = 0.5, assay_name = NULL)
x |
A PhysioExperiment object with EMG data. |
window_sec |
Analysis window in seconds (default: 1.0). |
overlap |
Overlap fraction (default: 0.5). |
assay_name |
Input assay name (default: first assay). |
A data.frame with one row per channel per window, containing columns:
Integer channel index.
Integer window number (1-indexed).
Zeroth spectral moment (total power).
First spectral moment (frequency-weighted power).
Second spectral moment (frequency-squared-weighted power).
De Luca, C.J. (1984). "Myoelectrical manifestations of localized muscular fatigue in humans." Critical Reviews in Biomedical Engineering, 11(4), 251-279.
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigue() for median and mean frequency tracking,
emgFatigueIndex() for summary fatigue metric,
emgEnvelope() for time-domain amplitude analysis
pe <- make_emg_fatigue(n_time = 10000, sr = 1000) sm <- emgSpectralMoments(pe, window_sec = 1.0) head(sm)pe <- make_emg_fatigue(n_time = 10000, sr = 1000) sm <- emgSpectralMoments(pe, window_sec = 1.0) head(sm)
Builds a static network from pairwise weighted phase-lag index (wPLI), which is less sensitive to zero-lag coupling artifacts.
emgWPLINetwork( x, freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL, debiased = FALSE )emgWPLINetwork( x, freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, assay_name = NULL, aggregate = c("mean", "max", "median"), threshold = NULL, debiased = FALSE )
x |
A PhysioExperiment object. |
freq_band |
Optional numeric vector |
channels |
Integer vector of channel indices to include. If NULL, uses all. |
nperseg |
Segment length for Welch estimation (default: 256). |
noverlap |
Overlap length (default: |
assay_name |
Input assay name. If NULL, uses default assay. |
aggregate |
Aggregation across frequency bins: "mean", "max", or "median". |
threshold |
Optional threshold for binary adjacency matrix. |
debiased |
Logical; if TRUE, uses debiased wPLI estimator. |
A list with:
Numeric matrix (channel x channel) of wPLI values.
Logical matrix after thresholding, or NULL.
3D array (freq x channel x channel).
Frequency vector (Hz).
Channel labels used in the network.
emgCoherenceNetwork(), emgPartialCoherenceNetwork()
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgWPLINetwork(pe, freq_band = c(20, 150)) res$networkpe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) res <- emgWPLINetwork(pe, freq_band = c(20, 150)) res$network
Retrieves the SENIAM electrode/muscle metadata previously attached with
setEMGElectrode().
getEMGElectrode(x)getEMGElectrode(x)
x |
A PhysioExperiment object. |
A data.frame with one row per channel and columns channel,
muscle, placement, orientation, ied_mm,
reference and side (NA where unset).
setEMGElectrode(), seniamMuscles()
pe <- PhysioExperiment(assays = list(raw = matrix(rnorm(100), ncol = 1)), samplingRate = 1000) pe <- setEMGElectrode(pe, muscle = "Tibialis anterior") getEMGElectrode(pe)pe <- PhysioExperiment(assays = list(raw = matrix(rnorm(100), ncol = 1)), samplingRate = 1000) pe <- setEMGElectrode(pe, muscle = "Tibialis anterior") getEMGElectrode(pe)
Convenience bridge that forwards to
PhysioHDEMG::hdEMGDecompose() for high-density surface EMG
motor-unit decomposition by convolutive blind source separation. The
PhysioHDEMG package must be installed. See
PhysioHDEMG::hdEMGDecompose() for
the full argument list and return value.
hdEMGDecompose(x, ...)hdEMGDecompose(x, ...)
x |
HD-sEMG data: an |
... |
Further arguments passed to |
An hdemg_decomposition object (see PhysioHDEMG).
muscleSynergy() for whole-muscle synergy decomposition.
# Requires the optional PhysioHDEMG backend (Suggests). if (requireNamespace("PhysioHDEMG", quietly = TRUE)) { sim <- PhysioHDEMG::make_hdemg_sim(n_units = 2, duration_sec = 1) dec <- hdEMGDecompose(sim, n_units = 4) }# Requires the optional PhysioHDEMG backend (Suggests). if (requireNamespace("PhysioHDEMG", quietly = TRUE)) { sim <- PhysioHDEMG::make_hdemg_sim(n_units = 2, duration_sec = 1) dec <- hdEMGDecompose(sim, n_units = 4) }
Generates a synthetic multi-channel EMG PhysioExperiment object with
simulated muscle activity. Each channel contains baseline noise with a burst
of higher-amplitude activity in the middle 40 percent of the signal
(from 30 to 70 percent), mimicking a typical voluntary contraction.
make_emg(n_time = 2000, n_channels = 4, sr = 1000)make_emg(n_time = 2000, n_channels = 4, sr = 1000)
n_time |
Number of time points (default: 2000). |
n_channels |
Number of EMG channels (default: 4). |
sr |
Sampling rate in Hz (default: 1000). |
A PhysioExperiment object with a single "raw" assay
containing simulated EMG data (time x channels matrix), channel metadata
in colData, and the specified sampling rate.
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
make_emg_contraction() for EMG with a defined contraction window,
make_emg_fatigue() for EMG with fatigue progression,
emgEnvelope() for extracting amplitude envelopes
pe <- make_emg() pe dim(SummarizedExperiment::assay(pe, "raw"))pe <- make_emg() pe dim(SummarizedExperiment::assay(pe, "raw"))
Generates a synthetic single-channel EMG PhysioExperiment with a
clearly defined contraction period. The contraction region has substantially
higher amplitude than the baseline, making it suitable for testing onset
detection algorithms.
make_emg_contraction( n_time = 5000, sr = 1000, contraction_start = 0.3, contraction_end = 0.7, baseline_sd = 0.01, contraction_sd = 0.5 )make_emg_contraction( n_time = 5000, sr = 1000, contraction_start = 0.3, contraction_end = 0.7, baseline_sd = 0.01, contraction_sd = 0.5 )
n_time |
Number of time points (default: 5000). |
sr |
Sampling rate in Hz (default: 1000). |
contraction_start |
Proportion of signal where contraction begins (default: 0.3). |
contraction_end |
Proportion of signal where contraction ends (default: 0.7). |
baseline_sd |
Standard deviation of baseline noise (default: 0.01). |
contraction_sd |
Standard deviation of contraction activity (default: 0.5). |
A PhysioExperiment object with a single "raw" assay
containing a one-channel EMG signal with a known contraction window. The
contraction region is defined by contraction_start and
contraction_end as proportions of total signal length.
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. doi:10.1016/S0921-884X(96)95190-5
emgOnsetDetect() for detecting the contraction onset,
emgEnvelope() for extracting the amplitude envelope,
make_emg() for basic multi-channel EMG,
make_emg_fatigue() for EMG with fatigue progression
pe <- make_emg_contraction() pe onset <- emgOnsetDetect(pe) onset$onsetspe <- make_emg_contraction() pe onset <- emgOnsetDetect(pe) onset$onsets
Generates a synthetic single-channel EMG PhysioExperiment that
simulates a fatiguing isometric contraction. The median frequency of the
signal decreases progressively over time (from 80 Hz to approximately
44 Hz across 10 segments), mimicking the spectral compression
characteristic of muscle fatigue.
make_emg_fatigue(n_time = 10000, sr = 1000)make_emg_fatigue(n_time = 10000, sr = 1000)
n_time |
Number of time points (default: 10000). |
sr |
Sampling rate in Hz (default: 1000). |
A PhysioExperiment object with a single "raw" assay
containing a one-channel EMG signal exhibiting progressive median
frequency decrease across 10 equal-length segments.
De Luca, C.J. (1997). "The use of surface electromyography in biomechanics." Journal of Applied Biomechanics, 13(2), 135-163. doi:10.1123/jab.13.2.135
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigue() for tracking median frequency over time,
emgFatigueIndex() for computing a summary fatigue metric,
emgSpectralMoments() for spectral moment analysis,
make_emg() for basic multi-channel EMG
pe <- make_emg_fatigue() pe fatigue <- emgFatigue(pe) head(fatigue)pe <- make_emg_fatigue() pe fatigue <- emgFatigue(pe) head(fatigue)
Decomposes multi-channel EMG into muscle synergies using matrix factorization.
muscleSynergy( x, n_synergies, method = c("nmf", "pca", "ica"), n_restarts = 1L, max_iter = 200L, tol = 1e-04, seed = NULL, assay_name = NULL )muscleSynergy( x, n_synergies, method = c("nmf", "pca", "ica"), n_restarts = 1L, max_iter = 200L, tol = 1e-04, seed = NULL, assay_name = NULL )
x |
A PhysioExperiment object with multi-channel EMG. |
n_synergies |
Number of synergies to extract. |
method |
Decomposition method: "nmf" (non-negative matrix factorization), "pca" (principal component analysis), or "ica" (independent component analysis). |
n_restarts |
Number of random NMF restarts; the fit with the lowest reconstruction error (highest VAF) is returned (default: 1). Ignored for "pca". |
max_iter |
Maximum iterations for NMF (default: 200). |
tol |
Convergence tolerance for NMF (default: 1e-4). |
seed |
Optional integer seed for reproducible random initialization (NMF restarts and ICA). If set, the returned best-of-restarts solution is deterministic. |
assay_name |
Input assay name (default: first assay). |
A list with:
W: Synergy weight matrix (n_synergies x channels)
H: Activation pattern matrix (time x n_synergies)
vaf: Variance accounted for (0-1)
method: Method used
n_restarts: Number of restarts used
all_vaf: VAF of each NMF restart (NA for pca/ica)
convergence: For NMF, a list with the best restart's
iterations and converged flag (NULL otherwise)
original_data: Original data matrix for reconstruction
Lee, D.D. & Seung, H.S. (1999). "Learning the parts of objects by non-negative matrix factorization." Nature, 401(6755), 788-791. doi:10.1038/44565
Tresch, M.C., Cheung, V.C.K. & d'Avella, A. (2006). "Matrix factorization algorithms for the identification of muscle synergies." Journal of Neurophysiology, 95(4), 2199-2212. doi:10.1152/jn.00222.2005
muscleSynergyOrder() for model-order (synergy count) selection,
synergyReconstruct() for reconstructing data from synergies,
synergyCompare() for comparing synergy solutions
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) syn <- muscleSynergy(pe, n_synergies = 2, method = "nmf", seed = 1) syn$vaf dim(syn$W) # n_synergies x channelspe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) syn <- muscleSynergy(pe, n_synergies = 2, method = "nmf", seed = 1) syn$vaf dim(syn$W) # n_synergies x channels
Fits a mixture of factor analysers to per-subject muscle activation data to identify subgroups of subjects with distinct muscle-synergy structure, rather than forcing a single global synergy model on a heterogeneous population. Each cluster's factor loading matrix is that subgroup's synergy set.
muscleSynergyMixture( list_of_data, n_clusters, n_factors, method = c("mfa", "mpca"), max_iter = 100, tol = 1e-04, n_init = 5, seed = NULL )muscleSynergyMixture( list_of_data, n_clusters, n_factors, method = c("mfa", "mpca"), max_iter = 100, tol = 1e-04, n_init = 5, seed = NULL )
list_of_data |
A list of non-empty numeric matrices, one per subject,
each |
n_clusters |
Number of subgroups (mixture components). |
n_factors |
Number of synergies (factors) per subgroup. |
method |
|
max_iter |
Maximum EM iterations (default 100). |
tol |
Log-likelihood convergence tolerance (default 1e-4). |
n_init |
Random/k-means restarts; the best log-likelihood is kept (default 5). |
seed |
Optional integer seed. |
An object of class "synergy_mixture": a list with cluster
(subgroup label per subject), synergies (length-n_clusters list of
muscle x factor loading matrices), uniqueness (per-cluster diagonal
noise), mean (per-cluster mean), proportions (mixing weights),
responsibilities (subject x cluster), loglik, and bic.
Matsui Y. Mixture-Based Muscle Synergy Analysis. SSRN preprint. https://ssrn.com/abstract=6798399
Ghahramani Z, Hinton GE (1996). The EM algorithm for mixtures of factor analyzers. Technical Report CRG-TR-96-1, University of Toronto.
Matsui Y. synergyMixR: Mixture-Based Muscle Synergy Analysis. https://github.com/matsui-lab/synergyMixR
set.seed(1) M <- 6; r <- 2 LA <- matrix(abs(rnorm(M * r)), M, r) # subgroup A synergies LB <- matrix(abs(rnorm(M * r)), M, r) # subgroup B synergies gen <- function(L, n = 60) t(L %*% matrix(rnorm(r * n), r, n) + matrix(rnorm(M * n, 0, 0.3), M, n)) data <- c(replicate(6, gen(LA), simplify = FALSE), replicate(6, gen(LB), simplify = FALSE)) fit <- muscleSynergyMixture(data, n_clusters = 2, n_factors = 2, seed = 1) fit$clusterset.seed(1) M <- 6; r <- 2 LA <- matrix(abs(rnorm(M * r)), M, r) # subgroup A synergies LB <- matrix(abs(rnorm(M * r)), M, r) # subgroup B synergies gen <- function(L, n = 60) t(L %*% matrix(rnorm(r * n), r, n) + matrix(rnorm(M * n, 0, 0.3), M, n)) data <- c(replicate(6, gen(LA), simplify = FALSE), replicate(6, gen(LB), simplify = FALSE)) fit <- muscleSynergyMixture(data, n_clusters = 2, n_factors = 2, seed = 1) fit$cluster
Sweeps the number of synergies from 1 to max_synergies, fits a
multi-restart NMF at each, and selects the model order from the
variance-accounted-for (VAF) curve. The selected order is the smallest number
of synergies whose global VAF reaches vaf_threshold; if the threshold
is never reached, the knee (elbow) of the VAF curve is used instead.
muscleSynergyOrder( x, max_synergies, vaf_threshold = 0.9, per_synergy_vaf = 0.75, n_restarts = 10L, max_iter = 200L, tol = 1e-04, seed = NULL, assay_name = NULL )muscleSynergyOrder( x, max_synergies, vaf_threshold = 0.9, per_synergy_vaf = 0.75, n_restarts = 10L, max_iter = 200L, tol = 1e-04, seed = NULL, assay_name = NULL )
x |
A PhysioExperiment object with multi-channel EMG. |
max_synergies |
Maximum number of synergies to test (capped at the number of channels). |
vaf_threshold |
Global VAF threshold for selection (default: 0.90). |
per_synergy_vaf |
Per-channel VAF each muscle should reach at the selected order; reported as a quality check (default: 0.75). |
n_restarts |
Number of NMF restarts per synergy count (default: 10). |
max_iter |
Maximum NMF iterations (default: 200). |
tol |
NMF convergence tolerance (default: 1e-4). |
seed |
Optional integer seed making the whole sweep reproducible. |
assay_name |
Input assay name (default: first assay). |
A list with:
A data.frame with columns n_synergies and
vaf.
The selected number of synergies.
"vaf_threshold" or "knee".
The thresholds used.
Per-channel VAF at the selected order.
TRUE if every channel's VAF meets
per_synergy_vaf.
The muscleSynergy() result at the selected order.
Cheung, V.C.K. et al. (2005). "Central and sensory contributions to the activation and organization of muscle synergies during natural motor behaviors." Journal of Neuroscience, 25(27), 6419-6434. doi:10.1523/JNEUROSCI.4904-04.2005
Tresch, M.C., Cheung, V.C.K. & d'Avella, A. (2006). "Matrix factorization algorithms for the identification of muscle synergies." Journal of Neurophysiology, 95(4), 2199-2212. doi:10.1152/jn.00222.2005
muscleSynergy() for the underlying decomposition
# three synergies driving disjoint muscle pairs set.seed(1) tt <- seq_len(500) H <- sapply(c(150, 250, 350), function(c0) exp(-((tt - c0) / 40)^2)) W <- rbind(c(1, 1, 0, 0, 0, 0), c(0, 0, 1, 1, 0, 0), c(0, 0, 0, 0, 1, 1)) data <- H %*% W + matrix(abs(rnorm(500 * 6, sd = 0.02)), 500, 6) pe <- PhysioExperiment(assays = list(env = data), samplingRate = 1000) ord <- muscleSynergyOrder(pe, max_synergies = 5, seed = 1) ord$selected ord$vaf_curve# three synergies driving disjoint muscle pairs set.seed(1) tt <- seq_len(500) H <- sapply(c(150, 250, 350), function(c0) exp(-((tt - c0) / 40)^2)) W <- rbind(c(1, 1, 0, 0, 0, 0), c(0, 0, 1, 1, 0, 0), c(0, 0, 0, 0, 1, 1)) data <- H %*% W + matrix(abs(rnorm(500 * 6, sd = 0.02)), 500, 6) pe <- PhysioExperiment(assays = list(env = data), samplingRate = 1000) ord <- muscleSynergyOrder(pe, max_synergies = 5, seed = 1) ord$selected ord$vaf_curve
Non-negative CANDECOMP/PARAFAC of a three-way EMG tensor
muscle x time x trial. Each of the n_synergies components is a rank-one
term with a muscle weight vector, a temporal activation profile, and a
per-trial loading — the multiway analogue of a muscle synergy that captures
how the same spatial/temporal modules are reused and rescaled across trials or
conditions.
muscleSynergyTensor( tensor, n_synergies, max_iter = 500, tol = 1e-06, restarts = 5, seed = NULL )muscleSynergyTensor( tensor, n_synergies, max_iter = 500, tol = 1e-06, restarts = 5, seed = NULL )
tensor |
A non-negative numeric array with dimensions
|
n_synergies |
Number of synergies (rank) to extract. |
max_iter |
Maximum multiplicative-update iterations (default 500). |
tol |
Relative reconstruction-error tolerance for convergence (default 1e-6). |
restarts |
Random restarts; the best (lowest error) is kept (default 5). |
seed |
Optional integer seed for reproducibility. |
An object of class "synergy_tensor": a list with muscle_weights
(muscle x synergy), temporal (time x synergy), trial_loadings
(trial x synergy), vaf (variance accounted for), iterations, and
n_synergies.
Cichocki A, Zdunek R, Phan AH, Amari S (2009). Nonnegative Matrix and Tensor Factorizations. Wiley.
set.seed(1) M <- 6; Tt <- 50; K <- 8; R <- 2 A0 <- matrix(runif(M * R), M, R); B0 <- matrix(runif(Tt * R), Tt, R) C0 <- matrix(runif(K * R), K, R) X <- array(0, c(M, Tt, K)) for (r in 1:R) for (k in 1:K) X[, , k] <- X[, , k] + (A0[, r] %o% B0[, r]) * C0[k, r] fit <- muscleSynergyTensor(X, n_synergies = 2, restarts = 3, seed = 1) fit$vafset.seed(1) M <- 6; Tt <- 50; K <- 8; R <- 2 A0 <- matrix(runif(M * R), M, R); B0 <- matrix(runif(Tt * R), Tt, R) C0 <- matrix(runif(K * R), K, R) X <- array(0, c(M, Tt, K)) for (r in 1:R) for (k in 1:K) X[, , k] <- X[, , k] + (A0[, r] %o% B0[, r]) * C0[k, r] fit <- muscleSynergyTensor(X, n_synergies = 2, restarts = 3, seed = 1) fit$vaf
Fits non-negative PARAFAC for increasing rank and returns the smallest number
of synergies whose reconstruction VAF reaches vaf_threshold.
muscleSynergyTensorOrder(tensor, max_synergies = 6, vaf_threshold = 0.9, ...)muscleSynergyTensorOrder(tensor, max_synergies = 6, vaf_threshold = 0.9, ...)
tensor |
A non-negative |
max_synergies |
Largest rank to try (default 6). |
vaf_threshold |
VAF target (default 0.90). |
... |
Passed to |
A list with n_synergies (selected), vaf (per rank), and fit
(the selected "synergy_tensor").
set.seed(1) M <- 6; Tt <- 40; K <- 8; R <- 2 A0 <- matrix(runif(M * R), M, R); B0 <- matrix(runif(Tt * R), Tt, R) C0 <- matrix(runif(K * R), K, R) X <- array(0, c(M, Tt, K)) for (r in 1:R) for (k in 1:K) X[, , k] <- X[, , k] + (A0[, r] %o% B0[, r]) * C0[k, r] ord <- muscleSynergyTensorOrder(X, max_synergies = 3, seed = 1) ord$n_synergiesset.seed(1) M <- 6; Tt <- 40; K <- 8; R <- 2 A0 <- matrix(runif(M * R), M, R); B0 <- matrix(runif(Tt * R), Tt, R) C0 <- matrix(runif(K * R), K, R) X <- array(0, c(M, Tt, K)) for (r in 1:R) for (k in 1:K) X[, , k] <- X[, , k] + (A0[, r] %o% B0[, r]) * C0[k, r] ord <- muscleSynergyTensorOrder(X, max_synergies = 3, seed = 1) ord$n_synergies
Thin entry point that runs muscleSynergyMixture() directly on ecosystem
data: it reads each subject's time x muscle activation from a
PhysioExperiment (or accepts matrices) and fits the subgroup mixture.
physioSynergyMixture( pe_list, n_clusters, n_factors, method = c("mfa", "mpca"), assay_name = NULL, ... )physioSynergyMixture( pe_list, n_clusters, n_factors, method = c("mfa", "mpca"), assay_name = NULL, ... )
pe_list |
A list with one element per subject, each a |
n_clusters, n_factors
|
Number of subgroups and synergies (factors). |
method |
|
assay_name |
Assay to read from each PhysioExperiment (default: its default assay). |
... |
Further arguments passed to |
A "synergy_mixture" object.
set.seed(1) mk <- function(L) { pe <- PhysioExperiment::PhysioExperiment( assays = list(raw = t(L %*% matrix(rnorm(2 * 50), 2, 50) + matrix(rnorm(6 * 50, 0, .3), 6, 50))), samplingRate = 100) pe } LA <- matrix(abs(rnorm(12)), 6, 2); LB <- matrix(abs(rnorm(12)), 6, 2) pes <- c(replicate(5, mk(LA), simplify = FALSE), replicate(5, mk(LB), simplify = FALSE)) fit <- physioSynergyMixture(pes, n_clusters = 2, n_factors = 2, seed = 1) fit$clusterset.seed(1) mk <- function(L) { pe <- PhysioExperiment::PhysioExperiment( assays = list(raw = t(L %*% matrix(rnorm(2 * 50), 2, 50) + matrix(rnorm(6 * 50, 0, .3), 6, 50))), samplingRate = 100) pe } LA <- matrix(abs(rnorm(12)), 6, 2); LB <- matrix(abs(rnorm(12)), 6, 2) pes <- c(replicate(5, mk(LA), simplify = FALSE), replicate(5, mk(LB), simplify = FALSE)) fit <- physioSynergyMixture(pes, n_clusters = 2, n_factors = 2, seed = 1) fit$cluster
Overlays the agonist and antagonist envelopes and shades their common (co-contraction) area, annotated with the co-contraction index.
plotCoactivation( x, agonist, antagonist, method = c("falconer_winter", "rudolph", "frost"), normalize = TRUE, assay_name = NULL )plotCoactivation( x, agonist, antagonist, method = c("falconer_winter", "rudolph", "frost"), normalize = TRUE, assay_name = NULL )
x |
A PhysioExperiment object of EMG envelopes. |
agonist, antagonist
|
Channel index or label of the two muscles. |
method |
Co-contraction index for the annotation (see
|
normalize |
Peak-normalize each channel before plotting (default: TRUE). |
assay_name |
Input assay name (default: first assay). |
A ggplot object.
Falconer, K. & Winter, D.A. (1985). "Quantitative assessment of co-contraction at the ankle joint in walking." Electromyography and Clinical Neurophysiology, 25(2-3), 135-149.
t <- seq_len(1000) a <- exp(-((t - 400) / 120)^2); b <- exp(-((t - 600) / 120)^2) pe <- PhysioExperiment(assays = list(env = cbind(a, b)), colData = S4Vectors::DataFrame(label = c("TA", "GAS")), samplingRate = 1000) plotCoactivation(pe, "TA", "GAS")t <- seq_len(1000) a <- exp(-((t - 400) / 120)^2); b <- exp(-((t - 600) / 120)^2) pe <- PhysioExperiment(assays = list(env = cbind(a, b)), colData = S4Vectors::DataFrame(label = c("TA", "GAS")), samplingRate = 1000) plotCoactivation(pe, "TA", "GAS")
Plots the raw signal and its amplitude envelope for one channel, optionally
shading the resting noise floor from emgQualityCheck().
plotEmgEnvelope( x, channel = 1, method = "rms", window_ms = 50, qc = TRUE, assay_name = NULL )plotEmgEnvelope( x, channel = 1, method = "rms", window_ms = 50, qc = TRUE, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
channel |
Channel index or label to plot (default: 1). |
method |
Envelope method passed to |
window_ms |
Envelope window in ms (default: 50). |
qc |
If TRUE (default), shade the resting noise floor and annotate SNR. |
assay_name |
Input assay name (default: first assay). |
A ggplot object.
De Luca, C.J. (1997). "The use of surface electromyography in biomechanics." Journal of Applied Biomechanics, 13(2), 135-163. doi:10.1123/jab.13.2.135
emgEnvelope(), emgQualityCheck()
pe <- make_emg(n_time = 2000, n_channels = 1) plotEmgEnvelope(pe)pe <- make_emg(n_time = 2000, n_channels = 1) plotEmgEnvelope(pe)
Scatters the windowed median or mean frequency against time with the fitted fatigue-regression line and slope annotation.
plotEmgFatigue( x, feature = c("mdf", "mnf"), window_sec = 1, overlap = 0.5, assay_name = NULL )plotEmgFatigue( x, feature = c("mdf", "mnf"), window_sec = 1, overlap = 0.5, assay_name = NULL )
x |
A PhysioExperiment object with EMG data. |
feature |
"mdf" (median frequency) or "mnf" (mean frequency). |
window_sec, overlap
|
Windowing passed to |
assay_name |
Input assay name (default: first assay). |
A ggplot object (faceted by channel).
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
emgFatigue(), emgFatigueSlope()
pe <- make_emg_fatigue() plotEmgFatigue(pe)pe <- make_emg_fatigue() plotEmgFatigue(pe)
Plots the rectified signal with detected activation intervals shaded and onset/offset times marked.
plotEmgOnset(x, channel = 1, method = "hodges_bui", assay_name = NULL, ...)plotEmgOnset(x, channel = 1, method = "hodges_bui", assay_name = NULL, ...)
x |
A PhysioExperiment object with EMG data. |
channel |
Channel index or label to plot (default: 1). |
method |
Onset method passed to |
assay_name |
Input assay name (default: first assay). |
... |
Further arguments for |
A ggplot object.
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. doi:10.1016/S0921-884X(96)95190-5
pe <- make_emg_contraction() plotEmgOnset(pe)pe <- make_emg_contraction() plotEmgOnset(pe)
Renders a three-panel figure for a muscle-synergy decomposition: the synergy weight matrix W (heatmap), the activation time courses H, and the VAF curve.
plotSynergy(x)plotSynergy(x)
x |
A |
A patchwork/ggplot object combining the three panels.
Ting, L.H. & Chvatal, S.A. (2010). "Decomposing muscle activity in motor tasks: methods and interpretation." In Motor Control (Oxford).
muscleSynergy(), muscleSynergyOrder()
set.seed(1) tt <- seq_len(400) H <- sapply(c(120, 200, 280), function(c0) exp(-((tt - c0) / 30)^2)) W <- rbind(c(1, 1, 0, 0, 0, 0), c(0, 0, 1, 1, 0, 0), c(0, 0, 0, 0, 1, 1)) data <- H %*% W + matrix(abs(rnorm(400 * 6, sd = 0.02)), 400, 6) pe <- PhysioExperiment(assays = list(env = data), samplingRate = 1000) ord <- muscleSynergyOrder(pe, max_synergies = 5, seed = 1) plotSynergy(ord)set.seed(1) tt <- seq_len(400) H <- sapply(c(120, 200, 280), function(c0) exp(-((tt - c0) / 30)^2)) W <- rbind(c(1, 1, 0, 0, 0, 0), c(0, 0, 1, 1, 0, 0), c(0, 0, 0, 0, 1, 1)) data <- H %*% W + matrix(abs(rnorm(400 * 6, sd = 0.02)), 400, 6) pe <- PhysioExperiment(assays = list(env = data), samplingRate = 1000) ord <- muscleSynergyOrder(pe, max_synergies = 5, seed = 1) plotSynergy(ord)
Rebuilds the muscle x time x trial tensor from a Tucker core and factors;
exposed mainly for examples and testing.
reconstructTucker(core, spatial, temporal, trial)reconstructTucker(core, spatial, temporal, trial)
core |
A |
spatial, temporal, trial
|
Factor matrices. |
The reconstructed [muscle, time, trial] array.
set.seed(1) A <- matrix(stats::runif(6 * 2), 6, 2) B <- matrix(stats::runif(40 * 2), 40, 2) C <- matrix(stats::runif(8 * 2), 8, 2) G <- array(stats::runif(2 * 2 * 2), c(2, 2, 2)) X <- reconstructTucker(G, A, B, C) dim(X) # 6 x 40 x 8set.seed(1) A <- matrix(stats::runif(6 * 2), 6, 2) B <- matrix(stats::runif(40 * 2), 40, 2) C <- matrix(stats::runif(8 * 2), 8, 2) G <- array(stats::runif(2 * 2 * 2), c(2, 2, 2)) X <- reconstructTucker(G, A, B, C) dim(X) # 6 x 40 x 8
Returns the bundled catalog of SENIAM-recommended surface-EMG electrode locations (Hermens et al. 2000): for each muscle the recommended sensor location, the electrode orientation (fibre direction) and the recommended inter-electrode distance (mm).
seniamMuscles()seniamMuscles()
A data.frame with columns muscle, location,
orientation and ied_mm.
Hermens, H.J., Freriks, B., Disselhorst-Klug, C. & Rau, G. (2000). "Development of recommendations for SEMG sensors and sensor placement procedures." Journal of Electromyography and Kinesiology, 10(5), 361-374. doi:10.1016/S1050-6411(00)00027-4
setEMGElectrode() to attach electrode metadata to a
PhysioExperiment, getEMGElectrode() to retrieve it
head(seniamMuscles())head(seniamMuscles())
Attaches standardized SENIAM electrode/muscle metadata to the channels of a
PhysioExperiment, written as seniam_* columns of colData.
Muscle names are validated against seniamMuscles(); a recognized muscle
also back-fills its recommended electrode location, orientation and
inter-electrode distance when those are not supplied.
setEMGElectrode( x, muscle, placement = NULL, ied_mm = NULL, reference = NULL, side = NULL, channel = NULL )setEMGElectrode( x, muscle, placement = NULL, ied_mm = NULL, reference = NULL, side = NULL, channel = NULL )
x |
A PhysioExperiment object with EMG channels. |
muscle |
Muscle name(s) (character). Length 1 (applied to all targeted channels) or one per targeted channel. |
placement |
Optional electrode location text; back-filled from the
catalog when |
ied_mm |
Optional inter-electrode distance in mm; back-filled from the
catalog (SENIAM default 20 mm) when |
reference |
Optional reference-electrode description. |
side |
Optional body side ("left"/"right"/"L"/"R"/...). |
channel |
Channels to annotate: integer indices, channel labels, or
|
The PhysioExperiment with seniam_muscle,
seniam_placement, seniam_orientation, seniam_ied_mm,
seniam_reference and seniam_side added to colData.
Hermens, H.J. et al. (2000). "Development of recommendations for SEMG sensors and sensor placement procedures." Journal of Electromyography and Kinesiology, 10(5), 361-374. doi:10.1016/S1050-6411(00)00027-4
getEMGElectrode(), seniamMuscles(), emgMFCV() which can use the
stored inter-electrode distance
pe <- PhysioExperiment( assays = list(raw = matrix(rnorm(200), ncol = 2)), colData = S4Vectors::DataFrame(label = c("ch1", "ch2")), samplingRate = 1000) pe <- setEMGElectrode(pe, muscle = c("Biceps brachii", "Triceps brachii"), side = "right") getEMGElectrode(pe)pe <- PhysioExperiment( assays = list(raw = matrix(rnorm(200), ncol = 2)), colData = S4Vectors::DataFrame(label = c("ch1", "ch2")), samplingRate = 1000) pe <- setEMGElectrode(pe, muscle = c("Biceps brachii", "Triceps brachii"), side = "right") getEMGElectrode(pe)
Decomposes a set of trials' muscle x time EMG into shared SPATIAL synergies (which muscles group together), shared TEMPORAL synergies (when they are active) and a per-trial activation matrix linking them (Delis et al. 2014).
spaceByTimeSynergy( trials, n_spatial, n_temporal, max_iter = 500L, tol = 1e-06, n_restart = 5L, seed = NULL )spaceByTimeSynergy( trials, n_spatial, n_temporal, max_iter = 500L, tol = 1e-06, n_restart = 5L, seed = NULL )
trials |
A list of |
n_spatial, n_temporal
|
Numbers of spatial and temporal synergies. |
max_iter, tol
|
Multiplicative-update iterations and convergence tolerance. |
n_restart |
Random restarts (best kept; default 5). |
seed |
Optional RNG seed. |
a spacetime_synergy: spatial (muscle x n_spatial), temporal
(time x n_temporal), activation (list of n_spatial x n_temporal per
trial), vaf, iterations.
Delis I, et al. (2014) Front Comput Neurosci 8:118.
muscleSynergy(), tuckerSynergy()
set.seed(1) Ws <- matrix(stats::runif(6 * 2), 6, 2); Wt <- matrix(stats::runif(50 * 2), 50, 2) trials <- lapply(1:10, function(n) Ws %*% (matrix(stats::runif(4), 2, 2)) %*% t(Wt)) fit <- spaceByTimeSynergy(trials, 2, 2, n_restart = 2) fit$vafset.seed(1) Ws <- matrix(stats::runif(6 * 2), 6, 2); Wt <- matrix(stats::runif(50 * 2), 50, 2) trials <- lapply(1:10, function(n) Ws %*% (matrix(stats::runif(4), 2, 2)) %*% t(Wt)) fit <- spaceByTimeSynergy(trials, 2, 2, n_restart = 2) fit$vaf
Tests whether an inter-muscle coordination network is real signal or a
split-specific artefact: the recording is split into two independent halves,
the same network is built on each with coordinationNetwork(), and the
edge weights (upper triangle) and node strengths (row sums) are correlated
between the halves. A correlation near 1 means each half rebuilds the same
network. Different estimators trade sensitivity for specificity differently,
and a sparse graph's node strengths are more split-sensitive than its edges,
so build several and trust what reproduces in BOTH edges and topology.
splitHalfStability( x, methods = c("coherence", "partial_coherence", "wpli", "directed_gc"), freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, max_lag = 10L, debiased = FALSE, assay_name = NULL )splitHalfStability( x, methods = c("coherence", "partial_coherence", "wpli", "directed_gc"), freq_band = NULL, channels = NULL, nperseg = 256L, noverlap = NULL, max_lag = 10L, debiased = FALSE, assay_name = NULL )
x |
A |
methods |
Estimators to test (default: all four; see
|
freq_band, channels, nperseg, noverlap, max_lag, debiased, assay_name
|
Passed to |
A data frame (class split_half_stability), one row per method, with
method, split_half_edge_correlation,
split_half_node_strength_correlation, first_half_mean_edge, and
second_half_mean_edge.
coordinationNetwork(), emgCoordinationStructure()
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) splitHalfStability(pe, methods = "coherence", freq_band = c(20, 150))pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) splitHalfStability(pe, methods = "coherence", freq_band = c(20, 150))
Computes pairwise correlation between synergy weight vectors from two decompositions. Uses best-match pairing.
synergyCompare(result1, result2)synergyCompare(result1, result2)
result1 |
First result from |
result2 |
Second result from |
A data.frame with columns: synergy1, synergy2, correlation.
De Luca, C.J. (1997). "The use of surface electromyography in biomechanics." Journal of Applied Biomechanics, 13(2), 135-163. doi:10.1123/jab.13.2.135
Merletti, R. & Parker, P.A. (2004). "Electromyography: Physiology, Engineering, and Non-Invasive Applications." Wiley-IEEE Press. doi:10.1002/0471678384
muscleSynergy() for computing synergy decompositions,
synergyReconstruct() for reconstructing data from synergies
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) s1 <- muscleSynergy(pe, n_synergies = 2, method = "nmf", seed = 1) s2 <- muscleSynergy(pe, n_synergies = 2, method = "nmf", seed = 2) synergyCompare(s1, s2)pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) s1 <- muscleSynergy(pe, n_synergies = 2, method = "nmf", seed = 1) s2 <- muscleSynergy(pe, n_synergies = 2, method = "nmf", seed = 2) synergyCompare(s1, s2)
Reconstructs EMG data using a subset of synergies.
synergyReconstruct(synergy_result, n_synergies)synergyReconstruct(synergy_result, n_synergies)
synergy_result |
Result from |
n_synergies |
Number of synergies to use for reconstruction. |
A list with:
reconstructed: Reconstructed data matrix
vaf: VAF of the reconstruction
De Luca, C.J. (1997). "The use of surface electromyography in biomechanics." Journal of Applied Biomechanics, 13(2), 135-163. doi:10.1123/jab.13.2.135
muscleSynergy() for computing the initial decomposition,
synergyCompare() for comparing synergy solutions
pe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) syn <- muscleSynergy(pe, n_synergies = 3, method = "nmf", seed = 1) rec <- synergyReconstruct(syn, n_synergies = 2) rec$vafpe <- make_emg(n_time = 2000, n_channels = 4, sr = 1000) syn <- muscleSynergy(pe, n_synergies = 3, method = "nmf", seed = 1) rec <- synergyReconstruct(syn, n_synergies = 2) rec$vaf
A hierarchical tensor synergy model: factorises the muscle x time x trial
EMG tensor into non-negative spatial, temporal and trial factor matrices plus
a CORE tensor that weights every combination of them. Unlike PARAFAC
(muscleSynergyTensor()), the core is not diagonal, so a spatial synergy can
pair with several temporal synergies – capturing interactions PARAFAC cannot.
tuckerSynergy( trials, ranks, max_iter = 300L, tol = 1e-06, n_restart = 3L, seed = NULL )tuckerSynergy( trials, ranks, max_iter = 300L, tol = 1e-06, n_restart = 3L, seed = NULL )
trials |
A |
ranks |
Length-3 factor ranks |
max_iter, tol
|
Iterations and convergence tolerance. |
n_restart |
Random restarts (default 3). |
seed |
Optional RNG seed. |
a tucker_synergy: spatial (muscle x P), temporal (time x Q),
trial (trial x R), core (P x Q x R), vaf, iterations.
Kim YD, Choi S (2007) non-negative Tucker; Tucker LR (1966).
muscleSynergyTensor(), spaceByTimeSynergy()
set.seed(1) A <- matrix(stats::runif(6*2),6,2); B <- matrix(stats::runif(40*2),40,2); C <- matrix(stats::runif(8*2),8,2) G <- array(stats::runif(2*2*2), c(2,2,2)) X <- reconstructTucker(G, A, B, C) tuckerSynergy(X, ranks = c(2, 2, 2), n_restart = 1)$vafset.seed(1) A <- matrix(stats::runif(6*2),6,2); B <- matrix(stats::runif(40*2),40,2); C <- matrix(stats::runif(8*2),8,2) G <- array(stats::runif(2*2*2), c(2,2,2)) X <- reconstructTucker(G, A, B, C) tuckerSynergy(X, ranks = c(2, 2, 2), n_restart = 1)$vaf