Skill Tracer
Skills › Scientific Agent Skills › neurokit2 › ecg_cardiac.md

ecg_cardiac.md

197 lines · skills/neurokit2/references/ecg_cardiac.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# ECG and cardiac processing
2
3Checked **2026-10-01** against NeuroKit2 0.2.13 stable source/runtime,
4the live ECG API, and current psychophysiology measurement guidance.
5

ECG and cardiac processing

Checked 2026-10-01 against NeuroKit2 0.2.13 stable source/runtime, the live ECG API, and current psychophysiology measurement guidance.

Raw lines 1 to 5
6## Acquisition contract
7
8Record lead/configuration, electrode placement, reference/ground, hardware gain and
9filters, ADC resolution/range, physical unit, native sampling rate, timestamp clock,
10posture/task, medication and relevant population variables, and artifact annotations.
11Do not infer millivolts from a column named `ECG`.
13Sampling must support the intended endpoint. Rate/R-peak timing and P–QRS–T morphology
14have different bandwidth and precision needs. Psychophysiology guidance commonly uses
15at least 125 Hz and regards 500 Hz as conservative for HRV timing, but this is not a
16universal validation threshold. Validate the complete acquisition and detector on
17representative signals; morphology/delineation often uses 250–1000 Hz.

Acquisition contract

Record lead/configuration, electrode placement, reference/ground, hardware gain and filters, ADC resolution/range, physical unit, native sampling rate, timestamp clock, posture/task, medication and relevant population variables, and artifact annotations. Do not infer millivolts from a column named ECG.

Sampling must support the intended endpoint. Rate/R-peak timing and P–QRS–T morphology have different bandwidth and precision needs. Psychophysiology guidance commonly uses at least 125 Hz and regards 500 Hz as conservative for HRV timing, but this is not a universal validation threshold. Validate the complete acquisition and detector on representative signals; morphology/delineation often uses 250–1000 Hz.

Raw lines 6 to 18
19## `ecg_process()` in stable 0.2.13
21```python
22signals, info = nk.ecg_process(
23 ecg,
24 sampling_rate=250,
25 method="neurokit",
27```
29The stable source performs:
311. `signal_sanitize()` (index reset only);
322. `ecg_clean()` with the selected method;
333. `ecg_peaks(..., correct_artifacts=True)`;
344. interpolated rate;
355. default `ecg_quality(..., method="averageQRS")`;
366. DWT delineation; and
377. atrial/ventricular phase.
39The pinned default probe observed these 19 columns:
41```text
42ECG_Raw, ECG_Clean, ECG_Rate, ECG_Quality, ECG_R_Peaks,
43ECG_P_Peaks, ECG_P_Onsets, ECG_P_Offsets, ECG_Q_Peaks,
44ECG_R_Onsets, ECG_R_Offsets, ECG_S_Peaks, ECG_T_Peaks,
45ECG_T_Onsets, ECG_T_Offsets, ECG_Phase_Atrial,
46ECG_Phase_Completion_Atrial, ECG_Phase_Ventricular,
47ECG_Phase_Completion_Ventricular
48```
50This is a verified default schema, not a universal promise. Persist
51`list(signals.columns)` and `sorted(info)`.
53`info` is a flat dict. In the default pinned run it included corrected and uncorrected
54R-peaks, `ECG_fixpeaks_*` diagnostics, sampling rate, methods, and delineated wave
55indices. It is not nested under an `ECG` key.

ecg_process() in stable 0.2.13

signals, info = nk.ecg_process(
    ecg,
    sampling_rate=250,
    method="neurokit",
)

The stable source performs:

  1. signal_sanitize() (index reset only);
  2. ecg_clean() with the selected method;
  3. ecg_peaks(..., correct_artifacts=True);
  4. interpolated rate;
  5. default ecg_quality(..., method="averageQRS");
  6. DWT delineation; and
  7. atrial/ventricular phase.

The pinned default probe observed these 19 columns:

ECG_Raw, ECG_Clean, ECG_Rate, ECG_Quality, ECG_R_Peaks,
ECG_P_Peaks, ECG_P_Onsets, ECG_P_Offsets, ECG_Q_Peaks,
ECG_R_Onsets, ECG_R_Offsets, ECG_S_Peaks, ECG_T_Peaks,
ECG_T_Onsets, ECG_T_Offsets, ECG_Phase_Atrial,
ECG_Phase_Completion_Atrial, ECG_Phase_Ventricular,
ECG_Phase_Completion_Ventricular

This is a verified default schema, not a universal promise. Persist list(signals.columns) and sorted(info).

info is a flat dict. In the default pinned run it included corrected and uncorrected R-peaks, ECG_fixpeaks_* diagnostics, sampling rate, methods, and delineated wave indices. It is not nested under an ECG key.

