Quickstart

This page takes one 10-second, 12-lead recording from a NumPy file to a stored, queried and plotted ECG Record. It assumes pip install "ecg-records[viz,table]". A runnable notebook version is quickstart.ipynb.

1. Load a signal

ecg-records takes a channel-major array, one row per lead, with an explicit name for every row. Amplitudes default to millivolts.

import numpy as np

signal = np.load("ecg.npy")     # shape (12, 5000): 12 leads, 10 s at 500 Hz
leads = ["I", "II", "III", "aVR", "aVL", "aVF", "V1", "V2", "V3", "V4", "V5", "V6"]
print(signal.shape, signal.dtype)

If your data is sample-major, shaped (n_samples, n_leads) (common in CSV files and in wfdb), transpose it with signal.T. Preparing input signals covers units, lead order, WFDB files and fewer than 12 leads.

Files and the bundled examples skip this step: read_signal and load_example return a SignalData that carries its sampling rate, lead names and unit, and ecg_record accepts it directly.

import ecgfeat

example = ecgfeat.load_example("sinus")            # PTB-XL record, CC BY 4.0
from_file = ecgfeat.read_signal("ecg.npz", sampling_rate=500)   # WFDB, EDF, CSV, NumPy
print(example)                                     # SignalData(12 leads [...], 10 s @ 500 Hz, mV)

2. Measure

from ecgfeat import ecg_record

record = ecg_record(signal, sampling_rate=500, lead_names=leads)

print(record.record_id)          # rec-5f0b3c...: derived from the signal and settings
print(record.schema_version)     # 1.1.0
print(record.profile)            # summary
print(len(record.axes["beats"]), "beats")

ecg_record returns an immutable, already validated ECGRecord. With the same input and settings, the record, and its record_id, are identical on every run.

3. Read values

from ecgfeat import query_measurement

hr = query_measurement(record, "heart_rate_bpm")
print(hr.value, hr.unit)                     # 60 1/min

qrs = query_measurement(record, "qrs_duration_ms", lead="V1", beat=3)
print(qrs.value, qrs.unit)                   # e.g. 160 ms
print(qrs.absence)                           # None: the value was measured
print(qrs.validation.tier)                   # unvalidated
print(qrs.address)                           # ecg-record:rec-…@1.1.0#/measurements/intervals/qrs_duration_ms/values/3/6

Per-beat, per-lead measurements need a lead and a beat (a zero-based index into record.axes["beats"]). Record-level measurements such as the heart rate take neither, and median-beat values per lead take only a lead. When a value is missing, value is None and absence says why; see Reading and querying records.

The record itself has shortcuts:

print(record.summary())                          # global (record-level) rate, intervals, axes
print(record.query("qt_interval_ms"))            # qt_interval_ms = 443 ms [record; validated]
print(record.query("median_r_amplitude_uv", lead="V5"))   # ... µV [lead V5; unvalidated]
qt = record.get("qt_interval_ms")                # the bare value (443) when that is all you need
table = record.to_dataframe("global")            # pandas: value, unit, state, validation
print(table.loc["qrs_duration_ms", ["value", "unit", "validation"]].tolist())

4. Save and load

from ecgfeat import dumps_record, load_record

with open("ecg.record.json", "wb") as handle:
    handle.write(dumps_record(record))       # canonical UTF-8 JSON bytes

restored = load_record("ecg.record.json")    # strict validation by default
assert dumps_record(restored) == dumps_record(record)

Reading a record needs neither the signal nor the numerical engine. Anyone with the JSON file can query it.

5. Plot

import matplotlib
matplotlib.use("Agg")                        # headless; omit in a notebook

from ecgfeat.viz import plot_record

fig, axes = plot_record(signal, record, leads=["II", "V1", "V5"],
                        start_s=1.0, end_s=4.0, annotations="all")
fig.savefig("record.png", dpi=150)

The plot shows the raw signal with the record's P, QRS and T landmarks. The signal must be the exact array that produced the record (its SHA-256 is checked). The conventional 12-lead print is one call:

from ecgfeat.viz import plot_ecg

fig, ax = plot_ecg(signal, record)           # 3 x 4 + rhythm strip, 25 mm/s, 10 mm/mV
fig.savefig("ecg.png", dpi=150)

See Plotting for median beats, ST, rhythm and quality views.

6. The same from a shell

ecg-record measure ecg.npy --sampling-rate 500 --lead-names standard_12 -o ecg.record.json
ecg-record measure ptbxl/records500/00000/00001_hr.hea -o rec.json   # rate and leads from the file
ecg-record validate ecg.record.json
ecg-record summary ecg.record.json
ecg-record query ecg.record.json qrs_duration_ms --lead V1 --beat 3
ecg-record plot ptbxl/records500/00000/00001_hr.hea --kind paper -o ecg.png

See Command line for every subcommand, including parallel batch processing.

7. Interpretation (optional)

With pip install ecginterpret, analyze measures once and returns the record and a rule-based Interpretation document; render_report prints it.

from ecginterpret import analyze, render_report

record, document = analyze(signal, sampling_rate=500, lead_names="standard_12")
print(render_report(document, record))       # also format="markdown" or "html"

Next steps