Skip to main content

wifi_densepose_vitals/
lib.rs

1//! ESP32 CSI-grade vital sign extraction (ADR-021).
2//!
3//! Extracts heart rate and respiratory rate from WiFi Channel
4//! State Information using multi-subcarrier amplitude and phase
5//! analysis.
6//!
7//! # Architecture
8//!
9//! The pipeline processes CSI frames through four stages:
10//!
11//! 1. **Preprocessing** ([`CsiVitalPreprocessor`]): EMA-based static
12//!    component suppression, producing per-subcarrier residuals.
13//! 2. **Breathing extraction** ([`BreathingExtractor`]): Bandpass
14//!    filtering (0.1-0.5 Hz) with zero-crossing analysis for
15//!    respiratory rate.
16//! 3. **Heart rate extraction** ([`HeartRateExtractor`]): Bandpass
17//!    filtering (0.8-2.0 Hz) with autocorrelation peak detection
18//!    and inter-subcarrier phase coherence weighting.
19//! 4. **Anomaly detection** ([`VitalAnomalyDetector`]): Z-score
20//!    analysis with Welford running statistics for clinical alerts
21//!    (apnea, tachycardia, bradycardia).
22//!
23//! Results are stored in a [`VitalSignStore`] with configurable
24//! retention for historical analysis.
25//!
26//! # Example
27//!
28//! ```
29//! use wifi_densepose_vitals::{
30//!     CsiVitalPreprocessor, BreathingExtractor, HeartRateExtractor,
31//!     VitalAnomalyDetector, VitalSignStore, CsiFrame,
32//!     VitalReading, VitalEstimate, VitalStatus,
33//! };
34//!
35//! let mut preprocessor = CsiVitalPreprocessor::new(56, 0.05);
36//! let mut breathing = BreathingExtractor::new(56, 100.0, 30.0);
37//! let mut heartrate = HeartRateExtractor::new(56, 100.0, 15.0);
38//! let mut anomaly = VitalAnomalyDetector::default_config();
39//! let mut store = VitalSignStore::new(3600);
40//!
41//! // Process a CSI frame
42//! let frame = CsiFrame {
43//!     amplitudes: vec![1.0; 56],
44//!     phases: vec![0.0; 56],
45//!     n_subcarriers: 56,
46//!     sample_index: 0,
47//!     sample_rate_hz: 100.0,
48//! };
49//!
50//! if let Some(residuals) = preprocessor.process(&frame) {
51//!     let weights = vec![1.0 / 56.0; 56];
52//!     let rr = breathing.extract(&residuals, &weights);
53//!     let hr = heartrate.extract(&residuals, &frame.phases);
54//!
55//!     let reading = VitalReading {
56//!         respiratory_rate: rr.unwrap_or_else(VitalEstimate::unavailable),
57//!         heart_rate: hr.unwrap_or_else(VitalEstimate::unavailable),
58//!         subcarrier_count: frame.n_subcarriers,
59//!         signal_quality: 0.9,
60//!         timestamp_secs: 0.0,
61//!     };
62//!
63//!     let alerts = anomaly.check(&reading);
64//!     store.push(reading);
65//! }
66//! ```
67
68pub mod anomaly;
69pub mod breathing;
70pub mod heartrate;
71pub mod preprocessor;
72pub mod store;
73pub mod types;
74
75pub use anomaly::{AnomalyAlert, VitalAnomalyDetector};
76pub use breathing::BreathingExtractor;
77pub use heartrate::HeartRateExtractor;
78pub use preprocessor::CsiVitalPreprocessor;
79pub use store::{VitalSignStore, VitalStats};
80pub use types::{CsiFrame, VitalEstimate, VitalReading, VitalStatus};