Raw lines 19 to 56
57## Cleaning and R-peak methods
59High-level methods documented for `ecg_process()` include `neurokit`,
60`pantompkins1985`, `hamilton2002`, `elgendi2010`, and `engzeemod2012`.
61`ecg_clean()` and lower-level peak detection expose additional methods. A cleaning
62method and detector encode different assumptions; do not select whichever produces
63the expected group effect.
65In stable `ecg_process()`, `**kwargs` are forwarded only to `ecg_clean()`. They do not
66configure the R-peak detector, correction, quality method, or delineation. Use the
67lower-level functions when those settings matter, and preserve their parameters.
69For custom control:
71```python
72clean = nk.ecg_clean(ecg, sampling_rate=250, method="neurokit")
73markers, peak_info = nk.ecg_peaks(
74 clean,
75 sampling_rate=250,
76 method="neurokit",
77 correct_artifacts=False,
79```
81Validate:
83- R-peak precision, false positives, missed beats, and ectopy;
84- performance during motion, changing rate, and low-amplitude QRS;
85- lead polarity and possible inversion;
86- filter edge regions and discontinuities; and
87- failure modes by participant, condition, device, and population.

Cleaning and R-peak methods

High-level methods documented for ecg_process() include neurokit, pantompkins1985, hamilton2002, elgendi2010, and engzeemod2012. ecg_clean() and lower-level peak detection expose additional methods. A cleaning method and detector encode different assumptions; do not select whichever produces the expected group effect.

In stable ecg_process(), **kwargs are forwarded only to ecg_clean(). They do not configure the R-peak detector, correction, quality method, or delineation. Use the lower-level functions when those settings matter, and preserve their parameters.

For custom control:

clean = nk.ecg_clean(ecg, sampling_rate=250, method="neurokit")
markers, peak_info = nk.ecg_peaks(
    clean,
    sampling_rate=250,
    method="neurokit",
    correct_artifacts=False,
)

Validate:

  • R-peak precision, false positives, missed beats, and ectopy;
  • performance during motion, changing rate, and low-amplitude QRS;
  • lead polarity and possible inversion;
  • filter edge regions and discontinuities; and
  • failure modes by participant, condition, device, and population.
Raw lines 57 to 88
89## Peak correction
91`ecg_process()` always requests Lipponen–Tarvainen correction in 0.2.13. Inspect:
93```python
94uncorrected = info["ECG_R_Peaks_Uncorrected"]
95corrected = info["ECG_R_Peaks"]
96categories = {
97 key: info.get(f"ECG_fixpeaks_{key}", [])
98 for key in ["ectopic", "missed", "extra", "longshort"]
100```
102Correction can improve a tachogram but can also alter HRV. Report corrected proportions,
103categories, thresholds/method, excluded segments, and sensitivity with uncorrected or
104alternative policies. Do not assume an algorithm can distinguish ectopic from erroneous
105detection without waveform review or appropriate labels.

Peak correction

ecg_process() always requests Lipponen–Tarvainen correction in 0.2.13. Inspect:

uncorrected = info["ECG_R_Peaks_Uncorrected"]
corrected = info["ECG_R_Peaks"]
categories = {
    key: info.get(f"ECG_fixpeaks_{key}", [])
    for key in ["ectopic", "missed", "extra", "longshort"]
}

Correction can improve a tachogram but can also alter HRV. Report corrected proportions, categories, thresholds/method, excluded segments, and sensitivity with uncorrected or alternative policies. Do not assume an algorithm can distinguish ectopic from erroneous detection without waveform review or appropriate labels.

Raw lines 89 to 106
107## ECG quality is method-dependent
109```python
110quality = nk.ecg_quality(
111 clean,
112 rpeaks=peak_info["ECG_R_Peaks"],
113 sampling_rate=250,
114 method="averageQRS",
116```
118Stable options include `averageQRS`, `templatematch`, `zhao2018`,
119`dissimilarity`, and `ho2025`.
121- `averageQRS`: continuous array scaled 0–1 by this implementation.
122- `templatematch`: continuous morphology-template correlation; it is relative to the
123 recording.
124- `zhao2018`: one classification string (`Unacceptable`, `Barely acceptable`, or
125 `Excellent`).
126- `dissimilarity`: direction/scale differs from similarity scores.
127- `ho2025`: beat/interval-oriented quality based on detector agreement.
129There is no universal `>0.6` acceptance rule across these methods. A quality output is
130not device validation. Define thresholds on independent labeled data and preserve the
131method name and scale.

ECG quality is method-dependent

quality = nk.ecg_quality(
    clean,
    rpeaks=peak_info["ECG_R_Peaks"],
    sampling_rate=250,
    method="averageQRS",
)

Stable options include averageQRS, templatematch, zhao2018, dissimilarity, and ho2025.

  • averageQRS: continuous array scaled 0–1 by this implementation.
  • templatematch: continuous morphology-template correlation; it is relative to the recording.
  • zhao2018: one classification string (Unacceptable, Barely acceptable, or Excellent).
  • dissimilarity: direction/scale differs from similarity scores.
  • ho2025: beat/interval-oriented quality based on detector agreement.

