Method

How Kymo’s heart reading works

Version 1.0 · 2 November 2026 · Contact: kymobreathwork@gmail.com

What it is

Kymo offers an optional, 60-second heart reading before and after a breathing session, and as part of a morning check-in. The user rests a fingertip lightly over the phone’s back camera and flash. Kymo estimates heart rate (beats per minute) and heart-rate variability (RMSSD, in milliseconds) from the video, and shows the user their own change, for example “72 → 66 bpm”.

It is a wellness feature, not a medical device. It isn’t intended to diagnose, treat, monitor or prevent any condition, and the app says so where readings appear: “It isn’t for diagnosing or monitoring any condition. Check with a doctor before making health decisions.” Every number is labelled as an estimate (“HRV (est.)”, “bpm est.”). Kymo doesn’t measure blood oxygen, blood pressure, temperature or glucose, and makes no diagnosis. Its one derived figure, a stress estimate, is described below.

Method: photoplethysmography (PPG)

With each heartbeat, blood volume in the fingertip rises and falls, which changes how much of the flash’s light the tissue absorbs. The camera sees this as a small, periodic change in the red channel.

  1. Capture. The back camera runs with the flash on. For every frame, an on-device frame processor averages a 48 × 48 grid over the central half of the image into red and green brightness values (rebuilt from the YUV frame as R = Y + 1.402 (Cr − 128) and G = Y − 0.344 (Cb − 128) − 0.714 (Cr − 128)), plus the share of clipped pixels and a redness score. Only those numbers leave the camera thread. Red and green are analysed side by side: red is brighter, green often carries the clearer pulse through a fingertip. The live readout follows the steadier channel, and the result keeps the more trustworthy one. No frame is recorded, stored or transmitted.
  2. Resampling. Samples arrive at the camera’s frame rate (typically 25–60 fps, sometimes uneven) and are resampled to 50 Hz. A gap of more than 0.25 s breaks the beat chain, so no interval can span missing data.
  3. Quality gates, on every sample.
    • Dark: mean red under 30 of 255 (no light reaching the sensor, for example the flash off).
    • Pressure: saturated (mean over 247, or half the pixels clipped) or a pulse too flat to read (usually pressing too hard).
    • Moved: sudden level changes, bursts of band-passed energy, or a frame that isn’t red (the finger lifted or slid off).
    Gated stretches are excluded from the reading. The user sees a plain message (“Rest your fingertip lightly…”) instead of a number.
  4. Live signal. A ~1 s DC tracker removes the baseline; a causal 4th-order Butterworth band-pass (0.65–3.5 Hz, about 39–210 bpm) isolates the pulse; an adaptive peak picker (refractory period about 0.55 × the recent interval, envelope-following threshold) finds beats. A quality score (0–1) combines autocorrelation periodicity, ±20% interval regularity and amplitude. The signal counts as steady once quality passes 0.5, and stops being steady below 0.3. The live state only guides the user (“Rest your fingertip lightly…”) and decides when the camera locks its exposure and white balance (at the first steady pulse, so automatic adjustments can’t wash the pulse out); it never decides the number. Timing. The 60-second window starts once the fingertip covers the camera and runs in real time. A reading ends early, with no number, when no steady pulse appears within 40 s, when the pulse is lost for 20 s, or when the camera stops delivering frames for 5 s.
  5. Final analysis. When the window ends, the stored samples are re-analysed with zero-phase filtering, on clean (ungated) segments of at least 5 s. A segment whose periodicity is under 0.35 (typically the fingertip settling, or a shift in pressure) is left out rather than allowed to sink the reading. Each beat is timed at the half-amplitude point of its upstroke, where noise moves it least. Intervals are cleaned against a 9-beat local median (±20%).
    • Heart rate = 60 / mean clean interval, limited to 38–200 bpm.
    • RMSSD uses adjacent clean pairs only (at least 10). The timing-noise energy those pairs carry is estimated from beat-template residuals and the filter’s known noise correlation, and subtracted. RMSSD is withheld when noise is more than half of its energy. Successive differences over 20% of the mean interval (a missed or extra beat) are left out. When the noise estimate exceeds the measured energy by more than 1.5× (impossible for timing noise; on a real fingertip it means the estimate is picking up the pulse’s natural change of shape), RMSSD is computed from the filtered pairs of segments with periodicity of at least 0.45, the usual practice for phone PPG.
  6. Withholding. The whole reading is discarded, and the user told the signal kept moving, when there are fewer than 15 s analysed (or under a third of the recording), periodicity averaging under 0.45, fewer than 15 clean intervals, or fewer than 70% of intervals accepted. Kymo would rather show nothing than a wrong number.

Stress estimate

