Changelog — ecg-records¶
All notable changes to the ecg-records distribution (import name ecgfeat).
The ECG Record schema is versioned separately (see schema_version in every
record); this file tracks the package.
[Unreleased]¶
[0.3.2] - 2026-10-19¶
Shell and engine release. The two must be upgraded together: the staged
pipeline moved into the engine, so ecg-records 0.3.2 needs
ecg-records-engine 0.3.2 or later and the 0.3.2 engine needs ecg-records
0.3.2 or later (engine/all extras: ecg-records-engine>=0.3.2,<0.4). On a
platform without a 0.3.2 engine wheel, pip install "ecg-records[engine]"
installs 0.3.1 + 0.3.0; a 0.3.2 shell next to an older engine raises
EngineVersionError (engine_too_old) with both ways out.
Changed¶
summary()(text and HTML) states that its values are global: a "Global measurements (record level, across all leads)" line follows the header. Lead and beat values are read withrecord.query.- The documentation's quick-start example prints only
record.summary(); the quickstart page and notebook printrecord.query(...)lines instead of bare numbers. str(MeasurementResult)is one readable line (qrs_duration_ms = 128 ms [lead V1, beat 0; unvalidated]).ECGRecord.getandECGRecord.queryare typed (queryreturnsMeasurementResult).MeasurementSelectorErrormessages name the field's level and the valid selectors;lead=alone on a per-beat field says that no lead-level value exists and how to aggregate. Error codes are unchanged.- Documentation:
ecginterpretis released (FAQ); problems are reported at the public repository; 0.3 writes schema 1.1.0; the performance page has a 0.3 column. - Validation:
heart_rate_bpm,p_axis_deg,frontal_qrs_axis_degandt_axis_degareindirectly_validated(schema 1.1 registry): on the PTB-XL hold-out they agree with GE 12SL at least as closely as Glasgow Uni-G does (CSE-trimmed;benchmarks/evidence/ptbxl-*-record-1.1.json). The per-leadt_offsetisbenchmark_validated(LUDB beat × lead error SD 28.1 ms against the CSE tolerance of 30.6;ludb-t-offset-lead-1.1). The evidence was re-run on the 0.3.2 engine; per-lead median-beat amplitudes were compared with 12SL for the first time and stayunvalidated(numbers in the docs). -
Plotting gallery images re-rendered with the current engine; they now show the P peak, T onset and T peak (profile
all). -
The staged pipeline's context, stages and policies moved into the compiled engine (
ecgfeat._engine.pipeline); they carry measurement policy (pacing, QT, applicability, lead integrity, the stage algorithms). The extractor that orders the stages, the record builders and the file/segment front ends stay in the open shell. The old paths (ecgfeat.pipeline.context,ecgfeat.pipeline.stages.*,ecgfeat.pipeline.policies.*) are silent aliases of the same module objects, somock.patchtargets keep working.ecgfeat.record.availabilitymoved to the dependency-freeecgfeat.availability(re-exported at the old path). - Per-lead fiducials: the research round of 2026-10-16 is in the engine (record-P projection onto sinus-like beats, spike-aware paced QRS anchors, small-T baseline/consensus and record-QT bound for lead T ends, AF retraction by the median-beat sinus P, short-PR WPW fallback; PAC follow-up). On LUDB the beat x lead error SD falls from 37.2 to 20.4 ms (P onset), 34.1 to 19.2 (P offset), 12.0 to 10.8 (QRS onset), 15.5 to 12.2 (QRS offset) and 38.0 to 28.1 (T offset, now within the CSE tolerance of 30.6 ms); record-level intervals are unchanged within noise. Published per-lead values change, so the golden baseline is re-frozen.
- The preserved pre-decomposition orchestration (the
ECGFEAT_LEGACY_ORCHESTRATION=1rollback path) moved into the compiled engine (ecgfeat._engine.legacy_orchestration); it carries engine algorithms verbatim.ecgfeat.compat._api_v0_legacy_orchestrationstays as the shell-side entry that installs the interpretation and validation hooks. - Faster extraction, same bytes: exact medians without NumPy's generic reducer in the P-wave, delineation, atrial-residual and pacing-spike code paths and scalar clips in the J-point search (about 9% on the bundled examples; the golden sentinel corpus is byte-identical).
Fixed¶
ecgfeat.PWaveConfigis the classECGConfig.p_waveaccepts, soECGConfig(p_wave=ecgfeat.PWaveConfig())works (known issue since 0.1.0; the legacy engine's class of the same name stays private).plot_ecg(annotations="waves"): the marker legend gets its own footer line; with the 0.3 landmarks it overlapped the paper-speed note.
Added¶
ECG_RECORDS_NO_LICENSE_NOTICE=1silences the engine's non-commercial licence notice (an expired or invalid key is still reported). Takes effect with an engine release that contains it.
[0.3.1] - 2026-10-16¶
Shell-only patch: works with ecg-records-engine 0.3.0 (any 0.3.x).
Fixed¶
- Project links on PyPI point to the documentation site and the public
repository
humansys-lab/ecg-records(0.3.0 linked the source repository, which is no longer public). - Documentation: current-version mentions, the
ecginterpretversion (0.2.1, which requires ecg-records 0.3 with the engine), the P-wave settings known issue (still present in 0.3), and the quickstart notebook's install line and outputs.
[0.3.0] - 2026-10-15¶
Added¶
- Three more per-beat, per-lead landmarks in
/delineation/fiducials(profilesallanddebug; thesummarybyte contract is unchanged):p_peak,t_onsetandt_peak(native samples, enginep/peak,t/onset,t/peak), additive within schema 1.1. A P peak outside its P bounds, a T onset after the T offset or a T peak outside its T bounds is withheld withfiducial_order_violation; the final P/T bounds and every interval keep their values. ecgfeat.viz:plot_record,plot_beat,plot_representative_beat,plot_clinicalandexport_landmarksdraw/export the P peak, T onset and T peak when the record carries them (colour names the wave, shape the landmark:^peak,>onset).
Changed¶
- Distribution split. The numerical engine (
ecgfeat._engine) now ships only as the compiledecg-records-enginewheels (CPython 3.10–3.14; Linux x86_64/aarch64, macOS x86_64/arm64, Windows x86_64), licensed PolyForm Noncommercial 1.0.0 or commercially.ecg-recordsstays Apache-2.0 and pure Python; install both withpip install "ecg-records[engine]"(any versions of the same minor version work together, so a shell-only or engine-only patch release does not need the other package). Without the engine, measuring raisesEngineNotInstalledError; records can still be read, queried and plotted. New:ecgfeat.license_info(),EngineNotInstalledError,EngineVersionError; offline licence keys (ECG_RECORDS_LICENSE_KEY,ECG_RECORDS_LICENSE,~/.config/ecg-records/license.key). - The Kalman recursion of the compiled engine is a Cython kernel
(
_kalman_kernel.pyx, bit-identical to the NumPy/Numba reference); Numba is only used by source checkouts. - Atrial measurement (engine): the P boundary uses the T-corrected primary signal when it agrees better, with a record-quiet noise fallback; one conducted P is kept per cardiac cycle and weak extra atrial events are dropped in anchored cycles. On PTB-XL, PR availability rises (dev .902→.913, hold-out .972→.979) with PR error unchanged; LUDB global measurements are byte-identical.
[0.2.0] - 2026-10-14¶
Writes ECG Record schema 1.1.0 (additive) and reads 1.0.0 unchanged. Every value 0.1 published keeps its value, unit and absence encoding on the same input; the engine's measurement corrections of 2026-09-24..10-14 are included.
Added¶
- Schema 1.1.0 (
ecgfeat/schemas/ecg-record/1.1/, own validation registry): - record-level fields in
/measurements/global:p_duration_ms,pr_interval_ms,qrs_duration_ms,qt_interval_ms,qtc_fridericia_ms,qtc_framingham_ms,qtc_hodges_ms,p_axis_deg,t_axis_deg,qrs_t_angle_deg,atrial_rate_bpm,heart_rate_min_bpm/max,rr_mean_ms,rr_sd_ms,rmssd_ms,pnn50,rr_cv,qt_dispersion_ms,ptf_v1_uv_ms,sokolow_lyon_uv,cornell_voltage_uv,paced_beat_fraction,pacing_spike_count,organized_p_fraction,atrial_residual_rms_uv,atrial_dominant_cycle_ms; - median-beat per-lead fields (
median_*): P/Q/R/S/R′/T amplitudes, ST at J, J+40, J+60, J+80, P duration, PR, QRS, QT, Q duration, QRS area; - per-beat
rr_interval_ms; beatgroup,dominant,paced; /axes/atrial_eventsand/axes/pacing_spikes(all/debug);- per-lead quality
grade(every profile) andreason_codes,reliable_for, noisemetrics(all/debug); - per-cell feature states (
observed,unassessable, ...) on every field. - Validation: the four record-level intervals (P duration, PR, QRS, QT)
are
benchmark_validatedon LUDB 1.0.1 against IEC 60601-2-25 limits (CSE trimming); QRS and QT also agree with GE 12SL on the PTB-XL hold-out (indirect evidence). Manifests, tools and the non-passing results (per-lead fiducials; P duration and PR against 12SL) are inbenchmarks/evidence/. - File readers:
read_signalfor WFDB (formats 16, 212, 24, 32, 61, 80 natively;.mat), EDF/EDF+/BDF, CSV/TSV and NPY/NPZ, returningSignalData(now withmetadata,select,window);record_from_file; case-insensitive lead names (normalize_lead_name);lead_names="standard_12";ecg_record/ecg_prepareacceptSignalData. - Long recordings:
ecg_record_segmentswith/metadata/segment. - Examples:
load_example,list_examples,ecgfeat.datasets.describe_example-- three human-validated PTB-XL records (CC BY 4.0, see NOTICE). - Record conveniences:
ECGRecord.get,query,global_values,summary,to_rows,to_csv,to_dataframe("long"|"global"|"leads"|"beats"), a readablereprand_repr_html_; group-qualified names ("global/qt_interval_ms") and selector-based disambiguation. - Plots:
plot_ecg(ECG paper in real millimetres, 3x4/6x2/12x1 + rhythm strips, calibration, measurement header),plot_median_beats,plot_st_segments,plot_rhythm, a noise heat map inplot_quality_summary,PlotStylewith light/dark themes (set_style,use_style,style=),annotations_to_dict,plot_record_interactive(Plotly,interactiveextra). - CLI:
ecg-record summary,table(CSV/Parquet),plot,examples;measurereads any supported file and takes rate/leads from it,--start/--end;batchaccepts directories and globs, runs worker processes and writes--table. - Extras
table(pandas, pyarrow),interactive(Plotly),all; Python 3.14. - Release tools:
tools/check_record_crosswalk.py(live crosswalk over the sentinel corpus),tools/build_gallery.py,tools/make_examples.py.
Changed¶
- Summary records are up to 30,000 bytes for the 10 s / 12-lead / 10-beat
reference case (28,855 bytes; 24,000 in schema 1.0) and now include
qtc_bazett_ms. plot_recordandplot_beat_all_leadsuse one shared millivolt span by default (y_scale="independent"for the 0.1 look); markers shrink on long windows; fiducials use three hues (P, QRS, T) with marker shape per landmark (validated for colour-vision deficiency).ecg-record batch --jobs Nuses processes (--threadsrestores threads).- Internal readers share one cached document per record (faster queries, tables and plots).
Deprecated¶
ExtractionConfig(useECGConfig),extract_recordandextract_ecg_record(useecg_record): they warn withECGDeprecationWarningand are removed no earlier than 0.4.0.
Fixed¶
- Schema 1.0 resources are again byte-identical to 0.1.0: the per-cell
feature states and presence flags added since were emitted under
schema_version1.0.0 and now belong to 1.1.0.
Known limitations¶
- Per-lead fiducials do not meet the CSE delineation tolerances on LUDB and
stay
unvalidated; P-wave boundaries are the weakest endpoint. ecginterpret.interpret_recordstill needs the raw signal (the rule engines read engine state beyond schema 1.1); useecginterpret.analyzeto measure and interpret in one pass.
[0.1.0] - 2026-09-24¶
First release of the redesigned library (previously the unpublished
ecgfeat-dxl-inspired 0.1.0 working tree).
Added¶
- ECG Record (schema
1.0.0):ecg_record,ecg_prepare/ecg_measure/ecg_emit,load_record,validate_record,dumps_record,query_measurement,query_many,select_measurements,resolve_address,iter_leads,iter_beats, profilessummary/all/debug, canonical addressesecg-record:<record_id>@<schema_version>#<RFC 6901 pointer>, and three explicit absence states (plain null,unmeasurable(reason),not_applicable(reason), with a lossless per-field default state). - Packaged JSON Schema and validation-evidence registry
(
ecgfeat/schemas/ecg-record/1.0/); strict reading rejects validation claims above the registry. Every published field isunvalidatedin this release. - Verified NPZ sidecar for dense matrices (
serialize_record(..., sidecar_uri=...), CLImeasure --sidecar), deterministic bytes, SHA-256 and identity bound. ecg-recordcommand line:measure,validate,query,resolve,select,batch.- Record-based plotting in
ecgfeat.viz:plot_record,plot_beat,plot_beat_all_leads,plot_representative_beat,plot_quality_summary, all taking(signal, record)and returning(Figure, axes)through the object-oriented Matplotlib API; the signal is checked against the record's raw-signal SHA-256.import ecgfeatnever imports Matplotlib. - Extras:
viz(Matplotlib, forecgfeat.viz),interpret(installsecginterpret),performance(Numba),wfdb.
Changed¶
- Extraction runs as an explicit staged pipeline (input, quality, ventricular, beats, delineation, atrial, measurement, finalize) with pacing, QT, applicability and lead-integrity policy objects. Measurements are unchanged: legacy output is byte-identical to the pre-migration baseline on 1,063 golden records from seven public datasets.
- Engine modules are private (
ecgfeat._engine); interpretation moved to theecginterpretdistribution; the legacyECGFeaturesplots moved toecgfeat.viz.legacy. - Published records never contain an impossible fiducial order or a negative
interval: such cells are
unmeasurable(fiducial_order_violation)/unmeasurable(negative_interval)instead of failing the whole record.
Deprecated¶
ecgfeat.ECGFeatureExtractor/ecgfeat.api,ecgfeat.to_dictand theecgfeat.exportentry points, top-levelinterpret,plot_*,ecgfeat.visualize,load_wfdb_mat,parse_wfdb_header, and the old private module paths. They warn on use and are removed no earlier than 0.3.0;ecgfeat.compatis the explicit legacy contract meanwhile.
Known limitations¶
- No published field has an approved evidence manifest yet (all
unvalidated). - P-wave delineation is the weakest endpoint in the repository benchmarks.
- ECG Record 1.0 publishes a compact subset of the engine's measurements;
ecginterpret.interpret_recordtherefore needs the raw signal.