Skip to main content

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.

:: 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.

Metadata

Release files for o2ring-analyzer 1.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for o2ring-analyzer 1.0.3
File Size Uploaded
o2ring_analyzer-1.0.3.tar.gz 38.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for o2ring-analyzer 1.0.3
File Interpreter ABI Platform
o2ring_analyzer-1.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 74.2 kB

Release files / o2ring_analyzer-1.0.3.tar.gz

Download URL o2ring_analyzer-1.0.3.tar.gz
Size 38.5 kB
Tags Source
SHA-256 checksum
How to use checksums
8f847e9a4e3d4e0ddef267750048254bd761287c55742a59028b269cb1c11450
BLAKE2b-256 checksum
How to use checksums
23d2750dd8be1483696950e5aff9817e96647ec5ac5e61ef5d92bdd61cb32c17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release files / o2ring_analyzer-1.0.3-py3-none-any.whl

Download URL o2ring_analyzer-1.0.3-py3-none-any.whl
Size 35.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c498f98b5f78a8c815a1c6cdeb5d3c544c3cf443745761a7762ecd3e7d55b463
BLAKE2b-256 checksum
How to use checksums
51bdabee675c581245ee86404a6fbe16db4641a5dfa654a20c9ecc33b0315f4c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.3 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page