Skip to main content

Analyze pulse-oximetry CSVs exported from Wellue O2Ring devices (O2 Insight Pro): clean, resample, compute overnight metrics, and export CSV/JSON/Excel.

Project description

o2ring-analyzer

A command-line tool for analyzing pulse-oximetry data exported from Wellue / Viatom O2Ring devices through the O2 Insight Pro software. It reads the CSV export, cleans it, resamples the per-second recording into readable intervals, computes the standard overnight oximetry metrics (SpO2 statistics, time below thresholds, ODI, nadir, desaturation events, pulse statistics), and writes CSV, JSON, and formatted Excel reports with native charts.

Think of it as a spreadsheet tool that already knows what an oximetry export looks like: it turns a 30,000-row file into a summary you can read and a chart you can actually look at.

This is a data-analysis utility, not a medical device. Its output is not a diagnosis. It is for exploring your own exported data; anyone with health concerns should talk to a qualified clinician. See DISCLAIMER.md.

Supported by Night Time Comfort

This project is supported and maintained with backing from Night Time Comfort — an independent sleep-biohacking site covering sleep-tracking hardware, data analysis, gadgets for sleep review, and practical approaches to sleeping better. The backing keeps the tool maintained and free to use.

Background reading on what the metrics this tool produces actually mean, including the 4% drop rule and how to read an overnight oximetry trace: https://www.nighttimecomfort.com/oxygen-desaturation/


What it does

  • Reads one or more O2 Insight Pro CSV files, or a whole folder.
  • Auto-detects the delimiter, text encoding, decimal separator, metadata preamble rows, and column names across app versions and locales — all overridable.
  • Cleans out sensor dropouts and physiologically impossible values so they never get counted as real readings.
  • Resamples to any interval (10min, 5min, 1min, 30s, 1h, …) with per-bucket aggregations you choose.
  • Computes the standard metrics, including ODI under both the 3% and 4% rules with a documented, reproducible algorithm.
  • Exports CSV, JSON, and a formatted Excel workbook with a summary sheet, the resampled data, a desaturation-event table, and charts.

Example console output

=== O2Ring summary — sample_night.csv ===
  Source                   : sample_night.csv
  Duration                 : 479.9 min (100% valid)
  Valid recording          : 479.5 min
  SpO2 mean / median       : 96.4 / 96.0%
  SpO2 min / nadir         : 87 / 88%
  SpO2 p5 / p10 / p90      : 95 / 96 / 97%
  T95                      : 8.5 min (1.8%)
  T90                      : 1.1 min (0.2%)
  T88                      : 0.2 min (0.0%)
  T85                      : 0.0 min (0.0%)
  ODI3                     : 1.8 /h
  ODI4                     : 0.9 /h
  Pulse mean / min / max   : 58 / 45 / 70 bpm
  Pulse variability (SDSD) : 3.6

With rich installed the same summary is rendered as a styled table; without it you get the plain-text version above.

Installation

The tool is Windows-first but runs identically on macOS and Linux. It needs Python 3.10 or newer.

Windows (recommended: virtual environment)

:: From the folder where you unpacked or cloned the project
py -m venv .venv
.venv\Scripts\activate
pip install .

Optional extras for the styled console table and TOML config on older Pythons:

pip install ".[rich,toml]"

To try it without installing, use the convenience wrapper:

run.bat sample_night.csv --format xlsx

macOS / Linux

python3 -m venv .venv
source .venv/bin/activate
pip install .

After installation the o2ring-analyzer command is on your path.

Quickstart

Generate a synthetic overnight file (so you can try the tool without your own health data), then analyze it:

python make_sample_data.py --output sample_night.csv
o2ring-analyzer sample_night.csv --format all

That prints the summary and writes sample_night.csv, sample_night.xlsx, and sample_night.json next to the input.

CLI reference

Option Description Default
inputs One or more CSV files or directories.
--recursive Recurse into directories looking for *.csv. off
--format {csv,xlsx,json,all} Write output file(s) in this format. Omit for console summary only. console only
--output PATH Output file (single input) or directory (multiple inputs). next to input
--interval INTERVAL Resample interval: 10min, 5min, 1min, 30s, 1h, … 5min
--fields LIST Comma-separated output columns, in order. See --list-fields. time,spo2_mean,spo2_min,spo2_max,pulse_mean,valid_pct
--list-fields Print every available field with a description and exit.
--compare Emit a night-over-night trend table across all inputs. off
--col-spo2 / --col-pulse / --col-time / --col-motion Force a source column name instead of auto-detecting. auto
--delimiter / --encoding / --decimal Override format auto-detection. auto
--dayfirst Interpret ambiguous dates as day-first (DD/MM). off (MM/DD)
--spo2-min / --spo2-max Plausible SpO2 range; values outside become missing. 50 / 100
--pulse-min / --pulse-max Plausible pulse range. 25 / 250
--gap-seconds N Inter-sample spacing above which a recording gap is logged. 30
--drop-motion-artifacts Void samples whose motion exceeds --motion-threshold. off
--motion-threshold N Motion value above which a sample is an artifact. 0
--thresholds LIST SpO2 thresholds for the Txx metrics. 95,90,88,85
--odi-drops LIST ODI drop rules, in percent. 3,4
--baseline-window N ODI baseline window, seconds. 120
--min-event-duration N Minimum desaturation duration, seconds. 10
--max-event-duration N Maximum desaturation duration, seconds. 120
--nadir-min-duration N How long a low must persist to count as the nadir, seconds. 8
--config TOML Load defaults from a TOML file (CLI flags still win).
--verbose / --quiet More or less logging. info
--version Print version and attribution.

