Skip to content

feat: EOlife X phase-timed waveform reconstruction#291

Open
sorlob wants to merge 8 commits into
mainfrom
feat/289-eolife-phase-timing
Open

feat: EOlife X phase-timed waveform reconstruction#291
sorlob wants to merge 8 commits into
mainfrom
feat/289-eolife-phase-timing

Conversation

@sorlob
Copy link
Copy Markdown
Collaborator

@sorlob sorlob commented May 19, 2026

Summary

  • Adds data_timed to EOLifeRecord with timing-adjusted views of the 9 columns in the EOlife export
  • Discrete events (Labels) from 3 groups: cycle_start (Cycle number, Ti, Te, Tp at inspiration onset), insp_end (Vi at end of inspiration), exp_end (Vt, f, Leakage, Leakage ratio at end of expiration)
  • 4 reconstructed Channels: Vi (displayed), V_exp, Vt (displayed), f (displayed)
  • f (displayed) reverse-engineered from screen-recording ground truth on EOlife X firmware v2.1.3: rolling 3-breath mean of cycle durations, updated at next breath onset, NaN when rate > 60 /min
  • Contiguous uncharacterised breaths (Ti/Te exported as "Na", always absent together) exposed as eolife_uncharacterised_breaths IntervalLabel ("Uncharacterised Breaths")
  • source metadata derived from serial number prefix ("EOlife X0001126" → "EOlife X")
  • add_ventilatory_feedback dispatches groups as discrete events or channels and auto-applies DEFAULT_PLOT_STYLE

sorlob and others added 7 commits May 16, 2026 21:30
Add TimeSeriesBase.correct_clock_drift(anchor_time, drift_point, *,
drift=..., true_time=...) for the common workflow of correcting a
linear clock drift from one anchor and one drift observation. The
drift can be expressed either as a signed Timedelta (device − true)
or via the wall-clock time at the second observation; the two forms
are mutually exclusive. The method computes the appropriate
scale_factor and delegates to scale_time_index.

Rework the scale_time_index docstring to name clock-drift correction
as the primary use case, give the explicit mapping formula, and
explain how to derive scale_factor from two sync points so the
method is discoverable by users searching for "drift".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pull in correct_clock_drift / scale_time_index updates (PR #290) for
local use during eolife-phase-timing development. The drift commit
will deduplicate once PR #290 lands in main and this branch is
rebased.
Clock drift is a property of a clock, which typically governs more
than one channel/label of a recording — and some devices have
several clocks (e.g. via OEM modules). Correcting only some of the
channels sharing a drifted clock would desynchronise them, and
correcting unaffected channels would introduce new desync. Document
that users should loop over all channels and labels driven by the
affected clock.
Allow modifying the time index in-place by passing inplace=True, avoiding
the copy overhead when the original object is no longer needed. Default
remains inplace=False to preserve the existing copy-on-modify semantics.
The offset represents a deliberate alignment shift, not a clock-rate
artefact — it should not be scaled along with the recording. Previously
scale_time_index absorbed the offset into time_index and reset _offset
to zero, making it impossible to recover the original alignment after
the fact.

Now the offset is un-baked before scaling and re-applied unchanged
afterwards, so _offset is preserved on the returned series and the
effective timestamps (as returned by get_data) remain consistent.
Pull in inplace parameter, offset preservation fix, and metadata trail
for correct_clock_drift (PR #290) for local use during eolife development.
Adds `data_timed` to `EOLifeRecord`. The 9 columns of the EOlife export
are split into discrete events and reconstructed channels:

Labels (3 with distinct + 1 IntervalLabel):
  - cycle_start:
  - insp_end:
  - exp_end:
  + "Uncharacterised Breaths" IntervalLabel (global, when present)

Channels (4 reconstructed waveforms):
  - Vi (displayed): screen-accurate inspiratory volume ramp
  - V_exp:          expiratory volume ramp for leak comparison
  - Vt (displayed): step-function Vt readout
  - f (displayed):  step-function respiratory rate readout

The `f (displayed)` step function was reverse-engineered from
screen-recording ground truth on an EOlife X (firmware v2.1.3) — rolling
3-breath mean of cycle durations, updated at next breath onset, set to
NaN when rate > 60 /min.

Contiguous uncharacterised breaths — those where the device could not
determine Ti and Te, exported as literal "Na" (always absent together,
never one without the other) — are exposed as an `IntervalLabel`
(`eolife_uncharacterised_breaths`). The `source` metadata is derived
from the serial number prefix (e.g. "EOlife X0001126" → "EOlife X").

`add_ventilatory_feedback` dispatches each group as discrete events or
channels and applies `DEFAULT_PLOT_STYLE` entries automatically. Includes
an EOlife X firmware v2.1.3 test fixture and new test classes covering
uncharacterised-breath intervals and `f (displayed)` firmware behaviour.

Co-Authored-By: dpurkarthofer <dpurkarthofer@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sorlob sorlob requested a review from behackl May 19, 2026 14:55
@sorlob sorlob assigned sorlob and behackl and unassigned sorlob May 19, 2026
Previously f was bundled in exp_end and anchored at onset + Ti + Te,
before the Tp pause — too early for the device to have computed the
cycle time. f is now a separate label group anchored at onset + Ti + Te
+ Tp for fully characterised breaths, falling back to next inspiration
onset when any of Ti, Te, or Tp is missing. Leakage / Leakage ratio are
split into their own leakage group so the label addition order becomes
Vt → f → Leakage / Leakage ratio.

Co-Authored-By: dpurkarthofer <dpurkarthofer@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants