Numpy-only DSP primitives for biosignal analysis — Butterworth IIR, filtfilt, find_peaks, wavelets, resampling. scipy.signal-compatible, no scipy required.
Project description
opendsp
Numpy-only DSP primitives for biosignal analysis. A small, focused
scipy.signal / PyWavelets subset implemented in pure NumPy, with
optional backends (scipy, numba, torchaudio) for speed when available.
opendsp is the shared DSP layer underneath the open biosignal family
(openvital,
openecg,
openeeg). The primitives here are
modality-agnostic — bandpass filtering, peak-finding, wavelets,
resampling — and don't belong to any single signal type.
Why
scipy.signal is excellent but heavy: scipy + its scipy.sparse / BLAS
backing pulls 30+ MB and is awkward to install in stripped-down
inference environments (Lambda, edge boxes, TFLite runners). opendsp
matches scipy's coefficient output to within ~1e-12 on the filter
orders biosignal pipelines actually use (2 and 4), so you can keep your
filter design code identical while shrinking your deploy image.
Installation
pip install opendsp
# Optional speed backends (auto-selected on first lfilter call):
pip install opendsp[speed-scipy] # ~10x speedup, C-backed
pip install opendsp[speed-numba] # near-C after JIT warmup
pip install opendsp[speed-torch] # GPU/MKL via torchaudio
Set OPENDSP_LFILTER_BACKEND (one of scipy, numba, torch,
numpy) to force a specific backend — useful for benchmarking or
reproducibility-sensitive contexts.
API
import opendsp
# Butterworth IIR design (scipy.signal.butter drop-in)
b, a = opendsp.butter(4, 0.1, btype="low")
b, a = opendsp.butter(4, [0.01, 0.4], btype="band")
# Direct filtering
y = opendsp.lfilter(b, a, x)
y = opendsp.filtfilt(b, a, x) # zero-phase forward-backward
# Convenience bandpass (combines butter + filtfilt)
y = opendsp.band_pass(x, srate=128, fl=0.5, fh=30, order=4)
# Peak finding (scipy.signal.find_peaks subset)
peaks, props = opendsp.find_peaks(x, height=0.5, distance=10,
prominence=0.1)
# Wavelets (PyWavelets minimal subset)
coeffs = opendsp.wavedec(x, "db2", level=4)
y = opendsp.waverec(coeffs, "db2")
cwt = opendsp.cwt(x, scales=[1, 2, 4, 8], wavelet="gaus1")
# Resampling
y = opendsp.resample(x, dest_len=1000)
y = opendsp.resample_hz(x, srate_from=500, srate_to=128)
# Misc primitives biosignal pipelines need
y = opendsp.interp_undefined(x) # NaN-aware linear interp
y = opendsp.savitzky_golay(x, window_size=91, order=3)
y = opendsp.moving_average(x, N=10)
y = opendsp.rank_normalize(x) # amplitude-invariant
y = opendsp.remove_baseline_wander(x, fs=500, cutoff_hz=0.5)
Algorithms
- Butterworth: analog prototype poles → bilinear-transform pre-warping → digital (z, p, k) → polynomial (b, a). Matches scipy exactly because both follow Oppenheim & Schafer §7.1.
- filtfilt: reflect-pad ↔ forward ↔ reverse ↔ forward ↔ reverse ↔
trim, with initial conditions set via the
lfilter_zisteady-state method. - find_peaks: strict-left/non-strict-right local maxima (scipy's convention), height filter, greedy-from-largest distance filter, Wim Spalt's two-sided prominence walk.
- wavedec/waverec: DWT with
mode='symmetric'boundary extension (PyWavelets default). Daubechies-2 ships built-in; pass a 4-tuple(lo_d, hi_d, lo_r, hi_r)for any other wavelet. - cwt: Gaussian 1st derivative continuous wavelet transform — the
one used by ECG annotators. Other mother wavelets: pass
pywt.
Scope
opendsp is intentionally narrow:
- ✓ Linear-time-invariant filter design + application
- ✓ Peak / extremum detection
- ✓ Wavelet decomposition (DWT + CWT)
- ✓ Resampling, smoothing, interpolation primitives
What it deliberately does not include:
- ✗ Spectral analysis higher than 1-D periodogram / FFT helpers (use scipy.signal.welch / numpy.fft directly)
- ✗ Adaptive filters, Kalman, ML-based denoising
- ✗ Modality-specific features (QRS, BSR, SEF95 etc. — those live in
openecg,openeeg,openvital)
If you find yourself reaching for one of those, you probably want
scipy.signal or one of the modality libs above.
License
Apache-2.0. See LICENSE.
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 opendsp-0.1.0.tar.gz.
File metadata
- Download URL: opendsp-0.1.0.tar.gz
- Upload date:
- Size: 20.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c7c5464af47ee41a77fd6b4a6ede84e5c3a8f067667ab50c50b53a41cce4c6d
|
|
| MD5 |
aeb0a54ad1decf1b9fd85a045ab9bf01
|
|
| BLAKE2b-256 |
a151e97390ad6f2d83d77732a9d949e38404da8852a5fba31673c3df33b73f11
|
File details
Details for the file opendsp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: opendsp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.6 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 |
c812f62308856386e4184568522f699e68cb6dff18068ec1ac3672311b8096da
|
|
| MD5 |
e0644d91291834df42aab2a89761f18b
|
|
| BLAKE2b-256 |
13a25e4241f7ab88b68dcdab5d0072f2ed7cc2dcdf525b1edf661f816cda8f13
|