More recipes are in docs/USAGE_EXAMPLES.md.

How ODI is calculated (in plain language)

The Oxygen Desaturation Index is the number of desaturation events per hour. Different software counts them differently, so this tool uses one fixed, documented rule and reports it so you always know what a number means:

  1. It looks only at valid SpO2 samples.
  2. For each moment it works out a baseline — the average SpO2 over the preceding two minutes (the --baseline-window, default 120 s).
  3. A desaturation event starts when SpO2 drops at least N percentage points below that baseline (N = 4 for the 4% rule, 3 for the 3% rule). The baseline is frozen at its pre-drop value so it can't chase the dip down.
  4. The event lasts as long as SpO2 stays that far below baseline, and ends when it recovers back above the line.
  5. The event only counts if it lasted between --min-event-duration (default 10 s) and --max-event-duration (default 120 s) — this ignores single-sample sensor glitches and multi-minute drifts.
  6. ODI = events ÷ valid recording hours.

The exact formulas and every parameter are in docs/METRICS.md.

Output formats

  • csv — the resampled data with exactly the --fields you chose, written UTF-8 with newline='' so Windows doesn't add blank lines.
  • json — machine-readable metrics for every session, including the full desaturation-event lists, so you can pipe results into other tools.
  • xlsx — an Excel workbook with a formatted Summary sheet (metrics plus the ODI definition footnoted), a Resampled Data sheet, a Desaturation Events sheet, and a Charts sheet: a line chart of SpO2 over time with 90% and 88% reference lines, and a second chart overlaying pulse rate on a secondary axis. Panes are frozen and columns sized.
  • all — writes csv, xlsx, and json.

Troubleshooting

"Could not locate required column(s)" — the tool prints the columns it did find. Pass --col-time, --col-spo2, and --col-pulse with the exact names from that list. See docs/FILE_FORMATS.md.

Dates look wrong (day and month swapped) — your export uses the other date order. Add --dayfirst (or drop it). The tool logs which interpretation it used at --verbose.

"permission denied" writing the .xlsx — Excel on Windows locks a workbook while it's open. Close the file in Excel and run the command again.

Garbled characters in the console — the tool forces UTF-8 output; if your terminal is still on a legacy code page, redirect to a file (... > out.txt) or use Windows Terminal.

Everything shows as missing / 0% valid — the SpO2 column may be pointing at the wrong field, or the file is all dropouts. Run with --verbose to see the detected columns and sample counts.

Contributing

See CONTRIBUTING.md. In short: create a venv, pip install ".[dev]", and run pytest. There's a GitHub Actions workflow running the suite on Windows and Ubuntu across Python 3.10–3.12.

License

MIT — see LICENSE.


Not a medical device; output is not a diagnosis. Supported with backing from Night Time Comfort.

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

o2ring_analyzer-1.0.3.tar.gz (38.5 kB view details)

Uploaded Source

Built Distribution

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

o2ring_analyzer-1.0.3-py3-none-any.whl (35.7 kB view details)

Uploaded Python 3

File details

Details for the file o2ring_analyzer-1.0.3.tar.gz.

File metadata

  • Download URL: o2ring_analyzer-1.0.3.tar.gz
  • Upload date:
  • Size: 38.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for o2ring_analyzer-1.0.3.tar.gz
Algorithm Hash digest
SHA256 8f847e9a4e3d4e0ddef267750048254bd761287c55742a59028b269cb1c11450
MD5 3813f7222ed210970df7f1613367342f
BLAKE2b-256 23d2750dd8be1483696950e5aff9817e96647ec5ac5e61ef5d92bdd61cb32c17

See more details on using hashes here.

Provenance

The following attestation bundles were made for o2ring_analyzer-1.0.3.tar.gz:

Publisher: publish.yml on nighttimecf/o2ring-analyzer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file o2ring_analyzer-1.0.3-py3-none-any.whl.

File metadata

  • Download URL: o2ring_analyzer-1.0.3-py3-none-any.whl
  • Upload date:
  • Size: 35.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for o2ring_analyzer-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c498f98b5f78a8c815a1c6cdeb5d3c544c3cf443745761a7762ecd3e7d55b463
MD5 c44547665253b7c960485c8013abcd04
BLAKE2b-256 51bdabee675c581245ee86404a6fbe16db4641a5dfa654a20c9ecc33b0315f4c

See more details on using hashes here.

Provenance

The following attestation bundles were made for o2ring_analyzer-1.0.3-py3-none-any.whl:

Publisher: publish.yml on nighttimecf/o2ring-analyzer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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