| Title: | Raw Physiological Signal Analysis for Biological Rhythms Research |
|---|---|
| Description: | Raw physiological signal analysis for biological rhythms research. Provides ingestion, artefact detection, event detection, and feature extraction for multi-modal physiological recordings including polysomnography (EDF/EDF+), MRI-concurrent physiological logs (Philips PMU), and EEG. Covers cardiac rhythm (QRS detection, HRV), respiratory rhythm (apnoea detection, respiratory indices), and neural oscillations (sleep spindles, slow oscillations, automatic AASM sleep staging via a pre-trained LightGBM model ported from YASA; Vallat & Walker, 2021). Exports staged hypnograms to hypnor and derived metrics to syncR. Part of the Circadia Lab R ecosystem at Northumbria University. |
| Authors: | Lucas França [aut, cre], Mario Leocadio-Miguel [aut] (ORCID: <https://orcid.org/0000-0002-7248-3529>) |
| Maintainer: | Lucas França <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.4 |
| Built: | 2026-10-03 08:48:16 UTC |
| Source: | https://github.com/circadia-bio/mrpheus |
Designs a 4th-order Butterworth bandpass filter and applies it zero-phase
using gsignal::filtfilt(). Handles edge cases gracefully:
bandpass_filter(signal, sr, low_hz, high_hz)bandpass_filter(signal, sr, low_hz, high_hz)
signal |
Numeric vector. |
sr |
Numeric. Sampling rate in Hz. |
low_hz |
Numeric. Low cutoff frequency in Hz. Use |
high_hz |
Numeric. High cutoff frequency in Hz. |
low_hz <= 0 → low-pass at high_hz
high_hz >= Nyquist → high-pass at low_hz
Otherwise → bandpass between low_hz and high_hz
The high_hz cutoff is clamped to 99 % of Nyquist to avoid instability.
Because this uses a short IIR filter, it is safe on long recordings.
Filtered numeric vector, same length as signal.
## Not run: # Standard EEG bandpass filtered <- bandpass_filter(eeg_signal, sr = 256, low_hz = 0.3, high_hz = 35) # High-pass only (baseline drift removal) hp <- bandpass_filter(eeg_signal, sr = 256, low_hz = 0.1, high_hz = Inf) ## End(Not run)## Not run: # Standard EEG bandpass filtered <- bandpass_filter(eeg_signal, sr = 256, low_hz = 0.3, high_hz = 35) # High-pass only (baseline drift removal) hp <- bandpass_filter(eeg_signal, sr = 256, low_hz = 0.1, high_hz = Inf) ## End(Not run)
Calculates the AHI from respiratory event data and total sleep time.
compute_ahi(events, tst_hours)compute_ahi(events, tst_hours)
events |
Output of |
tst_hours |
Numeric. Total sleep time in hours. |
Numeric scalar. AHI (events per hour).
Estimates power spectral density (PSD) using Welch's method and integrates power within standard EEG frequency bands for each epoch and channel.
compute_band_power( psg, channels = NULL, bands = list(delta = c(0.5, 4), theta = c(4, 8), alpha = c(8, 12), sigma = c(12, 16), beta = c(16, 30), gamma = c(30, 45)), relative = FALSE, win_sec = 4, noverlap = 200L, nfft = 1024L )compute_band_power( psg, channels = NULL, bands = list(delta = c(0.5, 4), theta = c(4, 8), alpha = c(8, 12), sigma = c(12, 16), beta = c(16, 30), gamma = c(30, 45)), relative = FALSE, win_sec = 4, noverlap = 200L, nfft = 1024L )
psg |
An |
channels |
Character vector. Channel labels to include. If |
bands |
Named list of length-2 numeric vectors defining frequency bands
in Hz. Default matches YASA's list(delta = c(0.5, 4), theta = c(4, 8), alpha = c(8, 12),
sigma = c(12, 16), beta = c(16, 30), gamma = c(30, 45))
|
relative |
Logical. If |
win_sec |
Numeric. Welch window length in seconds. Default |
noverlap |
Integer. Number of overlapping samples between Welch windows.
Default |
nfft |
Integer. FFT length. Default |
The Welch implementation matches scipy.signal.welch with YASA's default
parameters (win_sec = 4, noverlap = 200, nfft = 1024, Hann window,
constant detrend, mean averaging). Relative power matches YASA's
relative = True behaviour: each band is divided by the sum of all band
powers (not the total PSD integral).
A tibble with columns epoch, channel, one column per named band,
and total_power (sum of all band powers, in V^2/Hz units before any
relative scaling).
## Not run: bp <- compute_band_power(psg) bp <- compute_band_power(psg, channels = "EEG Fpz-Cz", relative = TRUE) # Custom bands bp <- compute_band_power( psg, bands = list(slow = c(0.5, 1), delta = c(1, 4), theta = c(4, 8)) ) ## End(Not run)## Not run: bp <- compute_band_power(psg) bp <- compute_band_power(psg, channels = "EEG Fpz-Cz", relative = TRUE) # Custom bands bp <- compute_band_power( psg, bands = list(slow = c(0.5, 1), delta = c(1, 4), theta = c(4, 8)) ) ## End(Not run)
Converts R-peak sample indices (from detect_qrs()) into a
sample-by-sample instantaneous heart rate trace by filling each inter-beat
interval with the constant HR derived from that interval's length.
compute_hr_signal(qrs)compute_hr_signal(qrs)
qrs |
A |
Numeric vector of length max(qrs$qrs_i). Each sample contains
the instantaneous heart rate in beats per minute for the inter-beat
interval it falls within. Samples before the first detected R-peak are 0.
detect_qrs(), compute_hrv_sleep()
## Not run: rec <- read_philips_physlog("sub-01_physlog.log") qrs <- detect_qrs(rec$C[, "v1raw"], fs = rec$HDR$sfreq) hr <- compute_hr_signal(qrs) plot(seq_along(hr) / rec$HDR$sfreq, hr, type = "l", xlab = "Time (s)", ylab = "HR (bpm)") ## End(Not run)## Not run: rec <- read_philips_physlog("sub-01_physlog.log") qrs <- detect_qrs(rec$C[, "v1raw"], fs = rec$HDR$sfreq) hr <- compute_hr_signal(qrs) plot(seq_along(hr) / rec$HDR$sfreq, hr, type = "l", xlab = "Time (s)", ylab = "HR (bpm)") ## End(Not run)
Extracts R-peak positions from the ECG channel, computes standard time- and frequency-domain heart rate variability (HRV) metrics, and stratifies results by sleep stage.
compute_hrv_sleep( psg, staging = NULL, ecg_channel = NULL, min_rr_ms = 300, max_rr_ms = 2000 )compute_hrv_sleep( psg, staging = NULL, ecg_channel = NULL, min_rr_ms = 300, max_rr_ms = 2000 )
psg |
An |
staging |
Tibble from |
ecg_channel |
Character or |
min_rr_ms |
Numeric. Minimum physiologically plausible RR interval (ms).
Default |
max_rr_ms |
Numeric. Maximum physiologically plausible RR interval (ms).
Default |
A tibble with one row per sleep stage (or one row if staging is
NULL):
Character. AASM stage or "ALL".
Integer. Number of epochs in this stage.
Numeric. Mean RR interval (ms).
Numeric. SDNN — SD of all NN intervals.
Numeric. RMSSD — root mean square of successive differences.
Numeric. LF band power (0.04–0.15 Hz).
Numeric. HF band power (0.15–0.4 Hz).
Numeric. LF/HF ratio.
Calculates the ODI (number of >= 3% SpO2 desaturations per hour of sleep) from the SpO2 channel.
compute_odi(psg, spo2_channel, tst_hours, threshold = 3)compute_odi(psg, spo2_channel, tst_hours, threshold = 3)
psg |
An |
spo2_channel |
Character. SpO2 channel label. |
tst_hours |
Numeric. Total sleep time in hours. |
threshold |
Numeric. Desaturation threshold (percentage points).
Default |
Numeric scalar. ODI (desaturations per hour).
Detects slow oscillations (SOs) in EEG channels using a zero-crossing approach in the delta band (0.5-2 Hz), following the algorithm described in Molle et al. (2002) and implemented in YASA (Vallat & Walker, 2021).
compute_slow_oscillations( psg, channel = NULL, stages = NULL, freq_so = c(0.5, 2), amp_ptp_threshold_uv = c(75, 500), duration_pos_s = c(0.1, 1), duration_neg_s = c(0.1, 1.5) )compute_slow_oscillations( psg, channel = NULL, stages = NULL, freq_so = c(0.5, 2), amp_ptp_threshold_uv = c(75, 500), duration_pos_s = c(0.1, 1), duration_neg_s = c(0.1, 1.5) )
psg |
An |
channel |
Character. EEG channel label. If |
stages |
Integer vector or |
freq_so |
Numeric vector of length 2. SO frequency band (Hz).
Default |
amp_ptp_threshold_uv |
Numeric vector of length 2. Min and max
acceptable peak-to-peak amplitude (uV). Default |
duration_pos_s |
Numeric vector of length 2. Min and max positive half-
wave duration (s). Default |
duration_neg_s |
Numeric vector of length 2. Min and max negative half-
wave duration (s). Default |
A tibble with one row per detected slow oscillation:
Integer.
Numeric. Onset (s) relative to epoch start.
Numeric. Offset (s) relative to epoch start.
Numeric.
Numeric. Negative peak amplitude (uV).
Numeric. Positive peak amplitude (uV).
Numeric. Peak-to-peak amplitude (uV).
Character.
Molle, M., Marshall, L., Gais, S., & Born, J. (2002). Grouping of spindle activity during slow oscillations in human non-rapid eye movement sleep. Journal of Neuroscience, 22(24), 10941-10947.
Vallat, R., & Walker, M. P. (2021). An open-source, high-performance tool for automated sleep staging. eLife, 10, e70092. doi:10.7554/eLife.70092
Generates a short-time Fourier transform (STFT) based spectrogram for a single PSG channel across all epochs. Returns power (µV²/Hz) as a matrix with time (epochs) on rows and frequency on columns.
compute_spectrogram( psg, channel = NULL, freq_range = c(0, 40), window_s = 2, overlap = 0.5, db = TRUE )compute_spectrogram( psg, channel = NULL, freq_range = c(0, 40), window_s = 2, overlap = 0.5, db = TRUE )
psg |
An |
channel |
Character. A single channel label. |
freq_range |
Numeric vector of length 2. Frequency range to return
(Hz). Default |
window_s |
Numeric. STFT window length in seconds. Default |
overlap |
Numeric in [0, 1). Fractional window overlap. Default |
db |
Logical. Return power in decibels ( |
A list of class mrpheus_spectrogram with:
Numeric matrix. Rows = epochs, columns = frequency bins.
Numeric vector. Frequency bin centres (Hz).
Integer vector. Epoch indices.
Character. The channel label.
Logical. Whether power is in dB.
Detects sleep spindles in EEG channels using a band-pass / RMS envelope approach, closely following the algorithm in Lacourse et al. (2019) and implemented in YASA (Vallat & Walker, 2021). Spindles are identified as transient bursts of 11-16 Hz activity during NREM sleep.
compute_spindles( psg, channel = NULL, stages = NULL, freq_spindle = c(11, 16), rms_window_s = 0.3, min_duration_s = 0.5, max_duration_s = 3, threshold_sd = 1.5 )compute_spindles( psg, channel = NULL, stages = NULL, freq_spindle = c(11, 16), rms_window_s = 0.3, min_duration_s = 0.5, max_duration_s = 3, threshold_sd = 1.5 )
psg |
An |
channel |
Character. EEG channel label. If |
stages |
Integer vector or |
freq_spindle |
Numeric vector of length 2. Spindle frequency band (Hz).
Default |
rms_window_s |
Numeric. Moving RMS window length in seconds. Default |
min_duration_s |
Numeric. Minimum spindle duration in seconds.
Default |
max_duration_s |
Numeric. Maximum spindle duration in seconds.
Default |
threshold_sd |
Numeric. RMS threshold as a multiple of the channel SD
(within sigma band). Default |
A tibble with one row per detected spindle:
Integer. Epoch in which the spindle was detected.
Numeric. Spindle onset relative to epoch start (seconds).
Numeric. Spindle offset relative to epoch start (seconds).
Numeric. Spindle duration in seconds.
Numeric. Peak frequency within the spindle.
Numeric. Mean RMS amplitude within the spindle (uV).
Character. Channel label.
Lacourse, K., Yetton, B., Mednick, S., & Bhatt, D. L. (2019). Massive online sleep staging: A polysomnography data repository. Journal of Sleep Research.
Vallat, R., & Walker, M. P. (2021). An open-source, high-performance tool for automated sleep staging. eLife, 10, e70092. doi:10.7554/eLife.70092
Applies a sliding window over the scored epochs, concatenates the signal within each window, estimates the Welch PSD averaged across channels, and integrates power within standard EEG frequency bands. Returns a long-format tibble suitable for plotting temporal power dynamics across the night.
compute_temporal_bandpower( psg, hypno, channels = NULL, window_epochs = 10L, step_epochs = NULL, bands = list(delta = c(0.5, 4), theta = c(4, 8), alpha = c(8, 12), sigma = c(12, 16), beta = c(16, 30), gamma = c(30, 45)), win_sec = 4, noverlap = NULL, nfft = NULL )compute_temporal_bandpower( psg, hypno, channels = NULL, window_epochs = 10L, step_epochs = NULL, bands = list(delta = c(0.5, 4), theta = c(4, 8), alpha = c(8, 12), sigma = c(12, 16), beta = c(16, 30), gamma = c(30, 45)), win_sec = 4, noverlap = NULL, nfft = NULL )
psg |
An |
hypno |
Integer vector of length |
channels |
Character vector. Channel labels whose PSDs are averaged.
If |
window_epochs |
Integer. Number of 30-s epochs per window. Default
|
step_epochs |
Integer. Window step size in epochs. Default |
bands |
Named list of length-2 numeric vectors (Hz). Default matches
list(delta = c(0.5, 4), theta = c(4, 8), alpha = c(8, 12),
sigma = c(12, 16), beta = c(16, 30), gamma = c(30, 45))
|
win_sec |
Numeric. Welch window length in seconds. Default |
noverlap |
Integer or |
nfft |
Integer or |
Mirrors calculate_temporal_bandpower() from the companion Python notebook:
windows step by 50 % of their length (default), Welch overlap defaults to
half the window length in samples (proportional to sample rate), and
relative power is the share of each band in the total band-summed power.
A tibble with columns:
Numeric. Start time of the window in hours from recording onset.
Integer. First epoch index in the window (1-based).
Integer. Last epoch index in the window (1-based).
Integer. Most common stage code across the window
(artefact epochs excluded). NA if all epochs are artefacts.
Character. Band name.
Numeric. Mean band power across channels (V^2/Hz).
Numeric. Band power as a fraction of total band-summed power at this time point.
## Not run: rec <- read_edf("SC4001E0-PSG.edf") psg <- prepare_psg(rec) |> preprocess_psg() hypno <- stage_epochs(psg)$stage tbp <- compute_temporal_bandpower(psg, hypno) # Plot delta power across the night library(ggplot2) tbp |> dplyr::filter(band == "delta") |> ggplot(aes(time_hours, relative_power)) + geom_line() + labs(x = "Time (hours)", y = "Relative delta power") ## End(Not run)## Not run: rec <- read_edf("SC4001E0-PSG.edf") psg <- prepare_psg(rec) |> preprocess_psg() hypno <- stage_epochs(psg)$stage tbp <- compute_temporal_bandpower(psg, hypno) # Plot delta power across the night library(ggplot2) tbp |> dplyr::filter(band == "delta") |> ggplot(aes(time_hours, relative_power)) + geom_line() + labs(x = "Time (hours)", y = "Relative delta power") ## End(Not run)
Decomposes the EEG into independent components via FastICA, identifies
components whose time courses correlate with the EOG channels above
threshold, subtracts their contribution from the EEG, and returns a
cleaned mrpheus_psg. Mirrors MNE's ICA EOG rejection pipeline with
default settings matching the companion Python notebook
(n_components = min(6, n_eeg), threshold = 0.35).
correct_eog_ica( psg, eog_channels = NULL, eeg_channels = NULL, n_components = NULL, threshold = 0.35, fun = "logcosh", verbose = TRUE )correct_eog_ica( psg, eog_channels = NULL, eeg_channels = NULL, n_components = NULL, threshold = 0.35, fun = "logcosh", verbose = TRUE )
psg |
An |
eog_channels |
Character vector. EOG channel labels used as the
artifact reference. If |
eeg_channels |
Character vector. EEG channels to decompose and clean.
If |
n_components |
Integer or |
threshold |
Numeric. Absolute Pearson correlation threshold above
which a component is flagged as EOG-related. Default |
fun |
Character. Contrast function passed to |
verbose |
Logical. Print progress messages. Default |
Note: FastICA (this implementation) and MNE's default InfoMax ICA find different decompositions; the identified components and final signal will therefore differ from MNE output, but artifact removal performance is comparable.
A new mrpheus_psg with cleaned EEG signals and re-segmented
epochs. EOG and other channels are unchanged.
## Not run: rec <- read_edf("psg.edf") psg <- prepare_psg(rec) |> preprocess_psg() clean <- correct_eog_ica(psg) # Stricter threshold clean <- correct_eog_ica(psg, threshold = 0.5) ## End(Not run)## Not run: rec <- read_edf("psg.edf") psg <- prepare_psg(rec) |> preprocess_psg() clean <- correct_eog_ica(psg) # Stricter threshold clean <- correct_eog_ica(psg, threshold = 0.5) ## End(Not run)
For each EEG channel, fits a multiple linear regression on all EOG reference channels and subtracts the fitted EOG component, leaving the residual as the cleaned signal. This removes both transient blink artefacts and slow ocular drift without requiring blink event detection.
correct_eog_regression( psg, eog_channels = NULL, eeg_channels = NULL, verbose = TRUE )correct_eog_regression( psg, eog_channels = NULL, eeg_channels = NULL, verbose = TRUE )
psg |
An |
eog_channels |
Character vector. EOG channel labels to use as
regressors. If |
eeg_channels |
Character vector. EEG channel labels to clean. If
|
verbose |
Logical. Print progress messages. Default |
This is the pure-R equivalent of MNE's compute_proj_eog() / SSP
projection pipeline. Both approaches project out the subspace spanned by
the EOG channels; regression does so directly on the continuous signal.
A new mrpheus_psg with cleaned EEG signals and re-segmented
epochs. EOG and other channels are unchanged.
## Not run: rec <- read_edf("psg.edf") psg <- prepare_psg(rec) |> preprocess_psg() clean <- correct_eog_regression(psg) ## End(Not run)## Not run: rec <- read_edf("psg.edf") psg <- prepare_psg(rec) |> preprocess_psg() clean <- correct_eog_regression(psg) ## End(Not run)
Detects apneas and hypopneas in respiratory airflow and effort signals, returning event-level metadata and summary indices. Detection follows AASM 2012/2017 criteria: >= 90% signal reduction for >= 10 s (apnea), or >= 30% reduction with >= 3% SpO2 desaturation or arousal for >= 10 s (hypopnea).
detect_apneas( psg, airflow_channel = NULL, spo2_channel = NULL, min_duration_s = 10, apnea_threshold = 0.9, hypopnea_threshold = 0.3, desaturation_threshold = 3 )detect_apneas( psg, airflow_channel = NULL, spo2_channel = NULL, min_duration_s = 10, apnea_threshold = 0.9, hypopnea_threshold = 0.3, desaturation_threshold = 3 )
psg |
An |
airflow_channel |
Character. Airflow channel label (e.g. nasal pressure
or thermistor). If |
spo2_channel |
Character or |
min_duration_s |
Numeric. Minimum event duration in seconds. Default |
apnea_threshold |
Numeric. Fractional reduction required for apnea
classification. Default |
hypopnea_threshold |
Numeric. Fractional reduction required for
hypopnea classification. Default |
desaturation_threshold |
Numeric. SpO2 drop (percentage points) required
to confirm hypopnea. Default |
A list with:
Tibble. One row per event: epoch, start_s, end_s,
duration_s, type ("apnea" / "hypopnea"), desaturation.
Tibble. n_apneas, n_hypopneas, n_events.
Flags epochs containing likely artefacts based on amplitude thresholds, excessive high-frequency (muscle) power, and movement contamination. Operates on the EEG channels by default. Returns a logical vector (one value per epoch) and optionally attaches per-epoch artefact metrics for inspection.
detect_artifacts( psg, channels = NULL, amp_threshold_uv = 500, hf_band = c(40, 100), hf_percentile = 0.99, verbose = TRUE )detect_artifacts( psg, channels = NULL, amp_threshold_uv = 500, hf_band = c(40, 100), hf_percentile = 0.99, verbose = TRUE )
psg |
An |
channels |
Character vector. Channel labels to evaluate. If |
amp_threshold_uv |
Numeric. Peak-to-peak amplitude threshold in µV.
Epochs exceeding this in any selected channel are flagged. Default |
hf_band |
Numeric vector of length 2. Frequency band (Hz) considered
high-frequency / muscle contamination. Default |
hf_percentile |
Numeric. Epochs whose HF power exceeds this percentile
(across all epochs) are flagged. Default |
verbose |
Logical. Print summary. Default |
A tibble with one row per epoch and columns:
Integer. Epoch index (1-based).
Logical. TRUE if the epoch is flagged as artefact.
Character. Comma-separated reasons for flagging, or NA.
Numeric. Maximum peak-to-peak amplitude across selected channels.
Numeric. Mean HF band power (dB) across selected channels.
Estimates whether dominant powerline interference is at 50 Hz (Europe/UK) or 60 Hz (North America) by comparing PSD power at both frequencies using Welch's method on the first 10 seconds of the signal.
detect_powerline(signal, sr)detect_powerline(signal, sr)
signal |
Numeric vector. Should be an EEG or broadband channel with sufficient bandwidth. |
sr |
Numeric. Sampling rate in Hz. Must be > 100. |
If the signal's Nyquist limit is below 60 Hz, returns 50L immediately.
Integer. 50L or 60L.
## Not run: detect_powerline(eeg_signal, sr = 256) ## End(Not run)## Not run: detect_powerline(eeg_signal, sr = 256) ## End(Not run)
Detects R-peaks in a raw ECG signal using the Pan-Tompkins algorithm. Implements bandpass filtering, derivative, squaring, moving-average integration, and adaptive dual-threshold peak detection with T-wave rejection and search-back for missed beats.
detect_qrs(ecg, fs)detect_qrs(ecg, fs)
ecg |
Numeric vector. Raw ECG signal. |
fs |
Numeric. Sampling frequency in Hz. If |
Processing pipeline (Pan & Tompkins, 1985):
Bandpass filter 5–15 Hz (Butterworth N=3). At fs = 200 Hz, implemented as separate 12 Hz low-pass then 5 Hz high-pass stages.
Derivative filter — 5-point kernel interpolated to match fs.
Squaring — non-linear emphasis of dominant peaks.
150 ms moving-average integration.
Adaptive thresholding — dual-threshold scheme with T-wave rejection and search-back for missed beats (initialised on 2-second training window).
R-peak indices are returned relative to the bandpass-filtered signal.
Account for delay if you need indices aligned to the raw ECG.
A list of class mrpheus_qrs with components:
qrs_iInteger vector of R-peak sample indices (1-based), referenced to the bandpass-filtered signal.
qrs_ampNumeric vector of R-wave amplitudes at each detected peak (normalised bandpass signal units).
delayNumeric. Sample delay introduced by the moving-average stage (half the 150 ms window length).
fsNumeric. Sampling frequency passed to the function.
Pan J, Tompkins WJ. A real-time QRS detection algorithm. IEEE Trans Biomed Eng. 1985;32(3):230–236. doi:10.1109/TBME.1985.325532
read_philips_physlog(), compute_hr_signal(),
compute_hrv_sleep()
## Not run: rec <- read_philips_physlog("sub-01_physlog.log") qrs <- detect_qrs(rec$C[, "v1raw"], fs = rec$HDR$sfreq) qrs # Downstream HR signal hr <- compute_hr_signal(qrs) ## End(Not run)## Not run: rec <- read_philips_physlog("sub-01_physlog.log") qrs <- detect_qrs(rec$C[, "v1raw"], fs = rec$HDR$sfreq) qrs # Downstream HR signal hr <- compute_hr_signal(qrs) ## End(Not run)
Prepares the staging tibble produced by stage_epochs() for
downstream use with the hypnor package. Attaches recording metadata as
attributes and returns a tibble of class mrpheus_hypnogram, which
hypnor::new_hypnogram() accepts directly once hypnor is installed.
export_hypnogram( staging, epoch_s = 30, start_time = NULL, participant_id = NULL )export_hypnogram( staging, epoch_s = 30, start_time = NULL, participant_id = NULL )
staging |
A tibble from |
epoch_s |
Numeric. Epoch duration in seconds. Must match the |
start_time |
POSIXct or |
participant_id |
Character or |
A tibble of class mrpheus_hypnogram with columns epoch, stage,
and any probability columns from the staging model. Metadata (epoch_s,
start_time, participant_id, source, resolution) are attached as
attributes and forwarded to hypnor::new_hypnogram() when hypnor is
available.
Designs a 2nd-order Butterworth bandstop filter centred at freq Hz with
bandwidth bandwidth_hz and applies it zero-phase using
gsignal::filtfilt(). Because the filter has only ~9 coefficients, it is
safe to use on long recordings without memory issues.
notch_filter(signal, sr, freq, bandwidth_hz = 2, harmonics = TRUE)notch_filter(signal, sr, freq, bandwidth_hz = 2, harmonics = TRUE)
signal |
Numeric vector. |
sr |
Numeric. Sampling rate in Hz. |
freq |
Numeric. Centre frequency in Hz (e.g. |
bandwidth_hz |
Numeric. Full bandwidth of the notch in Hz. Default |
harmonics |
Logical. Also notch harmonics (2×, 3×, ...) below
Nyquist − 5 Hz. Default |
By default also applies the filter at all harmonics of freq below the
Nyquist limit, which is the standard approach for powerline noise removal.
Filtered numeric vector, same length as signal.
## Not run: clean <- notch_filter(eeg_signal, sr = 256, freq = 50) clean <- notch_filter(eeg_signal, sr = 256, freq = 60, harmonics = FALSE) ## End(Not run)## Not run: clean <- notch_filter(eeg_signal, sr = 256, freq = 50) clean <- notch_filter(eeg_signal, sr = 256, freq = 60, harmonics = FALSE) ## End(Not run)
An 8-colour palette extracted from the Roman mosaic Orpheus Charming the Animals (3rd century AD, Palermo Archaeological Museum). The mosaic depicts Orpheus — the mythological figure whose name and story this package honours — surrounded by animals, rendered in warm Mediterranean tesserae.
palette_orpheuspalette_orpheus
A named character vector of 8 hex colour codes:
#CDB992 — warm background tessera
#B83E2C — Orpheus's robe; terracotta
#6A7840 — tree and vegetation
#7C5432 — animal fur and earth
#3C2212 — shadows and outlines
#B07C3A — warm amber accent
#6C8284 — birds; dusty teal-grey
#EAD6AA — highlights and light tessera
The palette is intentionally earthy and muted, reflecting the natural pigments of Roman mosaic work: sandy limestone backgrounds, terracotta robes, olive vegetation, warm umber fauna, and the distinctive slate-teal of the birds.
Mosaic: Orpheus Charming the Animals, Roman, 3rd century AD. Palermo Archaeological Museum. Image via Wikimedia Commons, https://en.wikipedia.org/wiki/Orpheus.
if (requireNamespace("scales", quietly = TRUE)) { scales::show_col(palette_orpheus) }if (requireNamespace("scales", quietly = TRUE)) { scales::show_col(palette_orpheus) }
Takes an mrpheus_edf object and segments it into standard epochs,
performs a channel inventory (classifying signals by type), and flags
channels that appear flat or likely bad. This is the standard entry point
before any downstream analyses (spectral, event detection, staging).
prepare_psg( edf, epoch_s = 30, eeg_pattern = "EEG|C3|C4|F3|F4|O1|O2|Fpz|Pz", eog_pattern = "EOG|ROC|LOC", emg_pattern = "EMG|chin|Chin", ecg_pattern = "ECG|EKG", resp_pattern = "Thor|Abdo|Flow|SpO2|airflow", flat_threshold = 1e-06 )prepare_psg( edf, epoch_s = 30, eeg_pattern = "EEG|C3|C4|F3|F4|O1|O2|Fpz|Pz", eog_pattern = "EOG|ROC|LOC", emg_pattern = "EMG|chin|Chin", ecg_pattern = "ECG|EKG", resp_pattern = "Thor|Abdo|Flow|SpO2|airflow", flat_threshold = 1e-06 )
edf |
An |
epoch_s |
Numeric. Epoch length in seconds. Default |
eeg_pattern |
Character. Regex pattern to identify EEG channels.
Default |
eog_pattern |
Character. Regex pattern to identify EOG channels.
Default |
emg_pattern |
Character. Regex pattern to identify EMG channels.
Default |
ecg_pattern |
Character. Regex pattern to identify ECG/EKG channels.
Default |
resp_pattern |
Character. Regex pattern to identify respiratory channels.
Default |
flat_threshold |
Numeric. Variance below this value flags a channel as
flat/bad. Default |
A list of class mrpheus_psg with components:
The original mrpheus_edf object.
List. Each element is one epoch (30 s by default), itself a named list of channel vectors.
Integer. Total number of complete epochs.
Numeric. Epoch duration in seconds.
Data frame. Channel label, detected type
(EEG/EOG/EMG/ECG/RESP/OTHER), sample rate, and bad flag.
## Not run: rec <- read_edf("data/psg_001.edf") psg <- prepare_psg(rec) psg$channel_map ## End(Not run)## Not run: rec <- read_edf("data/psg_001.edf") psg <- prepare_psg(rec) psg$channel_map ## End(Not run)
Applies the standard sleep EEG preprocessing pipeline to an mrpheus_psg
object: optional channel renaming, DC offset removal, powerline notch
filtering (plus harmonics), and channel-type-specific bandpass filtering.
preprocess_psg( psg, channel_rename = NULL, dc = TRUE, powerline_freq = NULL, notch_harmonics = TRUE, notch_bw_hz = 2, eeg_bandpass = c(0.3, 35), eog_bandpass = c(0.3, 15), emg_bandpass = c(10, 99), ecg_bandpass = c(0.5, 40), verbose = TRUE )preprocess_psg( psg, channel_rename = NULL, dc = TRUE, powerline_freq = NULL, notch_harmonics = TRUE, notch_bw_hz = 2, eeg_bandpass = c(0.3, 35), eog_bandpass = c(0.3, 15), emg_bandpass = c(10, 99), ecg_bandpass = c(0.5, 40), verbose = TRUE )
psg |
An |
channel_rename |
Named character vector mapping old channel labels to
new labels, e.g. |
dc |
Logical. Remove per-channel DC offset via |
powerline_freq |
Integer or |
notch_harmonics |
Logical. Notch harmonics of |
notch_bw_hz |
Numeric. Full bandwidth of each notch in Hz. Default |
eeg_bandpass |
Numeric vector of length 2. Bandpass limits (Hz) applied
to all EEG channels. Default |
eog_bandpass |
Numeric vector of length 2. Bandpass limits (Hz) applied
to all EOG channels. Default |
emg_bandpass |
Numeric vector of length 2. Bandpass limits (Hz) applied
to all EMG channels. Default |
ecg_bandpass |
Numeric vector of length 2. Bandpass limits (Hz) applied
to all ECG channels. Default |
verbose |
Logical. Print progress messages. Default |
Filtering is applied to the full continuous signal stored in
psg$edf$signals before re-epoching, which avoids filter discontinuities
at epoch boundaries. The returned object is a new mrpheus_psg with the
same epoch length and channel map as the input.
The individual filter steps (remove_dc(), detect_powerline(),
notch_filter(), bandpass_filter()) are exported separately and can be
called on any numeric vector without constructing a PSG object.
A new mrpheus_psg object with filtered signals and re-segmented
epochs. The edf$signals entries are replaced in-place with the filtered
continuous signals so that subsequent epoch access is consistent.
## Not run: rec <- read_edf("data/psg_001.edf") psg <- prepare_psg(rec) # Auto-detect powerline, apply standard filters psg_clean <- preprocess_psg(psg) # Rename linked-ear channels to 10-20 names, fix powerline explicitly psg_clean <- preprocess_psg( psg, channel_rename = c("C3-A2" = "C3", "C4-A1" = "C4", "O1-A2" = "O1", "O2-A1" = "O2"), powerline_freq = 50L ) ## End(Not run)## Not run: rec <- read_edf("data/psg_001.edf") psg <- prepare_psg(rec) # Auto-detect powerline, apply standard filters psg_clean <- preprocess_psg(psg) # Rename linked-ear channels to 10-20 names, fix powerline explicitly psg_clean <- preprocess_psg( psg, channel_rename = c("C3-A2" = "C3", "C4-A1" = "C4", "O1-A2" = "O1", "O2-A1" = "O2"), powerline_freq = 50L ) ## End(Not run)
Reads a European Data Format (EDF or EDF+) file and returns a structured
list containing signal data, channel metadata, and recording header.
Wraps edfReader::readEdfHeader() and edfReader::readEdfSignals() with
consistent output formatting for the mrpheus pipeline.
read_edf(path, channels = NULL, only_header = FALSE)read_edf(path, channels = NULL, only_header = FALSE)
path |
Character. Path to an |
channels |
Character vector or |
only_header |
Logical. If |
A list of class mrpheus_edf with components:
Data frame. Recording metadata (patient info, start time, number of signals, etc.).
Named list of numeric vectors, one per channel.
Data frame. Channel-level metadata: label, sample rate, physical min/max, digital min/max, transducer type, prefiltering.
Numeric. Total recording duration in seconds.
Character. Resolved path to the source file.
## Not run: rec <- read_edf("data/psg_001.edf") rec <- read_edf("data/psg_001.edf", channels = c("EEG Fpz-Cz", "EOG horizontal")) rec$channels ## End(Not run)## Not run: rec <- read_edf("data/psg_001.edf") rec <- read_edf("data/psg_001.edf", channels = c("EEG Fpz-Cz", "EOG horizontal")) rec$channels ## End(Not run)
Reads a Philips PMU physiological log (.log) file recorded alongside an
MRI acquisition and returns a structured object containing the signal
matrix, event markers, and recording metadata. Supports all Philips PMU
hardware variants via the system preset.
read_philips_physlog( path, system = c("wBTU", "wired", "custom"), sfreq = NULL, channels = NULL, skipprep = FALSE )read_philips_physlog( path, system = c("wBTU", "wired", "custom"), sfreq = NULL, channels = NULL, skipprep = FALSE )
path |
Character. Path to the Philips |
system |
Philips PMU hardware preset. One of:
|
sfreq |
Numeric or |
channels |
Character vector or |
skipprep |
Logical. If |
Bit-encoded marker values:
0x0001 ECG trigger 0x0002 PPU trigger 0x0004 respiration 0x0008 slice onset 0x0010 scanner start 0x0020 scanner stop 0x0040 external trigger 0x0080 calibration 0x8000 ref ECG trigger
Use the scanner-stop marker (more reliable than start) to time-lock data:
stopidx <- tail(data$I$ScannerStop, 1) - enddelay startidx <- stopidx - round(n_vols * TR * data$HDR$sfreq) epoch <- data$C[startidx:stopidx, ]
A list of class mrpheus_physlog with components:
CInteger matrix (samples × channels). Column order matches the file header.
MTwo-column integer matrix: marker (bit-encoded value) and
index (1-based sample number) for every non-zero marker.
INamed list of 1-based sample index vectors for each event type: VcgOnset, PpuOnset, TriggerResp, Measurement, ScannerStart, ScannerStop, TriggerExt, Calibration, RefTriggerVcg.
HDRList of header metadata: ID, DATETIME, STATS, DockableTable, COLUMN_NAMES, system, sfreq.
pathCharacter. Resolved path to the source file.
detect_qrs(), compute_hr_signal()
## Not run: # Philips Achieva/Ingenia with wireless VCG (default) rec <- read_philips_physlog("sub-01_ses-01_physlog.log") rec # Older wired scanner rec <- read_philips_physlog("sub-01_physlog.log", system = "wired") # Custom sampling rate rec <- read_philips_physlog("sub-01_physlog.log", system = "custom", sfreq = 400) # ECG channels only rec <- read_philips_physlog("sub-01_physlog.log", channels = c("v1raw", "v2raw")) ## End(Not run)## Not run: # Philips Achieva/Ingenia with wireless VCG (default) rec <- read_philips_physlog("sub-01_ses-01_physlog.log") rec # Older wired scanner rec <- read_philips_physlog("sub-01_physlog.log", system = "wired") # Custom sampling rate rec <- read_philips_physlog("sub-01_physlog.log", system = "custom", sfreq = 400) # ECG channels only rec <- read_philips_physlog("sub-01_physlog.log", channels = c("v1raw", "v2raw")) ## End(Not run)
Subtracts the signal mean (per-channel DC offset removal). Should be applied before filtering to prevent filter transients from a non-zero baseline.
remove_dc(signal)remove_dc(signal)
signal |
Numeric vector. |
Numeric vector with mean removed, same length as signal.
x <- c(100.1, 100.5, 99.8, 100.3) remove_dc(x)x <- c(100.1, 100.5, 99.8, 100.3) remove_dc(x)
Stages each 30-second epoch using a pre-trained LightGBM model originally
developed for YASA (Vallat & Walker, 2021) and shipped as a cross-language
serialised model in inst/models/yasa_staging.txt. Features are computed in
R to match the Python feature extraction pipeline exactly; bit-exact parity
is validated in the package test suite.
stage_epochs( psg, eeg_channel = NULL, eog_channel = NULL, emg_channel = NULL, artefacts = NULL, model_path = system.file("models", "yasa_staging.txt", package = "mrpheus") )stage_epochs( psg, eeg_channel = NULL, eog_channel = NULL, emg_channel = NULL, artefacts = NULL, model_path = system.file("models", "yasa_staging.txt", package = "mrpheus") )
psg |
An |
eeg_channel |
Character. Central EEG channel (e.g. |
eog_channel |
Character or |
emg_channel |
Character or |
artefacts |
Tibble or |
model_path |
Character. Path to the serialised LightGBM model. Defaults
to the bundled model at |
Stages returned follow standard AASM nomenclature:
W (wake), N1, N2, N3, REM.
A tibble with one row per epoch:
Integer.
Character. AASM stage: W, N1, N2, N3, REM, or
NA (artefact).
Numeric. Posterior class probabilities from the LightGBM model.
Vallat, R., & Walker, M. P. (2021). An open-source, high-performance tool for automated sleep staging. eLife, 10, e70092. doi:10.7554/eLife.70092