원클릭으로
peakdetect-qrs
Pan-Tompkins QRS detector — extract heart-beat events from EOG/ECG channels
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Pan-Tompkins QRS detector — extract heart-beat events from EOG/ECG channels
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Neurodata Without Borders (.nwb) — the de-facto standard for in-vivo electrophysiology (Neuropixels / AIBS / DANDI / IBL)
Index of L0 data-format loader skills by family
Index of all L2 paradigm skills by group + analysis_goal → paradigm matrix
Two-phase BCI data preprocessing pipeline: deep-inspect → plan → propose → user confirm → automated code/execute/QC/export
BIDS-iEEG sidecar (*_channels.tsv / *_electrodes.tsv / *_coordsystem.json / *_ieeg.json) — clinical sEEG / ECoG standard
Multiscale Electrophysiology Format v3 (Mayo Clinic) — long-duration encrypted sEEG
| name | peakdetect_qrs |
| description | Pan-Tompkins QRS detector — extract heart-beat events from EOG/ECG channels |
| layer | L3 |
| group | adaptive_cleaning |
| metadata | {"tags":["operator","adaptive_cleaning","qrs","pan_tompkins","ecg","heart_rate"],"modalities":["eeg","meg"],"step_string":"peakdetect_qrs","analysis_goal_allowed":["feature_extraction","clinical_screening","exploratory","generic","online_inference"],"analysis_goal_forbidden":["source_localization","phase_amplitude_coupling"]} |
Detects QRS complexes (R-wave peaks) in an ECG channel using the
Pan-Tompkins (1985) algorithm. Produces event timestamps suitable for
downstream ssp_ecg, heart-rate variability (HRV), or cardiac-event
exclusion in epoched analyses.
Input / Output: data with ECG-named channel → meta["qrs_times"] (event
times in seconds) + meta["heart_rate_bpm"].
peakdetect_qrs:{ecg_ch}
| Parameter | Type | Default | Description |
|---|---|---|---|
ecg_ch | str | "ECG" | ECG channel substring. |
refractory_ms (kw) | float | 200.0 | Min interval between QRS peaks. |
EEG / MEG (with ECG channel): yes. Direct ECG recording: yes. Other modalities without ECG: forbidden.
Use when: extracting QRS events for SSP-ECG; computing HRV features; masking cardiac epochs in PAC.
Don't use when: no ECG channel; cardiac artifact subspace removal is handled by SSP-ECG directly.
ssp_ecg or epoch_event_masking.| Failure | Symptom | Detection |
|---|---|---|
| ECG saturated | Bandpass output flat. | If np.ptp(filtered) < 0.001 raise recoverable. |
| HR < 30 or > 200 BPM | Likely false detection. | Warn. |
from __future__ import annotations
import numpy as np
from scipy.signal import butter, filtfilt, find_peaks
def peakdetect_qrs(
ecg: np.ndarray, sfreq: float, refractory_ms: float = 200.0,
) -> np.ndarray:
"""Pan-Tompkins QRS detection. Returns sample indices."""
b, a = butter(2, [5 / (sfreq/2), 15 / (sfreq/2)], btype="bandpass")
filtered = filtfilt(b, a, ecg)
diff = np.diff(filtered, prepend=filtered[0])
energy = diff ** 2
win = max(1, int(0.15 * sfreq))
energy = np.convolve(energy, np.ones(win) / win, mode="same")
threshold = 4 * np.median(np.abs(energy)) / 0.6745
refractory_samples = int(refractory_ms * sfreq / 1000)
peaks, _ = find_peaks(energy, height=threshold, distance=refractory_samples)
return peaks
from typing import Any, Dict
import time
import numpy as np
from easybci_lib.tools.neural_processing.operator_errors import EasyBCIOperatorError
from easybci_lib.tools.neural_processing.preprocess.step_cache import record_step_elapsed
def operator_peakdetect_qrs(
data_dict: Dict[str, Any], *, ecg_ch: str = "ECG", refractory_ms: float = 200.0,
) -> Dict[str, Any]:
"""Pan-Tompkins QRS peak detection.
Parameters
----------
data_dict : dict
ecg_ch : str
refractory_ms : float
Returns
-------
dict — `meta["qrs_times"]`, `meta["heart_rate_bpm"]`.
Raises
------
EasyBCIOperatorError
recoverable=True if ECG channel absent or signal flat.
Modality coverage
-----------------
EEG / MEG / ECG-as-data: yes.
References
----------
Pan & Tompkins 1985.
"""
channels = data_dict.get("channels", [])
ecg_idx = next((i for i, c in enumerate(channels) if ecg_ch.lower() in c.lower()), None)
if ecg_idx is None:
raise EasyBCIOperatorError(
operator="peakdetect_qrs", reason=f"no channel matching {ecg_ch!r}",
recoverable=True, fallback_step=f"acquire ECG or skip",
)
t0 = time.monotonic()
sfreq = float(data_dict["frequency"])
from scipy.signal import butter, filtfilt, find_peaks
ecg = data_dict["data"][ecg_idx]
b, a = butter(2, [5 / (sfreq/2), 15 / (sfreq/2)], btype="bandpass")
filtered = filtfilt(b, a, ecg)
if float(np.ptp(filtered)) < 0.001:
raise EasyBCIOperatorError(
operator="peakdetect_qrs", reason="ECG signal flat after bandpass",
recoverable=True, fallback_step="verify ECG channel",
)
diff = np.diff(filtered, prepend=filtered[0])
energy = diff ** 2
win = max(1, int(0.15 * sfreq))
energy = np.convolve(energy, np.ones(win) / win, mode="same")
threshold = 4 * np.median(np.abs(energy)) / 0.6745
refractory_samples = int(refractory_ms * sfreq / 1000)
peaks, _ = find_peaks(energy, height=threshold, distance=refractory_samples)
qrs_times = peaks.astype(np.float64) / sfreq
duration = data_dict["data"].shape[-1] / sfreq
hr_bpm = float(len(peaks) / duration * 60.0) if duration > 0 else 0.0
elapsed = time.monotonic() - t0
out = dict(data_dict)
out["elapsed_s"] = elapsed
out["meta"] = {
**out.get("meta", {}),
"qrs_times": qrs_times,
"heart_rate_bpm": hr_bpm,
"peakdetect_qrs": {"ecg_ch": ecg_ch, "refractory_ms": refractory_ms},
}
record_step_elapsed("peakdetect_qrs", elapsed, (data_dict.get("meta") or {}).get("step_cache_key"))
return out