Wavelet-based ECG delineator, ported from ecg-kit MATLAB with output parity
Project description
wavedet
A Python port of wavedet, the wavelet-based ECG delineator from
ecg-kit (GPL-2.0, upstream dead since 2020-02). It marks
P/QRS/T onsets, peaks, and offsets. Nothing else from the kit is ported.
from wavedet import delineate
d = delineate(signal, fs=250) # 1-D → single lead
d = delineate(signal_12lead, fs=500) # 2-D (n, leads) → SLR multilead fusion
d.qrs, d.qrs_on, d.t_off, d.p_on # np.ndarray[float], NaN = not found
d.per_lead[0].t_off # single-lead results retained
Parity
The goal is output parity with the MATLAB original, not per-function parity. The pass/fail bar,
per mark type per record: ≥99% of marks bit-identical to MATLAB, 100% within ±1 sample, and an
identical NaN pattern. tests/test_parity.py is that gate; it runs offline against committed
goldens, so it stays runnable without MATLAB. PARITY.md reports the full-database
comparison — the whole of QTDB + LUDB against MATLAB, with per-fiducial deviations and the
wavelet-transform ulp that is the root cause of every divergence.
Development
uv run ruff check
uv run ruff format --check
uv run mypy src/
uv run pytest # gate against committed goldens
The goldens and the signals they were made from are both committed, so the gate needs neither
MATLAB nor network. Because the gate is bit-exact and the wavelet transform's dot products route
through BLAS, conftest.py pins OpenBLAS to a fixed, CPU-independent kernel
(OPENBLAS_CORETYPE=Prescott); without it the last ~1e-14 — and so a few knife-edge marks — would
vary by host CPU. uv run pytest --full-oracle additionally runs the port over the whole of
QTDB + LUDB as a robustness sweep (downloads the databases via wfdb; no goldens).
Scope
Ported: single-lead wavedet, and SLR multilead (per-lead delineation fused by a k-nearest-neighbour rule; needs ≥3 leads).
Not ported: the VCG/3D spatial loop (delineate3D, delineateP3D, twave3D, optimline).
It is dead code upstream and cannot be fed. Reaching it needs three commented-out regions of
wavedet_3D.m restored, and even then there is no input path: leadsynth_flag is hardcoded to 0,
and leadcalc.m — the Dower/Levkov transform that would synthesize the orthogonal XYZ leads it
requires — is absent from the kit entirely. Leads I/II/III are not a substitute, since III = II − I
makes the loop rank-2 and degenerates optimline's fit. optimline also depends on lsqnonlin,
which has no bit-identical scipy equivalent, so its marks could not clear the parity gate anyway.
Authorship
Most of this port was written by an AI model (Claude Opus 4.8), under human direction and review. The parity gate and the PARITY.md full-database comparison are how that work was held to the MATLAB original.
License
GPL-2.0-only, inherited from ecg-kit. Its LICENSE.txt is the plain GPL-2 text with no "or later"
grant, so this derived work cannot relicense upward.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wavedet-0.1.0.tar.gz.
File metadata
- Download URL: wavedet-0.1.0.tar.gz
- Upload date:
- Size: 142.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b8a170f65a68143cd0cbd8818b49d54138b3c214551399fe49a044bbd7b03a2
|
|
| MD5 |
fb7239e7d72711b37db813e4a7c0b415
|
|
| BLAKE2b-256 |
5ad07be30dd6a1c1ce8a60a2a2cf2f96670b91ae8484745d9d490c4971bd011d
|
File details
Details for the file wavedet-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wavedet-0.1.0-py3-none-any.whl
- Upload date:
- Size: 69.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
315d64ac0277912b3e53d1c0ef00d601d54364c04b46c3d1770c3011a7e1a1cd
|
|
| MD5 |
cbf8c1823163da2196683db3cb629ee6
|
|
| BLAKE2b-256 |
cd66c8ab458e71765128ca2f01626526e5e576921a014618c399040c1b0aa45e
|