Pneumonitor
Python toolkit for analysing cardiorespiratory data recorded with the Pneumonitor 4 wearable device. The device simultaneously acquires ECG and impedance pneumography signals, enabling synchronised analysis of cardiac and respiratory activity.
Project structure
Pneumonitor/
├── pneumonitor/
│ ├── load.py # Data loading and timestamp normalisation
│ ├── preprocess.py # Signal processing (ECG, respiration, IMU)
│ ├── filters.py # Digital filter building blocks
│ └── plots.py # Interactive Plotly visualisations
├── <recording_id>/ # One folder per recording (e.g. 00001/)
│ ├── bio.txt # ECG + bioimpedance (I/Q) at 500 Hz
│ ├── imu.txt # 3-axis accelerometer at 50 Hz
│ ├── mark.txt # User-triggered event markers
│ ├── stat.txt # Battery and device status
│ ├── imp.txt # Empty in the current version
│ └── info.txt # Recording metadata and error log
└── experiments.py # Example analysis notebook (%-cell format)
Recording format
Each recording folder contains semicolon-delimited text files:
| File | Columns | Description |
|---|---|---|
bio.txt |
timestamp[us], ECG[uV], BiozI[uV], BiozQ[uV] |
ECG and bioimpedance in-phase / quadrature components |
imu.txt |
timestamp[us], X[mq], Y[mg], Z[mg] |
3-axis accelerometer in mg |
mark.txt |
timestamp[us] |
Timestamps of manual markers |
stat.txt |
timestamp[us], BatLevel[%], BatVoltage[mV], BatCurrent[mA], Status[NONE] |
Device telemetry |
info.txt |
Key-value pairs | Hardware/firmware version, sample counts, error list |
Timestamps are in microseconds from device boot. load_data() normalises them to seconds relative to the first biosignal sample.
Usage
1. Load a recording
from pneumonitor.load import load_data
df_bio, df_acc, df_markers, df_stats, errors = load_data('00001')
df_bio already contains the derived Amplitude (√(I²+Q²)) and Phase (arctan2(Q,I)) columns computed from the bioimpedance I/Q pair.
2. Process IMU data
from pneumonitor.preprocess import process_imu
df_acc = process_imu(df_acc, rms_window_sec=1, sampling_rate=50)
Adds Acc_Magnitude[g] (gravity-removed vector magnitude) and Acc_RMS[g] (rolling RMS over the specified window).
3. Preprocess cardiorespiratory signals
from pneumonitor.preprocess import preprocess_cardio_resp
df_bio = preprocess_cardio_resp(df_bio, sampling_rate=500)
Uses NeuroKit2 internally and appends the following columns to df_bio:
| Column | Description |
|---|---|
ECG_clean[uV] |
Cleaned ECG signal |
ECG_R_Peaks |
Binary mask of R-peak locations |
ECG_Rate |
Instantaneous heart rate (bpm) |
Amplitude_clean |
Cleaned respiratory amplitude |
RSP_Rate |
Instantaneous respiratory rate (bpm) |
RSP_Peaks |
Binary mask of respiration peaks |
RSP_Phase |
Respiratory phase (0 = exhalation, 1 = inhalation) |
4. Visualise
from pneumonitor.plots import plot_cardio_resp_data, plot_accelerometer_data
# Cardiorespiratory overview — add extra derived signals as extra subplots
plot_cardio_resp_data(df_bio, df_markers, include_raw=True,
additional_signals=['ECG_Rate', 'RSP_Rate'])
# Accelerometer overview
plot_accelerometer_data(df_acc, df_markers)
Both functions return a Plotly Figure and call .show(). The cardiorespiratory plot shades the respiratory amplitude subplot green (inhalation) / red (exhalation) based on RSP_Phase, and overlays R-peak markers on the ECG subplot.
5. Filters (optional low-level use)
from pneumonitor.filters import bandpass_filter, notch_filter, resp_filter
ecg_filtered = notch_filter(df_bio['ECG[uV]'].values) # remove 50 Hz mains
ecg_filtered = bandpass_filter(ecg_filtered, lowcut=0.5, highcut=25)
resp_filtered = resp_filter(df_bio['Amplitude'].values) # 3–180 bpm bandpass
Installation
Once published to PyPI, install it into any project with:
pip install pneumonitor
# or
uv add pneumonitor
Development
This project uses uv for dependency management.
Installing dependencies
Install all dependencies specified in pyproject.toml:
uv sync
Adding new dependencies
To add a new package to the project:
uv add <package-name>
This will update both pyproject.toml and the virtual environment automatically.
Metadata
Release files for pneumonitor 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pneumonitor-0.1.1.tar.gz | 2.5 MB | Without provenance | Details for pneumonitor-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pneumonitor-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for pneumonitor-0.1.1-py3-none-any.whl |
Total release size: 2.5 MB
Release files / pneumonitor-0.1.1.tar.gz
| Download URL | pneumonitor-0.1.1.tar.gz |
|---|---|
| Size | 2.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d02fc9be6d4d50e73c9ece73124287a5de1cc0b82bf2f09345583c6123423ade
|
|
BLAKE2b-256 checksum How to use checksums |
86cda4b78cfc894f1bb142caf0e9f0fc98e111336c0c17132d7aa433ee6f89e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.4.28
|
Release files / pneumonitor-0.1.1-py3-none-any.whl
| Download URL | pneumonitor-0.1.1-py3-none-any.whl |
|---|---|
| Size | 9.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c260d716514ded2a022f9e5311cb2665a25fcf1ca2d5a135639dc32f1a71606b
|
|
BLAKE2b-256 checksum How to use checksums |
37acaecee1606e7f6755b646ad250d4cbd6f9f7017f0f6fa9231a69d66a498a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.4.28
|
Security for pneumonitor 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.