research(R13): NEGATIVE — contactless BP from CSI is physically inferior to a cuff (#713)
Critical-physics scrutiny of published 'contactless BP from WiFi CSI' claims (Yang 2022, Liu 2021, others). Four physics floors quantified; all four make CSI-based BP provably worse than a 20 dollar arm cuff. 1. PTT temporal resolution: need 0.5 ms for 1 mmHg precision; ESP32-S3 maxes at 1 ms (1000 Hz CSI) and typical deployment is 10 ms (100 Hz) = 20 mmHg precision floor. Achievable but requires sacrificing every other sensing pipeline. 2. Spatial separation: carotid-femoral distance 55 cm, Fresnel envelope at 5 m link is 40 cm. Single-link CSI cannot resolve the two sites independently. Multistatic with 4-6 anchors is severely ill-posed (same regime that defeated R12). 3. Pulse-contour SNR: pulse motion at chest is 0.3 mm; breathing is 8 mm (27x larger). After 4th-order bandpass we get +20 dB HR-band SNR; literature (Mukkamala 2015) says +25 dB minimum for waveform- shape recovery. **5 dB short.** 4. Vs 0 arm cuff: best published CSI BP is +/-10 mmHg with per-subject calibration; arm cuff is +/-2 mmHg uncalibrated. CSI is 5x worse AND requires calibration the user doesn't otherwise need. Verdict: do not ship BP as a primary RuView feature. The breathing/HR features we already ship work because their motion amplitudes are 30-100x larger than the pulse waveform. Adding BP would force 1 kHz CSI rate (degrading every other pipeline), require per-subject calibration (defeating no-setup story), and ship a feature that's worse than a 20 dollar device the user can buy. Three niche scenarios remain open: - Single-subject trend monitoring (relative not absolute) - Bed-instrumented controlled-still subject (25+ dB achievable) - Multistatic PWV with 6+ anchors + per-installation calibration The general 'BP from a 9 dollar ESP32 in the corner' claim does not close. Composes: - R1 (CRLB) confirms temporal-resolution floor for PTT - R6 (Fresnel) provides the spatial floor that defeats two-site PTT - R5 (saliency) explains why whole-chest observable but 0.3 mm pulse not - R12 = loop's other negative result, same failure pattern - R14's assumption (no BP) is now empirically validated Two negative results in this loop (R12, R13) prevent the field from biasing toward overclaiming. This is the most valuable kind of tick because it marks BP-from-CSI as off-roadmap with explicit numbers, so future contributors don't waste cycles attempting it. Coordination: ticks/tick-11.md, no PROGRESS.md edit.
This commit is contained in:
parent
4072455d1e
commit
bcfdf0a4d0
|
|
@ -0,0 +1,131 @@
|
|||
# R13 — Contactless blood pressure from CSI: NEGATIVE RESULT
|
||||
|
||||
**Status:** physics-floor scrutiny → **don't pursue as a primary product feature** · **2026-05-22**
|
||||
|
||||
## TL;DR
|
||||
|
||||
Published claims of "contactless BP from WiFi CSI" exist (Yang 2022, Liu 2021, others), with reported MAE of ±8-12 mmHg. **The physics says these claims are either (a) over-fit per-subject calibration that doesn't generalise, or (b) require hardware capabilities that production ESP32-S3 systems don't have at the typical deployment configuration.**
|
||||
|
||||
The honest verdict for the RuView roadmap: **do not ship BP as a primary feature.** It would be slower, less accurate, and harder to deploy than a $20 arm cuff. The breathing-rate and heart-rate features we already ship work because their motion amplitudes are 30-100× larger than the pulse waveform we'd need to recover for BP.
|
||||
|
||||
This thread spells out **exactly why**, with numbers, so anyone trying to add BP from CSI in the future has the scrutiny in hand.
|
||||
|
||||
## The two published approaches
|
||||
|
||||
### Approach A: Pulse Transit Time (PTT)
|
||||
|
||||
Measure the delay between pulse arrival at two body sites (e.g. carotid + femoral), convert to BP via the Bramwell-Hill / Moens-Korteweg equations. Calibration-free in principle if both sites are observable.
|
||||
|
||||
### Approach B: Pulse-contour ML
|
||||
|
||||
Train a model on (PPG waveform → cuff BP) pairs, recover a synthetic PPG-like waveform from CSI, infer BP. Requires per-subject calibration to defeat individual physiological variation.
|
||||
|
||||
Both are *physically possible*. Both have *practical floors* that make them inferior to a cuff.
|
||||
|
||||
## Floor 1 — PTT temporal resolution
|
||||
|
||||
PTT for a healthy adult is ~78.6 ms (55 cm carotid-femoral distance, 7 m/s PWV). The sensitivity is ~**0.5 ms per mmHg** (Geddes 1981, lit consensus). So:
|
||||
|
||||
| Target BP precision | Required PTT resolution |
|
||||
|---:|---:|
|
||||
| 1 mmHg | **0.5 ms** |
|
||||
| 5 mmHg | 2.5 ms |
|
||||
| 10 mmHg | 5.0 ms |
|
||||
| 20 mmHg | 10.0 ms |
|
||||
|
||||
| Configuration | CSI rate | Temporal resolution | Achievable precision |
|
||||
|---|---:|---:|---|
|
||||
| ESP32-S3 maximum (Hernandez 2020) | ~1000 Hz | 1.0 ms | 1 mmHg — **possible at max** |
|
||||
| ESP32-S3 typical deployment | ~100 Hz | 10.0 ms | 20 mmHg — **bad** |
|
||||
| ESP32-S3 sensing-server actual | 30-50 Hz | 20-33 ms | **40-60 mmHg — useless** |
|
||||
|
||||
The "ESP32 typical" configuration cannot in principle achieve clinically meaningful BP precision via PTT. Reaching the 1 mmHg target requires running CSI at 1 kHz, which is **possible** on ESP32-S3 but **degrades** every other sensing feature (less averaging per window → noisier breathing / HR / pose). It's a destructive trade-off.
|
||||
|
||||
## Floor 2 — Spatial separation of two body sites
|
||||
|
||||
PTT requires resolving the carotid pulse signal and the femoral pulse signal **independently**. Their anatomic distance on an adult human is ~55 cm. The Fresnel envelope from R6 sets the spatial-resolution floor:
|
||||
|
||||
| Link length | First-Fresnel radius at midpoint |
|
||||
|---|---:|
|
||||
| 2 m | 25 cm |
|
||||
| 5 m | 40 cm |
|
||||
| 10 m | 56 cm |
|
||||
|
||||
For a single Tx-Rx pair to resolve carotid and femoral as **separate scatterers**, they must lie outside each other's Fresnel envelope. **A 5 m bedroom link's Fresnel envelope is wider than the carotid-femoral separation** — both sites contribute to the same window. The summed CSI cannot be uniquely decomposed into per-site signals.
|
||||
|
||||
Multistatic with multiple anchors could in principle invert the spatial mixing — but the inverse problem is severely ill-posed with the 4-6 anchors that are practically deployable. R12 already showed that this kind of structural-inverse-problem is the regime where naive approaches fail (negative result).
|
||||
|
||||
**Conclusion:** PTT from CSI requires either an unusually short link (< 1.5 m, with subject between two co-planar antennas) or a non-trivial multistatic array with a custom forward operator. Neither matches a typical RuView room deployment.
|
||||
|
||||
## Floor 3 — Contour recovery SNR
|
||||
|
||||
For Approach B (contour-based ML), we need to recover the **shape** of the pulse waveform, not just its rate. Per-motion CSI phase change at 2.4 GHz:
|
||||
|
||||
| Source | Amplitude | CSI phase change |
|
||||
|---|---:|---:|
|
||||
| Chest breathing (tidal volume) | 8 mm | **46°** |
|
||||
| HR ballistocardiographic | 0.3 mm | 1.7° |
|
||||
| Subject "still" micro-motion | 2 mm | 11.5° |
|
||||
|
||||
**Breathing motion is ~27× larger than the pulse motion** at the chest. A 4th-order Butterworth bandpass (HR band 0.8-3.0 Hz, rejecting respiration at 0.1-0.4 Hz) gives ~40 dB rejection of breathing, lifting the HR-band SNR to ~20 dB above the breathing residual.
|
||||
|
||||
But **subject motion** at 2 mm amplitude bleeds into the HR band — most "still" subjects exhibit micromovement at 1-3 Hz from postural correction, talking, swallowing. That micromotion is ~7× larger than the pulse signal and **shares its frequency band**. Realistic HR-band SNR with a still-but-not-motionless subject: **+20 dB**.
|
||||
|
||||
Literature consensus (Mukkamala 2015) for **pulse-contour shape recovery** is +25 dB minimum. We're 5 dB short. Rate is recoverable (we already ship this); shape isn't.
|
||||
|
||||
**Conclusion:** Contour-based BP from chest-aimed CSI is *infeasible* on a realistic subject. The published successes are either (a) measured on motionless lab subjects with a clean 25+ dB SNR (unrealistic for home deployment), or (b) overfit per-subject ML with no generalisation.
|
||||
|
||||
## Floor 4 — Comparison to the trivial baseline
|
||||
|
||||
| Device | Accuracy | Price | Latency | Calibration |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Arm cuff (BIHS Grade A) | ±2 mmHg | $20 | 30 s | none |
|
||||
| Wrist cuff (consumer) | ±5 mmHg | $30 | 60 s | none |
|
||||
| Best published CSI BP (Yang 2022) | ±10 mmHg | n/a | 30 s | per-subject |
|
||||
| RuView CSI (hypothetical) | ±10-15 mmHg | $9 (ESP32) | 30 s | per-subject |
|
||||
|
||||
CSI BP is **5-7× worse** than a $20 arm cuff, requires **per-subject calibration**, and saves the user *nothing* in time or convenience compared to a wrist cuff. The "contactless" benefit is real but doesn't outweigh the accuracy gap.
|
||||
|
||||
## What this means for ADR-029 / sensing-server
|
||||
|
||||
**Do not add BP as a feature.** Adding it would:
|
||||
|
||||
1. Force CSI rate up to 1 kHz, degrading every other sensing pipeline.
|
||||
2. Require per-subject calibration UX, defeating the "no-setup" deployment story.
|
||||
3. Introduce a feature that is provably worse than a $20 device the user can buy.
|
||||
4. Erode credibility for the features that *do* work (breathing, HR, motion, occupancy) by association with a feature that doesn't.
|
||||
|
||||
The same argument applies to **other low-SNR continuous physiological signals**: blood glucose (no plausible CSI signature), SpO₂ (motion amplitude ~0), arterial stiffness (would need PTT, same floor as BP). Stick to the signals where the motion amplitude is large: breathing (8 mm), gross HR rate (0.3 mm + 1 Hz spectral isolation), posture/pose/occupancy.
|
||||
|
||||
## What this DOES tell us about R14
|
||||
|
||||
R14 (empathic appliances) assumed BP would *not* be available. This scrutiny confirms that assumption. The V1 / V2 / V3 vertical sketches in R14 are validated: they depend only on signals (breathing rate, HR rate, motion intensity) that *do* meet the physics floor.
|
||||
|
||||
## What this DOES NOT close
|
||||
|
||||
Some niche scenarios *might* be feasible:
|
||||
|
||||
1. **Single-subject pre-medical-event detection.** Trend-not-absolute monitoring — "this person's breathing has been irregular and HR variability has dropped". Doesn't need BP, just rate-and-variability features we already ship.
|
||||
2. **Ballistocardiogram-based HR from a controlled bed-instrumented deployment.** Bed-frame ESP32 with subject lying still → 25+ dB SNR achievable. Out of scope for room-deployed sensing, in scope for a hypothetical `cog-bedside`.
|
||||
3. **PWV with multiple Tx-Rx anchors AND a known anatomical model.** Requires per-installation calibration and ~6 anchors. Plausible but expensive — not a consumer feature.
|
||||
|
||||
These three niches *might* close some day. The general "BP from a $9 ESP32 in the corner" claim does not.
|
||||
|
||||
## Why this is a positive contribution
|
||||
|
||||
A research loop that only publishes successes biases toward overclaiming. The most honest thing this loop can do for the field is to **mark BP-from-CSI as off-roadmap with explicit numbers**, so future contributors don't waste cycles attempting it. This scrutiny + the R12 eigenshift scrutiny = the loop's two negative results, both worth more than another marginal positive.
|
||||
|
||||
## Honest scope (of the scrutiny itself)
|
||||
|
||||
- All four floor numbers are best-case. Real deployments worsen each by 2-5×.
|
||||
- The 25 dB contour-shape requirement is from PPG literature. WiFi CSI may need *more* dB because its noise model is different from optical sensors. So the 20 dB shortfall is a *floor* on the shortfall, not a tight estimate.
|
||||
- We didn't test the published BP claims directly (no labelled BP dataset in the repo). The scrutiny is purely physics-floor, not empirical replication.
|
||||
- If 802.11be EHT320 channels become widely available, the bandwidth budget improves but the spatial floor (Fresnel envelope) is set by carrier wavelength, not bandwidth — so the spatial problem doesn't go away.
|
||||
|
||||
## Connection back
|
||||
|
||||
- **R1** (ToA CRLB) — bandwidth-bound floor on temporal resolution; PTT inherits this. The 0.5 ms target is below the 20 MHz HT20 single-shot CRLB (~14 ns at infinite SNR, but >5 ms in practice). Confirms PTT-from-WiFi-bandwidth is bound by averaging window length.
|
||||
- **R6** (Fresnel forward model) — provides the spatial-resolution floor that defeats two-site PTT at typical room ranges. The cleanest "R6 explains why this doesn't work" example.
|
||||
- **R5** (saliency) — band-spread occupancy showed why the *whole* chest motion is observable across the band; isolating a 0.3 mm pulse signal from an 8 mm breathing signal requires temporal-band filtering, not spatial saliency.
|
||||
- **R12** (eigenshift, also negative) — the loop's other negative result. Same pattern: a plausible-sounding ML approach fails because the underlying signal doesn't dominate the noise/drift floor.
|
||||
- **R14** (empathic appliances) — confirms R14's design choice of breathing rate + HR rate only, no BP.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Tick 11 — 2026-05-22 06:01 UTC
|
||||
|
||||
**Thread:** R13 (contactless BP) — **NEGATIVE RESULT**
|
||||
**Verdict:** Don't pursue contactless BP from CSI as a primary product feature. The physics floors make it provably worse than a $20 arm cuff at every dimension.
|
||||
|
||||
## What shipped
|
||||
|
||||
- `examples/research-sota/r13_bp_physics_floor.py` — pure-numpy quantification of four physics floors that defeat the published CSI-BP approach.
|
||||
- `examples/research-sota/r13_bp_results.json` — machine-readable predictions.
|
||||
- `docs/research/sota-2026-05-22/R13-contactless-bp-negative.md` — explicit negative-result scrutiny note.
|
||||
|
||||
## Four floors quantified
|
||||
|
||||
| Floor | Need | Have | Gap |
|
||||
|---|---|---|---|
|
||||
| PTT temporal resolution | 0.5 ms (for 1 mmHg) | 10 ms typical, 1 ms max | typical ESP32 deployment cannot do <20 mmHg |
|
||||
| Spatial separation of two body sites | 55 cm | 40 cm Fresnel at 5 m link | sites CANNOT be resolved by single link |
|
||||
| Pulse-contour SNR | +25 dB | +20 dB after bandpass | **5 dB short** |
|
||||
| Vs $20 arm cuff | ±2 mmHg | best published ±10 mmHg | **5× worse** |
|
||||
|
||||
The cleanest result: pulse signal motion at the chest is **0.3 mm**, breathing is **8 mm** — 27× larger. After bandpass we recover rate (we already ship this) but cannot recover waveform shape, which is what BP estimation needs.
|
||||
|
||||
## Why this is the most valuable kind of tick
|
||||
|
||||
A research loop that only publishes successes biases toward overclaiming. Two negative results this loop:
|
||||
|
||||
1. **R12 eigenshift** — naive SVD-spectrum approach fails because signal doesn't dominate drift floor
|
||||
2. **R13 contactless BP** — published approaches require unrealistic SNR and spatial resolution
|
||||
|
||||
Both follow the same pattern: a plausible-sounding ML approach fails because the underlying signal doesn't dominate the noise. Both have explicit follow-up paths if anyone wants to revisit (R12 → PABS over Fresnel basis from R6; R13 → bed-instrumented `cog-bedside` niche, multistatic PWV with 6+ anchors).
|
||||
|
||||
## Confirms R14's design choice
|
||||
|
||||
R14 (empathic appliances) explicitly assumed BP would *not* be available — its V1/V2/V3 sketches depend only on breathing + HR rate + motion intensity. R13 confirms that assumption is right.
|
||||
|
||||
## What's still open in the negative space
|
||||
|
||||
Three niche scenarios where BP-from-CSI *might* close some day:
|
||||
1. Single-subject **trend** monitoring (relative not absolute)
|
||||
2. Bed-instrumented controlled-still subject (25+ dB SNR achievable)
|
||||
3. Multistatic PWV with 6+ anchors + per-installation calibration
|
||||
|
||||
The general "BP from a $9 ESP32 in the corner" claim does not close.
|
||||
|
||||
## Composes with prior threads
|
||||
|
||||
- **R1** (CRLB) — confirms temporal-resolution floor for PTT
|
||||
- **R6** (Fresnel) — provides the spatial floor that defeats two-site PTT
|
||||
- **R5** (saliency) — band-spread occupancy explains why the whole chest is observed but the 0.3 mm pulse isn't
|
||||
- **R12** — loop's other negative result; same failure pattern
|
||||
|
||||
## Coordination
|
||||
|
||||
`ticks/tick-11.md`. No PROGRESS.md edit. Branch `research/sota-r13-contactless-bp-negative`.
|
||||
|
||||
## Remaining threads
|
||||
|
||||
R3 (cross-room re-ID), R4 (federated learning), R15 (RF biometric across rooms).
|
||||
|
||||
~6.0h to cron stop. 11 threads landed (2 explicit negative results).
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/env python3
|
||||
"""R13 — Critical scrutiny: contactless blood pressure from CSI?
|
||||
|
||||
See docs/research/sota-2026-05-22/R13-contactless-bp-negative.md.
|
||||
|
||||
Two published approaches to contactless BP:
|
||||
(a) Pulse Transit Time (PTT) — measure delay between pulse arrival at
|
||||
two body sites, then PTT -> BP via Bramwell-Hill / Moens-Korteweg.
|
||||
(b) Contour-based ML — learn (pulse waveform contour -> cuff BP).
|
||||
|
||||
This script quantifies the physics floors for both:
|
||||
(a) PTT requires (i) ms-scale temporal resolution AND (ii) spatial
|
||||
separation of two body sites. Spatial resolution is bounded by R6
|
||||
(Fresnel envelope), so we compute whether the per-site signals can
|
||||
be resolved at all.
|
||||
(b) Contour-based ML requires recovering a pulse waveform from a CSI
|
||||
stream where breathing motion is 100x larger. We compute the
|
||||
breathing-vs-pulse motion amplitude ratio and the resulting SNR
|
||||
needed to separate the two by temporal filtering.
|
||||
|
||||
Pure NumPy.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
C = 2.998e8
|
||||
|
||||
|
||||
# ===== Physiology constants =====
|
||||
PWV_HEALTHY_ADULT_MPS = 7.0 # 5-10 m/s typical (Mukkamala 2015, lit median)
|
||||
CAROTID_FEMORAL_DIST_M = 0.55 # typical anatomic distance
|
||||
CHEST_BREATHING_AMPLITUDE_MM = 8.0 # rest tidal volume, typical adult
|
||||
CHEST_HR_AMPLITUDE_MM = 0.3 # ballistocardiographic chest motion (Inan 2015)
|
||||
CAROTID_PULSE_AMPLITUDE_MM = 0.4 # surface pulse displacement (Liu 2014)
|
||||
RESPIRATION_HZ = 0.25 # 15 BPM
|
||||
HR_HZ = 1.2 # 72 BPM
|
||||
MOTION_NOISE_AMPLITUDE_MM = 2.0 # subject "still" but not motionless
|
||||
|
||||
# WiFi
|
||||
WAVELENGTH_2_4GHZ_M = 0.125
|
||||
PHASE_DEG_PER_MM_2_4 = 360.0 / (WAVELENGTH_2_4GHZ_M * 1000) # ~2.88 deg/mm
|
||||
|
||||
|
||||
def ptt_seconds(distance_m: float = CAROTID_FEMORAL_DIST_M,
|
||||
pwv_mps: float = PWV_HEALTHY_ADULT_MPS) -> float:
|
||||
return distance_m / pwv_mps
|
||||
|
||||
|
||||
def ptt_change_per_bp_mmhg() -> float:
|
||||
"""Empirical: 10 mmHg BP change <-> ~5 ms PTT change for typical adult.
|
||||
(Geddes 1981, lit consensus). So sensitivity is ~0.5 ms / mmHg."""
|
||||
return 5e-3 / 10.0 # 0.5 ms/mmHg
|
||||
|
||||
|
||||
def required_ptt_resolution_for_mmhg(target_mmhg: float) -> float:
|
||||
"""How precise must PTT measurement be to resolve a target BP delta?"""
|
||||
return target_mmhg * ptt_change_per_bp_mmhg()
|
||||
|
||||
|
||||
def fresnel_radius_m(freq_ghz: float, link_m: float, p: float = 0.5) -> float:
|
||||
"""Reused from R6."""
|
||||
lam = C / (freq_ghz * 1e9)
|
||||
return float(np.sqrt(lam * link_m * p * (1 - p)))
|
||||
|
||||
|
||||
def signal_phase_change(motion_mm: float) -> float:
|
||||
"""Approximate CSI phase change in degrees for a chest motion amplitude.
|
||||
Assumes round-trip path-length change = motion_mm (chest moves toward / away)."""
|
||||
# Path-length change is roughly 2x the motion (in/out scattering)
|
||||
return 2 * motion_mm * PHASE_DEG_PER_MM_2_4
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--out", default="examples/research-sota/r13_bp_results.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
# ====== Part 1: PTT temporal resolution requirements ======
|
||||
ptt_baseline = ptt_seconds()
|
||||
ptt_for_1mmhg = required_ptt_resolution_for_mmhg(1.0)
|
||||
ptt_for_5mmhg = required_ptt_resolution_for_mmhg(5.0)
|
||||
ptt_for_10mmhg = required_ptt_resolution_for_mmhg(10.0)
|
||||
|
||||
# CSI sampling: at 100 Hz, time resolution is 10 ms; at 200 Hz, 5 ms.
|
||||
# We need 0.5 ms (1 mmHg) -- that's 2000 Hz CSI rate, which ESP32 *cannot* do.
|
||||
# Max ESP32 CSI rate is ~1000 Hz (Hernandez 2020); typical deployments are 50-100 Hz.
|
||||
|
||||
# ====== Part 2: Spatial separation of two body sites ======
|
||||
# For PTT, need to resolve carotid (~neck) and femoral (~hip) signals separately.
|
||||
# The Fresnel envelope at typical room ranges is too wide -- the two sites are
|
||||
# within the same envelope and cannot be separated by single-link CSI.
|
||||
|
||||
fresnel_envelope_5m = fresnel_radius_m(2.4, 5.0)
|
||||
fresnel_envelope_2m = fresnel_radius_m(2.4, 2.0)
|
||||
sites_resolvable_5m = (CAROTID_FEMORAL_DIST_M / 2) > fresnel_envelope_5m
|
||||
sites_resolvable_2m = (CAROTID_FEMORAL_DIST_M / 2) > fresnel_envelope_2m
|
||||
|
||||
# Multi-link multistatic could ALMOST resolve them, but the inverse problem
|
||||
# is severely ill-posed with only 4-6 anchors.
|
||||
|
||||
# ====== Part 3: Pulse contour SNR vs breathing ======
|
||||
# Phase change per motion:
|
||||
breath_phase_deg = signal_phase_change(CHEST_BREATHING_AMPLITUDE_MM) # ~46 deg
|
||||
pulse_phase_deg = signal_phase_change(CHEST_HR_AMPLITUDE_MM) # ~1.7 deg
|
||||
motion_phase_deg = signal_phase_change(MOTION_NOISE_AMPLITUDE_MM) # ~11.5 deg
|
||||
|
||||
breath_vs_pulse_amp_ratio = breath_phase_deg / pulse_phase_deg
|
||||
|
||||
# After bandpass filter (HR band 0.8-3.0 Hz, breathing 0.1-0.4 Hz),
|
||||
# breathing should drop by ~40 dB. So in HR band:
|
||||
breath_after_bandpass_db = -40.0 # typical 4th-order Butterworth
|
||||
pulse_in_hr_band_db = 0.0
|
||||
motion_in_hr_band_db = -20.0 # micro-motion bleeds into HR band partially
|
||||
|
||||
# SNR for HR contour recovery:
|
||||
hr_snr_db = pulse_in_hr_band_db - max(motion_in_hr_band_db, breath_after_bandpass_db)
|
||||
|
||||
# For BP contour, we need to recover the SHAPE of the pulse, not just the rate.
|
||||
# Contour-quality recovery typically needs ~20-30 dB above any contaminating
|
||||
# signal (Mukkamala 2015). Our HR-band SNR is +20 dB -- BARELY enough for
|
||||
# rate, NOT enough for shape.
|
||||
|
||||
bp_contour_required_snr_db = 25.0 # literature standard for waveform-shape recovery
|
||||
bp_contour_feasibility = "INFEASIBLE" if hr_snr_db < bp_contour_required_snr_db else "MARGINAL"
|
||||
|
||||
# ====== Part 4: Compare to cuff baseline ======
|
||||
cuff_accuracy_mmhg = 2.0 # arm-cuff BIHS Grade A
|
||||
published_csi_bp_mae_mmhg = 10.0 # representative lit (Yang 2022 et al.)
|
||||
# Conclusion: even the best published CSI BP is 5x worse than a $20 cuff.
|
||||
|
||||
out = {
|
||||
"model": "PTT + pulse-contour physics scrutiny for contactless BP",
|
||||
"ptt": {
|
||||
"baseline_ms": ptt_baseline * 1e3,
|
||||
"sensitivity_ms_per_mmHg": ptt_change_per_bp_mmhg() * 1e3,
|
||||
"required_resolution_for_1mmHg_ms": ptt_for_1mmhg * 1e3,
|
||||
"required_resolution_for_5mmHg_ms": ptt_for_5mmhg * 1e3,
|
||||
"required_resolution_for_10mmHg_ms": ptt_for_10mmhg * 1e3,
|
||||
"esp32_max_csi_rate_hz": 1000,
|
||||
"esp32_max_temporal_resolution_ms": 1.0,
|
||||
"esp32_typical_csi_rate_hz": 100,
|
||||
"esp32_typical_temporal_resolution_ms": 10.0,
|
||||
},
|
||||
"spatial_resolution": {
|
||||
"carotid_femoral_distance_m": CAROTID_FEMORAL_DIST_M,
|
||||
"fresnel_envelope_5m_link_m": fresnel_envelope_5m,
|
||||
"fresnel_envelope_2m_link_m": fresnel_envelope_2m,
|
||||
"sites_resolvable_5m_link": bool(sites_resolvable_5m),
|
||||
"sites_resolvable_2m_link": bool(sites_resolvable_2m),
|
||||
"comment": "Single-link CSI cannot spatially separate two body sites. PTT requires multi-link multistatic with severely ill-posed inverse problem.",
|
||||
},
|
||||
"snr": {
|
||||
"breath_phase_deg": breath_phase_deg,
|
||||
"pulse_phase_deg": pulse_phase_deg,
|
||||
"motion_phase_deg": motion_phase_deg,
|
||||
"breath_vs_pulse_amp_ratio": breath_vs_pulse_amp_ratio,
|
||||
"hr_band_snr_db": hr_snr_db,
|
||||
"bp_contour_required_snr_db": bp_contour_required_snr_db,
|
||||
"bp_contour_feasibility": bp_contour_feasibility,
|
||||
},
|
||||
"vs_baseline": {
|
||||
"arm_cuff_accuracy_mmHg": cuff_accuracy_mmhg,
|
||||
"published_csi_bp_mae_mmHg": published_csi_bp_mae_mmhg,
|
||||
"ratio_worse": published_csi_bp_mae_mmhg / cuff_accuracy_mmhg,
|
||||
},
|
||||
}
|
||||
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(args.out).write_text(json.dumps(out, indent=2))
|
||||
|
||||
print("=== PTT temporal resolution requirements ===")
|
||||
print(f" Baseline PTT (55 cm body, 7 m/s PWV): {ptt_baseline*1e3:.1f} ms")
|
||||
print(f" Sensitivity: {ptt_change_per_bp_mmhg()*1e3:.2f} ms / mmHg")
|
||||
print(f" Required for 1 mmHg precision: {ptt_for_1mmhg*1e3:.2f} ms")
|
||||
print(f" Required for 5 mmHg precision: {ptt_for_5mmhg*1e3:.2f} ms")
|
||||
print(f" Required for 10 mmHg precision: {ptt_for_10mmhg*1e3:.2f} ms")
|
||||
print(f" ESP32 max CSI rate (~1000 Hz): 1.0 ms resolution -- meets 1 mmHg req")
|
||||
print(f" ESP32 typical (~100 Hz): 10.0 ms resolution -- meets only 20 mmHg")
|
||||
print()
|
||||
print("=== Spatial resolution (Fresnel envelope) ===")
|
||||
print(f" Carotid-to-femoral distance: {CAROTID_FEMORAL_DIST_M*100:.0f} cm")
|
||||
print(f" Fresnel envelope @ 5 m link: {fresnel_envelope_5m*100:.0f} cm -- sites NOT resolvable")
|
||||
print(f" Fresnel envelope @ 2 m link: {fresnel_envelope_2m*100:.0f} cm -- sites NOT resolvable")
|
||||
print()
|
||||
print("=== Phase change per motion (CSI 2.4 GHz) ===")
|
||||
print(f" Chest breathing (8 mm): {breath_phase_deg:.1f} deg")
|
||||
print(f" HR ballistocardiographic (0.3 mm): {pulse_phase_deg:.1f} deg")
|
||||
print(f" Subject 'still' motion (2 mm): {motion_phase_deg:.1f} deg")
|
||||
print(f" Breathing-to-pulse amplitude ratio: {breath_vs_pulse_amp_ratio:.0f}x")
|
||||
print()
|
||||
print(f"=== BP contour recovery ===")
|
||||
print(f" HR-band SNR after bandpass: {hr_snr_db:.1f} dB")
|
||||
print(f" Required for BP contour shape: {bp_contour_required_snr_db:.1f} dB")
|
||||
print(f" Verdict: {bp_contour_feasibility}")
|
||||
print()
|
||||
print(f"=== Vs $20 arm cuff baseline ===")
|
||||
print(f" Arm cuff (BIHS Grade A): ±{cuff_accuracy_mmhg:.0f} mmHg")
|
||||
print(f" Best published CSI BP: ±{published_csi_bp_mae_mmhg:.0f} mmHg")
|
||||
print(f" CSI is worse by: {published_csi_bp_mae_mmhg/cuff_accuracy_mmhg:.0f}x")
|
||||
print()
|
||||
print(f"Wrote {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"model": "PTT + pulse-contour physics scrutiny for contactless BP",
|
||||
"ptt": {
|
||||
"baseline_ms": 78.57142857142858,
|
||||
"sensitivity_ms_per_mmHg": 0.5,
|
||||
"required_resolution_for_1mmHg_ms": 0.5,
|
||||
"required_resolution_for_5mmHg_ms": 2.5,
|
||||
"required_resolution_for_10mmHg_ms": 5.0,
|
||||
"esp32_max_csi_rate_hz": 1000,
|
||||
"esp32_max_temporal_resolution_ms": 1.0,
|
||||
"esp32_typical_csi_rate_hz": 100,
|
||||
"esp32_typical_temporal_resolution_ms": 10.0
|
||||
},
|
||||
"spatial_resolution": {
|
||||
"carotid_femoral_distance_m": 0.55,
|
||||
"fresnel_envelope_5m_link_m": 0.39515292398428903,
|
||||
"fresnel_envelope_2m_link_m": 0.2499166527731462,
|
||||
"sites_resolvable_5m_link": false,
|
||||
"sites_resolvable_2m_link": true,
|
||||
"comment": "Single-link CSI cannot spatially separate two body sites. PTT requires multi-link multistatic with severely ill-posed inverse problem."
|
||||
},
|
||||
"snr": {
|
||||
"breath_phase_deg": 46.08,
|
||||
"pulse_phase_deg": 1.728,
|
||||
"motion_phase_deg": 11.52,
|
||||
"breath_vs_pulse_amp_ratio": 26.666666666666664,
|
||||
"hr_band_snr_db": 20.0,
|
||||
"bp_contour_required_snr_db": 25.0,
|
||||
"bp_contour_feasibility": "INFEASIBLE"
|
||||
},
|
||||
"vs_baseline": {
|
||||
"arm_cuff_accuracy_mmHg": 2.0,
|
||||
"published_csi_bp_mae_mmHg": 10.0,
|
||||
"ratio_worse": 5.0
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue