Extract clean CSVs from your OSCAR CPAP data
Project description
oscar-etl
Extract clean CSVs from your OSCAR CPAP data.
Why this exists
The manual OSCAR export gives you three flat CSV dumps -- a daily summary, per-session stats, and a massive interleaved details file mixing pressure readings with apnea events in a narrow format. It only includes three signals (pressure, EPAP, flow limitation), omits leak, respiratory rate, tidal volume, minute ventilation, and snore entirely, and uses BiPAP column headers that are always zero for your APAP machine. Session boundaries are also only available through the OSCAR GUI -- the CSVs don't make it easy to correlate events with specific mask-on segments.
oscar-etl reads the same raw EDF files OSCAR does, but produces cleaner, more complete output. It splits sessions by each mask-on segment (matching OSCAR's internal model), gives you seven signals in a wide-format timeseries CSV (one row per 2-second timestamp instead of one row per measurement), and separates events into their own file for easy correlation with sleep-tracker data. Daily stats are recomputed from the raw signal data across all sessions -- not approximated from the longest session or averaged. The whole thing runs in about a minute with no manual clicking.
Quick Start
pip install oscar-etl
oscar-etl
Expected output:
oscar-etl v0.1.0
Data source
-----------------------------------
Location: /Users/you/OSCAR_Data
Profile: Your Name
Machine: ResMed_23192261096
EDF files: 2,847
Extracting
-----------------------------------
Discovering sessions ━━━━━━━━━━━━━━━━ 100%
Parsing EDF files ━━━━━━━━━━━━━━━━ 100%
Writing sessions.csv ━━━━━━━━━━━━━━━━ 100%
Writing daily.csv ━━━━━━━━━━━━━━━━ 100%
Writing events.csv ━━━━━━━━━━━━━━━━ 100%
Writing timeseries.csv ━━━━━━━━━━━━━━━━ 100%
Output
-----------------------------------
oscar-etl-output/
cpap_sessions.csv 42 KB
cpap_daily.csv 18 KB
cpap_events.csv 1.2 MB
cpap_timeseries.csv 812.4 MB
Done in 58s
Four CSV files land in ./oscar-etl-output/. See Output Files for what each contains.
macOS Setup
On macOS, OSCAR stores data in ~/Documents/OSCAR_Data/, which is protected by the OS. You need to grant Terminal Full Disk Access temporarily to move the data somewhere unprotected. There are two options.
Option A: Symlink (recommended)
This moves your data out of the protected Documents folder and symlinks it back so OSCAR still finds it. You only need to do this once, and afterward no special permissions are required.
-
Open System Settings > Privacy & Security > Full Disk Access and enable Terminal.
-
Move the data and create a symlink:
mv ~/Documents/OSCAR_Data ~/OSCAR_Data ln -s ~/OSCAR_Data ~/Documents/OSCAR_Data
-
Open System Settings > Privacy & Security > Full Disk Access and disable Terminal.
-
Verify it works:
ls ~/OSCAR_Data
oscar-etl now reads from ~/OSCAR_Data permanently, no special permissions needed. OSCAR itself continues to work because the symlink points it to the new location.
Option B: Leave Full Disk Access on
If you prefer not to move files around, just leave Full Disk Access enabled for Terminal. oscar-etl will read directly from ~/Documents/OSCAR_Data/.
Windows and Linux
No special setup is needed. oscar-etl auto-discovers the OSCAR data directory in the standard locations.
Output Files
All output goes to ./oscar-etl-output/ by default (override with --output-dir).
cpap_sessions.csv
One row per mask-on segment.
| Column | Description |
|---|---|
date |
Night date (see evening date convention) |
session_start |
ISO 8601 timestamp when mask went on |
session_end |
ISO 8601 timestamp when mask came off |
duration_minutes |
Session length in minutes |
ahi |
Apnea-hypopnea index for this session |
ca_count |
Central apnea count |
oa_count |
Obstructive apnea count |
h_count |
Hypopnea count |
ua_count |
Unclassified apnea count |
arousal_count |
Arousal/RERA count |
pressure_median |
Median pressure (cmH2O) |
pressure_95 |
95th percentile pressure |
pressure_995 |
99.5th percentile pressure |
leak_median |
Median leak rate (L/min) |
leak_95 |
95th percentile leak rate |
resp_rate_median |
Median respiratory rate (breaths/min) |
tidal_vol_median |
Median tidal volume (mL) |
minute_vent_median |
Median minute ventilation (L/min) |
cpap_daily.csv
One row per night, aggregated from all sessions for that date.
| Column | Description |
|---|---|
date |
Night date |
sessions |
Number of mask-on segments |
start |
Earliest session start |
end |
Latest session end |
total_minutes |
Total therapy time across all sessions |
ahi |
AHI across all sessions |
pressure_median, pressure_95, etc. |
Same stat columns as sessions, recomputed across all sessions for the night |
cpap_events.csv
One row per event annotation recorded by the machine. This includes therapy events counted in AHI (Obstructive Apnea, Central Apnea, Hypopnea, Apnea, Arousal) as well as other annotations the machine records (Large Leak, CSR, etc.). The session and daily CSVs only count the five therapy event types in their AHI and event count columns.
| Column | Description |
|---|---|
datetime |
ISO 8601 timestamp of the event |
date |
Night date the event belongs to |
session_start |
The session this event occurred in |
event |
Event type (e.g., Obstructive Apnea, Large Leak, Hypopnea) |
duration_sec |
Duration of the event in seconds |
cpap_timeseries.csv
One row per 2-second sample, wide format. This file is large (typically around 800 MB for a year of data). Use --skip-timeseries to skip it.
| Column | Description |
|---|---|
datetime |
ISO 8601 timestamp |
date |
Night date |
session_start |
The session this sample belongs to |
pressure |
Therapy pressure (cmH2O) |
leak |
Leak rate (L/min) |
resp_rate |
Respiratory rate (breaths/min) |
tidal_vol |
Tidal volume (mL) |
minute_vent |
Minute ventilation (L/min) |
snore |
Snore index |
flow_limit |
Flow limitation index |
Evening date convention
Sessions starting before noon are attributed to the previous calendar day. For example, a session that starts at 1:00 AM on March 16 belongs to the "night of March 15." This matches OSCAR and most sleep tracker conventions.
Shift workers or anyone with a non-standard sleep schedule can adjust the cutoff with the --day-boundary flag. For example, --day-boundary 6 attributes sessions starting before 6 AM to the previous day.
CLI Reference
oscar-etl [OPTIONS]
| Flag | Description |
|---|---|
--oscar-dir PATH |
Path to OSCAR_Data directory. Auto-discovered from standard locations if omitted. |
--output-dir PATH |
Output directory. Default: ./oscar-etl-output/ |
--profile NAME |
Profile name. If omitted and multiple profiles exist, an interactive picker is shown. |
--machine SERIAL |
Machine serial number filter. |
--skip-timeseries |
Skip the large timeseries CSV (~800 MB). |
--day-boundary HOUR |
Hour (0--23) that separates sleep nights. Default: 12. |
--version |
Show version and exit. |
Supported Machines
ResMed only, tested with AirSense 10 AutoSet.
Other ResMed models that use the same EDF-based data format should work. The tool warns on unrecognized signal labels rather than crashing, so you can try it and see what happens.
PRs welcome from users with other machines (Philips, Fisher & Paykel, etc.) who can build and test against their own data.
Contributing
Contributions are welcome. Here is a quick orientation for the codebase:
- Signal label mapping lives in
src/oscar_etl/etl.py(PLD_SIGNAL_MAP). This is the dictionary that maps ResMed EDF signal labels to the normalized column names used in the output CSVs. - EDF parser lives in
src/oscar_etl/edf.py. It is format-agnostic and reads any valid EDF file. - To add support for a new machine, you would add signal label mappings for that machine's EDF signal names and test against real data from that machine.
Issues and PRs are welcome on GitHub. If you are adding support for a new machine, please include sample output (anonymized) so reviewers can verify correctness.
License
MIT -- see LICENSE for details.
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 oscar_etl-0.1.0.tar.gz.
File metadata
- Download URL: oscar_etl-0.1.0.tar.gz
- Upload date:
- Size: 41.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dea4489ad9eb2edde9a43ab00b095d32ae4533b1886106dd2de21dc6422de0a3
|
|
| MD5 |
c8958a59492eca792083f9d19a9459ef
|
|
| BLAKE2b-256 |
c8e58102999e1df95e29b8dae0605baafc152ace4d442db01954b5d42f7c2903
|
File details
Details for the file oscar_etl-0.1.0-py3-none-any.whl.
File metadata
- Download URL: oscar_etl-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cda86c874fb10f33ae6d08dfd8c1b60c141ea105fb61587e3be07d4f2aab0a94
|
|
| MD5 |
1f9b7d66d6c00b75074a68c7bd4ec021
|
|
| BLAKE2b-256 |
cdb2af5d8414a6d16ca20848a73b5d8a8685066f50b92204e898110ac2a576e1
|