There is no universal >0.6 acceptance rule across these methods. A quality output is not device validation. Define thresholds on independent labeled data and preserve the method name and scale.

Raw lines 107 to 132
133## Delineation return order
135```python
136delineation_signals, waves = nk.ecg_delineate(
137 clean,
138 rpeaks=peak_info["ECG_R_Peaks"],
139 sampling_rate=250,
140 method="dwt",
142```
144The first object is a same-length marker DataFrame; the second is a dict of wave sample
145indices. Stable methods include `peak`, `prominence`, `cwt`, and `dwt`. Missing wave
146indices may be NaN. Validate every wave endpoint needed for an interval/morphology
147claim; R-peak accuracy does not validate P/T delineation.

Delineation return order

delineation_signals, waves = nk.ecg_delineate(
    clean,
    rpeaks=peak_info["ECG_R_Peaks"],
    sampling_rate=250,
    method="dwt",
)

The first object is a same-length marker DataFrame; the second is a dict of wave sample indices. Stable methods include peak, prominence, cwt, and dwt. Missing wave indices may be NaN. Validate every wave endpoint needed for an interval/morphology claim; R-peak accuracy does not validate P/T delineation.

Raw lines 133 to 148
149## Phase and event-related analysis
151`ECG_Phase_Atrial` and `ECG_Phase_Ventricular` are binary phase labels;
152completion columns are fractions from 0 to 1. Their validity depends on delineation.
153For cardiac-locked stimuli, characterize trigger latency/jitter independently of
154software phase estimates.
156`ecg_eventrelated()` and `ecg_intervalrelated()` inspect available columns. Their output
157columns are conditional. Use explicit dispatch and save observed columns:
159```python
160features = nk.ecg_analyze(
161 epochs,
162 sampling_rate=250,
163 method="event-related",
165```

Phase and event-related analysis

ECG_Phase_Atrial and ECG_Phase_Ventricular are binary phase labels; completion columns are fractions from 0 to 1. Their validity depends on delineation. For cardiac-locked stimuli, characterize trigger latency/jitter independently of software phase estimates.

ecg_eventrelated() and ecg_intervalrelated() inspect available columns. Their output columns are conditional. Use explicit dispatch and save observed columns:

features = nk.ecg_analyze(
    epochs,
    sampling_rate=250,
    method="event-related",
)
Raw lines 149 to 166
Starts an agent dispatch :157
167## ECG-derived respiration
169`ecg_rsp()` takes a heart-rate series, not raw/clean ECG:
171```python
172edr = nk.ecg_rsp(signals["ECG_Rate"], sampling_rate=250, method="vangent2019")
173```
175EDR is a proxy and depends on ECG morphology/rate modulation. It is not interchangeable
176with a calibrated respiration sensor for RSA, tidal volume, or respiratory diagnosis.

ECG-derived respiration

ecg_rsp() takes a heart-rate series, not raw/clean ECG:

edr = nk.ecg_rsp(signals["ECG_Rate"], sampling_rate=250, method="vangent2019")

EDR is a proxy and depends on ECG morphology/rate modulation. It is not interchangeable with a calibrated respiration sensor for RSA, tidal volume, or respiratory diagnosis.

Raw lines 167 to 177
178## Bounded pipeline
180```bash
181python skills/neurokit2/scripts/ecg_hrv_pipeline.py \
182 --input deidentified.csv --column ECG --root . --deidentified \
183 --sampling-rate 250 --method neurokit --domains time \
184 --signals-output ecg_processed.csv --output ecg_report.json
185```
187The helper rejects missing/non-finite samples instead of silently interpolating them,
188reports observed schemas and correction categories, and gates longer HRV domains.

Bounded pipeline

python skills/neurokit2/scripts/ecg_hrv_pipeline.py \
  --input deidentified.csv --column ECG --root . --deidentified \
  --sampling-rate 250 --method neurokit --domains time \
  --signals-output ecg_processed.csv --output ecg_report.json

The helper rejects missing/non-finite samples instead of silently interpolating them, reports observed schemas and correction categories, and gates longer HRV domains.

Raw lines 178 to 189
Runs code python skills/neurokit2/scripts/ecg_hrv_pipeline.py :181
190## Sources checked 2026-10-01
192- [Official ECG API](https://neuropsychology.github.io/NeuroKit/functions/ecg.html)
193- [Stable v0.2.13 `ecg_process` source](https://github.com/neuropsychology/NeuroKit/blob/v0.2.13/neurokit2/ecg/ecg_process.py)
194- [Quigley et al. (2024), HR/HRV measurement guidelines](https://doi.org/10.1111/psyp.14604)
195- [Laborde et al. (2017), HRV planning/reporting](https://doi.org/10.3389/fpsyg.2017.00213)
196- [Lipponen & Tarvainen (2019), correction algorithm](https://doi.org/10.1080/03091902.2019.1640306)
197- [Pan & Tompkins (1985)](https://doi.org/10.1109/TBME.1985.325532)

Sources checked 2026-10-01

Raw lines 190 to 197