The Heart tab shows a stress estimate from 0 to 100 for readings that have an HRV. It describes how a resting reading compares with the user’s own usual, where 50 is your usual: lower HRV and a higher heart rate than usual move it up, and the reverse moves it down. HRV carries 70% of the weight (on a log scale; about half the usual HRV reaches the top), heart rate 30% (24 bpm above usual reaches the top). “Usual” is the user’s readings from the last 30 days, blended with typical resting values (68 bpm, RMSSD 40 ms) weighted as three readings, so it starts sensibly and becomes the user’s own within a few readings. It is labelled an estimate, described in words (“lower than your usual”, “about your usual”, “higher than your usual”), and is never presented as a measure of mental health or a diagnosis.

Live readings and the Frequency test

Two Premium features use the same camera pipeline and gates:

  • Live heart during a session shows an estimated heart rate (“bpm est.”) and a soft glow that follows it while the user breathes. Nothing from it is stored.
  • The Frequency test guides five slow breathing paces (about 6.7 to 4.6 breaths a minute, a minute each) with a fingertip reading running throughout. For each pace it fits a sine at the pace’s own breathing frequency to the beat-to-beat heart rate, leaving out the first 15 s of each pace, and names the pace where the heart rate rose and fell most with the breath (often called resonance breathing). It names a pace only when at least four paces had enough clean beats, and says when the runner-up came close. The test stops, with nothing saved, if the user leaves the app or the pulse is lost. It stores the chosen pace and each pace’s score.

Devices that can’t take a reading

A reading needs a back camera with a flash. On a device without one (most iPads, where an iPhone app may run in compatibility mode), or without camera permission, the reading reports that it isn’t available and the session continues without it. Release builds never substitute a simulated reading. Simulated readings exist only in development builds, and wherever one appears it is labelled “Simulated reading, not your heart.”

What is stored

Only the resulting numbers (heart rate, RMSSD when available, a quality value and the time; for a morning check-in, any tags the user chose; for the Frequency test, its pace and scores) are stored, on the device, in the app’s private storage. They are not uploaded, not synced and not written to Apple Health (Kymo writes only Mindful Minutes there). See the privacy policy: kymo-breathwork.pages.dev/privacy/.

Accuracy testing so far

The signal processing is covered by automated tests on synthetic PPG and on two real iPhone recordings:

ConditionResult
Clean pulse, 48–140 bpmHR within ±2 bpm; RMSSD recovered
Added noise at several levelsHR within ±2 bpm; RMSSD corrected for noise, or withheld
Uneven 25–60 fps sampling, dropped frames, jitterHR and RMSSD still recovered
A one-second hole in the streamthe chain breaks, the reading survives
Out-of-order and duplicate timestampsignored
One brief motion burstflagged as moved; the reading stays accurate
Frequent motion bursts, noise without a pulse, flat, saturated or dark signalsno reading (withheld)
Two real iPhone fingertip recordings, each with a lost-pulse stretchHR within 3 bpm of the live display; RMSSD in the resting range

On synthetic data RMSSD comes back within about 20% or is withheld. Low-HRV readings at high heart rates are noise-limited and are usually withheld.

Known limits. Thresholds (light level, motion, flatness, redness, periodicity) were tuned on synthetic data and two real iPhone recordings, and await the comparison study below. Before the first steady pulse the camera’s automatic exposure and white balance are still adjusting, which can weaken the early signal; the final analysis leaves those seconds out when they are irregular. Cold hands, very light pressure, nail polish and movement reduce signal quality; Kymo withholds the reading in those cases rather than guessing.

Validation status

Comparison study, MONTH YEAR. N adults (age range, skin tone range by Fitzpatrick type, devices: models and OS versions) each took paired readings with Kymo and a Polar H10 chest strap (ECG-derived RR intervals) at rest and after a 5-minute session.

  • Heart rate: mean difference x bpm, 95% limits of agreement a to b bpm (Bland–Altman), n readings.
  • RMSSD: mean difference x ms, limits of agreement a to b ms, n readings with RMSSD shown.
  • Withheld readings: x% (reasons: moved / pressure / dark).
  • Raw data and analysis available on request.

Study protocol (planned)

  • At least 10 adults before submission (the release gate in RELEASE.md §7.5), 20–30 for the full study, across a range of skin tones, ages and resting heart rates; at least two iPhone models and two Android phones.
  • Polar H10 worn throughout; RR intervals recorded with a logging app at 1 ms resolution.
  • Per person: a resting reading, a 5-minute resonance session, then a second reading; repeated with the fingertip deliberately pressed hard and lifted once, to confirm the gates.
  • Align Kymo’s 60-second window to the H10 stream by timestamp. Compare HR and RMSSD with Bland–Altman plots; report the withheld rate and its reasons.
  • Acceptance for keeping the feature as shipped: HR limits of agreement within ±5 bpm at rest. If RMSSD limits exceed ±15 ms, keep HRV labelled “(est.)” with its explanation, or hide it.

Contact

Justin Gibbs · kymobreathwork@gmail.com