-
Notifications
You must be signed in to change notification settings - Fork 645
The Science
NOOP computes recovery, strain, HRV, and sleep locally on your device using transparent, published physiological methods. Every metric is an approximation of common exercise-science and cardiology literature — not a reproduction of any proprietary algorithm, and not a medical device or medical advice.
Not affiliated with WHOOP. NOOP is independent interoperability software for your own device and your own data. The methods described here are grounded in peer-reviewed literature and are the same class of tools used in sports-science research labs — they are estimates, not clinical measurements. Do not use them to diagnose, treat, or make health decisions. Consult a qualified professional.
All mathematics in NOOP is pure, deterministic, and database-free: same inputs always produce the same outputs, which makes every analyzer straightforward to unit-test. The analytics live in the cross-platform StrandAnalytics Swift package, and they run on-device against:
- Live BLE streams (heart rate, R-R intervals, respiration, gravity/accelerometer) arriving from the strap in real time.
- Imported history from WHOOP CSV exports and Apple Health exports.
- Historical offload from the strap's on-device store (~14 days) each time you connect.
The computed metrics are persisted in SQLite under the "<deviceId>-noop" source, but WHOOP's own imports (when you import a CSV) always take precedence — NOOP's approximations win only for data WHOOP doesn't cover.
HRV is the beat-to-beat variation in your heart rate, measured as R-R intervals (the time between consecutive heartbeats in milliseconds). NOOP computes RMSSD — the classic Task Force (1996) metric:
RMSSD = sqrt( (1/(N-1)) · Σ(NN[i+1] − NN[i])² )
Where NN is a sequence of normal R-R intervals (in ms), and each term is the squared difference between consecutive intervals.
Why RMSSD? It captures the parasympathetic tone (vagal activity) of your autonomic nervous system — the "rest and digest" branch that keeps you calm and recovered. Higher RMSSD means more variability, which usually correlates with better recovery and lower stress.
Raw R-R data can contain artifacts — missed beats, extra beats (ectopics), electrical noise. NOOP applies a deterministic cleaning pipeline:
-
Range filter — drop intervals outside the plausible
[300, 2000]ms band (~30–200 bpm). - Ectopic rejection (Malik et al., 1989) — drop any beat deviating more than 20% from a local median over a 5-beat centered window. This is a simpler substitute for the Kubios algorithm (which isn't available on-device) and catches physiologically impossible jumps.
-
Sufficiency gate — require at least 20 clean intervals before returning a result; otherwise return
nilrather than guessing.
Honest substitution: The reference method used neurokit2's Kubios/Lipponen–Tarvainen artifact classifier, which requires external Python libraries. NOOP substitutes the classical Malik local-median rule — fully deterministic, works on-device, and achieves the same intent: removing impossible beat-to-beat swings before computing HRV.
- SDNN — the sample standard deviation of NN intervals (Task Force 1996). A broader measure of variability over the whole recording.
- pNN50 — the percentage of successive differences > 50 ms. A marker of parasympathetic dominance.
NOOP's recovery score is an HRV-dominant, baseline-normalized composite that blends several drivers into a single 0–100 number. It is explicitly approximate — the key insight is that recovery is personal: your good recovery looks different from someone else's, so comparing you to yourself matters more than comparing to a population average.
| Driver | Direction | Weight |
|---|---|---|
| HRV vs your baseline | Higher → better recovery | 60% (dominant) |
| Resting HR vs your baseline | Lower → better | 20% |
| Respiration rate vs your baseline | Lower → better | 5% |
| Sleep performance | Better sleep → better recovery | 15% |
Each metric is standardized against your personal baseline using a robust z-score:
z = (value − mean) / (1.253 · spread)
The 1.253 factor converts an EWMA mean-absolute-deviation (MAD) into an approximate Gaussian standard deviation (E[|X−μ|] ≈ σ / 1.253). For "lower is better" metrics (resting HR, respiration), the z is inverted so high values still push recovery down.
Sleep performance is centered directly: (sleepPerf − 0.85) / 0.12 — anchoring on 85% efficiency as a typical target.
The weighted-mean z-score is then squashed to 0–100 with a logistic curve:
score = 100 / (1 + exp(−k · (z − z0)))
where k = 1.6, z0 = −0.20
The z = 0 point (your personal baseline, across all metrics) maps to approximately 58% — matching WHOOP's published population-average recovery. This anchoring means: when your HRV, RHR, and respiration are all exactly at your typical baseline, your score is ~58%, not 50%. Z = +2 drives toward 95%; Z = −2 drives toward 5%.
| Band | Range |
|---|---|
| Red (needs recovery) | < 34 |
| Yellow (balanced) | 34–67 |
| Green (ready to push) | ≥ 67 |
If your HRV baseline isn't trustworthy yet (fewer than 4 valid nights), NOOP returns nil rather than a fabricated number. This is intentionally honest — more painful than a guess, but more truthful. Callers can fall back to the population mean (58%), but the screen flags it as provisional.
Once you have 4+ nights, the baseline becomes "provisional" (4–13 nights), then "trusted" (14+).
Strain is the cumulative physiological load from exercise during a day. NOOP uses a published, scalable method: the Heart-Rate Reserve (Karvonen) intensity model combined with TRIMP (Training Impulse) accumulation, mapped to a 0–21 logarithmic scale.
HRR = HRmax − Resting HR
%HRR = (HR − RHR) / HRR × 100, clamped [0, 100]
This expresses your current heart rate as a fraction of your reserve — what percentage of your maximum capacity you're using right now.
NOOP supports two methods; the default is Edwards (1993):
Each heart-rate sample contributes its zone weight multiplied by duration:
| Zone | %HRR range | Weight |
|---|---|---|
| Zone 1 | < 50% | 1 |
| Zone 2 | 50–60% | 2 |
| Zone 3 | 60–70% | 3 |
| Zone 4 | 70–80% | 4 |
| Zone 5 | ≥ 80% | 5 |
TRIMP = Σ(duration_in_zone × weight)
An older but well-cited model: duration × x × 0.64 × e^(b·x) where x = %HRR/100 and b = 1.92 (men) or 1.67 (women).
strain = 21 · ln(TRIMP + 1) / ln(D)
where D = 7201
The denominator D is calibrated so that the Edwards daily ceiling — maximum zone weight (5) sustained for 24 hours = 5 × 1440 = 7200 — maps to exactly 21.0. This makes 21 a true physiological ceiling for the day: you'd need to stay in zone 5 all night to reach it.
NOOP estimates your maximum heart rate using two methods:
- Observed (if you have ≥600 HR samples on record): the 99.5th percentile of your recorded HR, used as the starting point.
-
Tanaka (2001):
HRmax = 208 − 0.7 × age. A gender-independent population estimate that serves as a floor — if Tanaka is higher than your observed max, Tanaka is used (to avoid underestimating if you haven't hit your true max yet). - Manual override: you can set your own max HR in Settings.
If you supply no age and have no data, the scorer returns nil.
NOOP detects sleep/wake and attempts to classify four stages: Wake, Light, Deep (slow-wave), and REM (rapid eye movement). This is the same classification used in clinical sleep studies, but without EEG — the ceiling for 4-class accuracy (without brain waves) is ~65–73% epoch-by-epoch agreement (Walch et al., 2019).
Honest hedging: These stages are approximations, not validated against polysomnography (PSG, the clinical gold standard). Light/deep separation is the weakest link. Deep-minute estimates are the least reliable output. Use these trends over time, not individual-night predictions.
The foundation is stillness. NOOP tracks the magnitude of gravity changes (accelerometer) and identifies periods where motion is minimal:
- Per-sample motion proxy = L2 norm of the gravity-vector change from the previous sample.
- A sample is "still" if this delta is < 0.01 g.
- Roll forward in a 15-minute window: if ≥70% of samples are still, the center is marked "sleep".
- Contiguous still runs ≥60 minutes (merging gaps < 15 min) are candidates.
- HR confirmation: the run's mean HR must be ≤1.05× the day's median HR (the sleep baseline). This rejects false sleep from long stillness at rest.
Additionally, NOOP computes the te Lindert 30-second Cole–Kripke sleep index per epoch as a cross-check:
SI = 0.001 · Σ w_i · A_i (sleep iff SI < 1)
weights = [106, 54, 58, 76, 230, 74, 67]
(This is a classical 7-channel electroencephalographic index adapted for actigraphy.)
Over a rolling 5-minute window, NOOP extracts:
- Mean heart rate — averaged over the window.
- HR variability (Walch difference-of-Gaussians) — two Gaussian convolutions of HR (σ = 120 s and 600 s) subtracted; this captures medium-scale HRV oscillations.
-
RMSSD and SDNN — computed from range-filtered R-R intervals (cleaned via
HRVAnalyzer). - Respiration rate and RRV — extracted from the raw 1 Hz respiration channel via a peak detector (detrend → find local maxima ≥2 s apart → bin into breath intervals 1.5–12 s → median interval → rate = 60 / interval).
Omission: Frequency-domain HRV (high-frequency HRV, LF/HF ratio) is not computed because FFT and signal processing require libraries unavailable on-device. The parasympathetic-tone signal is RMSSD only. The respiration peak-finder is a faithful port of the reference method (which also avoided scipy).
The session's sleep-period epochs (Cole–Kripke = sleep) define reference distributions for each feature. Each epoch is then classified by comparing its features to these percentiles:
| Class | Rule |
|---|---|
| Wake | Sustained motion (≥15% of epoch) AND activated cardiac (high HR or high DoG-HR), OR no HR data |
| Deep | Still (≤10% motion) AND high parasympathetic tone (RMSSD ≥70th percentile) AND low HR (≤25th percentile) AND regular respiration |
| REM | Still body AND activated cardiac AND irregular respiration (RRV ≥65th percentile); fallback requires both cardiac signals if respiration is unavailable |
| Light | Everything else (the default) |
- 5-epoch median smoothing of the label sequence — smooths out single-epoch noise.
- No REM in the first 15 minutes — REM sleep doesn't appear right at sleep onset; demote to light.
- No deep after the first third — deep sleep is front-loaded; demote later deep epochs to light.
- Force pre-onset and post-final-wake to
wake.
Consecutive same-stage epochs are merged into segments tiling the session.
-
SleepSession: start, end, in-bed efficiency (
asleep / in-bed), per-session resting HR, average HRV (RMSSD over 5-min windows), and a reconstructed hypnogram (stage durations). - AASM-style roll-up: time-in-bed (TIB), total sleep time (TST), sleep-period time (SPT), sleep onset latency (SOL), REM latency, wake after sleep onset (WASO), efficiency, disturbances, and deep/REM/light minutes.
Recovery and strain scoring depend on personal baselines — your own typical HRV, resting HR, and respiration — so NOOP learns them as you wear the strap. Two interchangeable approaches produce the same result:
A robust, recency-weighted center with exponential-moving-average spread tracking:
- Half-life → smoothing factor: HRV center uses 14-night half-life; spread uses 21-night (slower, more conservative).
- Sanity gate: values outside the per-metric range (e.g., HRV 5–250 ms) are skipped and held.
- Hard outlier rejection: values > 5× spread away are recorded but not folded into the baseline.
- Winsor clamp: fold only within ±3× spread of the current baseline, so a single exceptional night can't yank the center. The spread itself uses the unclamped deviation so real physiological change is still tracked.
let clamped = max(lo, min(hi, value)) // ±3·spread
let newBaseline = lb * clamped + (1 - lb) * state.baseline
let newSpread = max(floorSpread, ls * abs(value - newBaseline) + (1 - ls) * state.spread)Plain mean and sample SD (ddof = 1) over the last 30 valid nights. The σ floor is applied and converted back to absolute-deviation space so the two methods recover the same Gaussian σ.
| Status | Condition |
|---|---|
calibrating |
Fewer than 4 valid nights (no score yet) |
provisional |
4–13 valid nights (usable, higher uncertainty) |
trusted |
14+ valid nights |
stale |
Trusted but no update for > 14 days |
NOOP detects exercise periods without manual logging — a sustained window (≥5 minutes) where both gates hold:
-
Elevated HR: above
RHR + 15 bpm(the resting HR of your session + margin). - Sustained motion: gravity-derived intensity (10-second trailing mean) above 0.20.
Active samples are grouped into runs, merged if gaps are < 150 seconds, then qualified: ≥50% of the bout must be in Edwards zone 2+ to count.
Per workout, NOOP reports:
- Average and peak heart rate.
- Duration, Edwards zone distribution, mean %HRR.
- Strain (computed via the same StrainScorer as daily strain).
- Calories (see below).
Per-second blend with sex-specific coefficients:
- Below
RHR + 0.30 × HRR: use revised Harris–Benedict (resting metabolic rate). - Above: use Keytel (2005) active expenditure model, sex-specific.
The blend provides a smooth transition. Approximate — not laboratory calorimetry.
NOOP provides three on-device interrogation engines for your own data:
r = Σ(x−x̄)(y−ȳ) / sqrt( Σ(x−x̄)² · Σ(y−ȳ)² )
slope = Σ(x−x̄)(y−ȳ) / Σ(x−x̄)²
t-statistic = r · sqrt( (n−2) / (1−r²) )
p-value ≈ 2·(1 − Φ(|t|)) [normal approximation]
- Returns
nilfor fewer than 3 pairs or zero variance. - The normal approximation slightly understates p for small n (true Student-t tails are heavier), but is fully deterministic on-device.
- Supports lagged correlations (e.g., today's strain vs tomorrow's recovery) via day-aligned and shifted series.
Splits days where you logged a behavior (Alcohol, Meditation, etc.) from days you didn't, and compares an outcome (Recovery, HRV, Sleep) between groups:
sp = sqrt( ((n1−1)·s1² + (n2−1)·s2²) / (n1+n2−2) ) [pooled SD]
d = (m1 − m2) / sp [Cohen's d]
t = (m1 − m2) / sqrt(s1²/n1 + s2²/n2) [Welch t]
-
significantrequiresp < 0.05andmin(nWith, nWithout) ≥ 5(guards against spurious significance from a handful of days). - Results are ranked by effect size (|d|), significant findings first.
- Plain-English summary: "On days you logged 'Alcohol', Recovery was 12% lower (avg 61 vs 69, n=140 vs 498)."
Month-over-month, quarter-over-quarter, or custom window comparison of a single metric:
SeriesStat: mean, median, min, max, sample SD (ddof=1), n, and OLS slope-per-day
PeriodComparison: signed delta, % change, direction (-1/0/+1)
NOOP's Readiness screen synthesizes several sports-science signals from your own history into a single readout ("Primed / Balanced / Strained / Run down"):
- HRV vs baseline (Plews/Buchheit) — parasympathetic tone shift.
- Resting-HR drift (Lamberts) — orthostatic stress rising.
- Respiratory-rate drift — autonomic load indicator.
- Acute:chronic workload ratio (ACWR) (Gabbett) — training volume balanced against your typical load.
- Training monotony (Foster) — variability of your training over the past week.
All signals are z-scored against your own baselines and combined with a logistic curve into a single readiness band. Approximate, not medical advice — this is the same class of analysis used in sports-science labs for coaching, not a clinical screen.
NOOP watches for the classic early-illness/elevated-strain signature on-device by comparing your last ~2 days against a ~28-day baseline (ending 3 days ago, to avoid contamination):
| Signal | Baseline shift | Anomaly condition |
|---|---|---|
| Resting HR ↑ | Elevated | Recent mean ≥ baseline mean + 5 bpm |
| HRV ↓ | Depressed | Recent mean ≤ baseline mean × 0.80 (−20%) |
| Skin temp ↑ | Elevated | Recent mean deviation ≥ +0.6 °C |
| Respiration ↑ | Elevated | Recent mean ≥ baseline mean + 1.5 bpm |
A banner appears only when two or more anomalies fire together (e.g., RHR up + HRV down + skin-temp up), the classic early-illness signature. Requires at least 14 days of history.
Important: This is a wellness nudge from your own baselines, not a clinical screen. It is informational only — not medical advice, not a diagnosis.
Every metric — recovery, strain, sleep stages, workout intensity, calories — is an explicit approximation of published methods. The source code documents exactly where it substitutes (Malik local-median instead of Kubios, RMSSD-only parasympathetic tone instead of frequency-domain HRV, normal-approximation p-values instead of Student-t tabled values).
No randomness, no wall-clock dependence inside the math, no database access. Same inputs always produce the same outputs, making the whole package unit-testable against fixed vectors.
- Z-scores use EWMA mean-absolute-deviation (
×1.253to Gaussian σ); rounding resistant. - Resting HR uses 5-minute bin minima; single-beat dips are rejected.
- Heart-rate display uses windowed medians; outliers don't spike the plot.
- Baselines use Winsor clamping; a single exceptional night doesn't yanked the center.
When a baseline isn't trustworthy yet, the scorer returns nil rather than a fabricated number. More painful than guessing, but more truthful.
None of this is diagnostic or medically actionable. HRV, recovery, strain, sleep stages, and the illness early-warning are wellness approximations, not clinical measurements. Consult a qualified professional for health decisions.
The methods implemented in NOOP draw from peer-reviewed literature:
- Task Force (1996). Heart rate variability: standards of measurement, physiological interpretation and clinical use. Circulation, 93(5), 1043–1065.
- Karvonen, M. J. (1957). Effects of vigorous exercise on the heart. Work and Stress, 9, 3–9.
- Edwards, S. (1993). The heart rate monitor book. Polar Electro (Oy).
- Banister, E. W. (1991). Modeling elite athletic performance. In Physiological testing of the high-performance athlete (pp. 403–424). Human Kinetics.
- Tanaka, H., Monahan, K. D., & Seals, D. R. (2001). Age-predicted maximal heart rate revisited. Journal of the American College of Cardiology, 37(1), 153–156.
- Keytel, L. R., et al. (2005). Prediction of energy expenditure from heart rate monitoring during submaximal exercise. Journal of Sports Sciences, 23(3), 289–297.
- Walch, O., et al. (2019). Sleep stage prediction with raw acceleration and photoplethysmography heart rate data derived from a consumer wearable device. Sleep, 42(12), zsz180.
- Plews, D. J., Laursen, P. B., & Buchheit, M. (2017). Training adaptation and heart-rate variability: A systematic review. Sports Medicine, 47(8), 1521–1538.
- Gabbett, T. J. (2016). The training-injury prevention paradox: should athletes be training smarter and harder? British Journal of Sports Medicine, 50(5), 273–280.
- Foster, C., et al. (1995). A new approach to monitoring exercise training. Journal of Strength and Conditioning Research, 12(3), 109–115.
- Features — what NOOP can show you on screen
- How NOOP Works — the architecture and data pipeline
- Privacy and Security — how your data stays on your device
- Strap Support and Pairing — which WHOOP models work and how to pair them
- Troubleshooting — common questions and fixes
NOOP is an independent, unofficial, non-commercial interoperability project — not affiliated with, endorsed by, or sponsored by WHOOP, Inc. "WHOOP" is a trademark of WHOOP, Inc., used nominatively. Works only with a device you own; not a medical device; every metric is an approximation, not medical advice. · Privacy and Security · Donations · Releases
Get started
Tutorials
- Tracking a Workout
- Recovery, Strain & Readiness
- Automations
- Breathe & Intervals
- Importing History
- AI Coach
- Widget & Notifications
- Reading Your Sleep
- Explore & Compare
Reference
Project