Skip to main content

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_zi steady-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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opendsp-0.1.0.tar.gz (20.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opendsp-0.1.0-py3-none-any.whl (20.6 kB view details)

Uploaded Python 3

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

Hashes for opendsp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6c7c5464af47ee41a77fd6b4a6ede84e5c3a8f067667ab50c50b53a41cce4c6d
MD5 aeb0a54ad1decf1b9fd85a045ab9bf01
BLAKE2b-256 a151e97390ad6f2d83d77732a9d949e38404da8852a5fba31673c3df33b73f11

See more details on using hashes here.

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

Hashes for opendsp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c812f62308856386e4184568522f699e68cb6dff18068ec1ac3672311b8096da
MD5 e0644d91291834df42aab2a89761f18b
BLAKE2b-256 13a25e4241f7ab88b68dcdab5d0072f2ed7cc2dcdf525b1edf661f816cda8f13

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page