wifi-densepose/v2/crates/wifi-densepose-vitals
ruv a7f7adfabc perf(vitals,wifiscan): O(1) VecDeque sliding windows + vitals bench (ADR-157 §A1/§D1)
Replace Vec::remove(0) (O(n) per-sample buffer shift -> O(n^2) full-window
sweep) with VecDeque push_back/pop_front (O(1) eviction) in the fixed-length
sliding/ring buffers of the vital-sign and wifiscan extractors. Where the
autocorrelation / zero-crossing / Pearson loop needs a contiguous slice,
make_contiguous() is called once per extract(), matching the idiom already used
in wifiscan/pipeline/orchestrator.rs. Output is bit-identical.

Sites: anomaly.rs (rr/hr history), store.rs (readings ring; history() now takes
&mut self to hand back a contiguous slice, no external callers), wifiscan
breathing_extractor.rs (filtered history), wifiscan correlator.rs (per-BSSID
histories -> Vec<VecDeque<f32>>). (heartrate.rs/breathing.rs windows land with
the §A2/§A3 fixes in a separate commit.)

New criterion bench crates/wifi-densepose-vitals/benches/vitals_bench.rs drives
each extractor over a full-window fill. Honest MEASURED result: end-to-end win
is NULL within noise at realistic ESP32 window sizes (1500-3000) because the
per-frame DSP dominates the eviction (heartrate 42.8ms->44.4ms, breathing
7.95ms->7.86ms, overlapping CIs). In isolation the eviction collapses O(n^2)
-> O(n) (34.6x at window=3000, 3158x at window=100000); A1 lands as the correct
data structure removing a latent O(n^2), NOT a claimed hot-path speedup.

Reproduce: cargo bench -p wifi-densepose-vitals --bench vitals_bench

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-11 20:59:57 -04:00
..
benches perf(vitals,wifiscan): O(1) VecDeque sliding windows + vitals bench (ADR-157 §A1/§D1) 2026-06-11 20:59:57 -04:00
src perf(vitals,wifiscan): O(1) VecDeque sliding windows + vitals bench (ADR-157 §A1/§D1) 2026-06-11 20:59:57 -04:00
Cargo.toml perf(vitals,wifiscan): O(1) VecDeque sliding windows + vitals bench (ADR-157 §A1/§D1) 2026-06-11 20:59:57 -04:00
README.md chore(repo): rename rust-port/wifi-densepose-rs → v2/ (flatten to one level) (#427) 2026-04-25 21:28:13 -04:00

README.md

wifi-densepose-vitals

Crates.io Documentation License

ESP32 CSI-grade vital sign extraction: heart rate and respiratory rate from WiFi Channel State Information (ADR-021).

Overview

wifi-densepose-vitals implements a four-stage pipeline that extracts respiratory rate and heart rate from multi-subcarrier CSI amplitude and phase data. The crate has zero external dependencies beyond tracing (and optional serde), uses #[forbid(unsafe_code)], and is designed for resource-constrained edge deployments alongside ESP32 hardware.

Pipeline Stages

  1. Preprocessing (CsiVitalPreprocessor) -- EMA-based static component suppression, producing per-subcarrier residuals that isolate body-induced signal variation.
  2. Breathing extraction (BreathingExtractor) -- Bandpass filtering at 0.1--0.5 Hz with zero-crossing analysis for respiratory rate estimation.
  3. Heart rate extraction (HeartRateExtractor) -- Bandpass filtering at 0.8--2.0 Hz with autocorrelation peak detection and inter-subcarrier phase coherence weighting.
  4. Anomaly detection (VitalAnomalyDetector) -- Z-score analysis using Welford running statistics for real-time clinical alerts (apnea, tachycardia, bradycardia).

Results are stored in a VitalSignStore with configurable retention for historical trend analysis.

Feature flags

Flag Default Description
serde yes Serialization for vital sign types

Quick Start

use wifi_densepose_vitals::{
    CsiVitalPreprocessor, BreathingExtractor, HeartRateExtractor,
    VitalAnomalyDetector, VitalSignStore, CsiFrame,
    VitalReading, VitalEstimate, VitalStatus,
};

let mut preprocessor = CsiVitalPreprocessor::new(56, 0.05);
let mut breathing = BreathingExtractor::new(56, 100.0, 30.0);
let mut heartrate = HeartRateExtractor::new(56, 100.0, 15.0);
let mut anomaly = VitalAnomalyDetector::default_config();
let mut store = VitalSignStore::new(3600);

// Process a CSI frame
let frame = CsiFrame {
    amplitudes: vec![1.0; 56],
    phases: vec![0.0; 56],
    n_subcarriers: 56,
    sample_index: 0,
    sample_rate_hz: 100.0,
};

if let Some(residuals) = preprocessor.process(&frame) {
    let weights = vec![1.0 / 56.0; 56];
    let rr = breathing.extract(&residuals, &weights);
    let hr = heartrate.extract(&residuals, &frame.phases);

    let reading = VitalReading {
        respiratory_rate: rr.unwrap_or_else(VitalEstimate::unavailable),
        heart_rate: hr.unwrap_or_else(VitalEstimate::unavailable),
        subcarrier_count: frame.n_subcarriers,
        signal_quality: 0.9,
        timestamp_secs: 0.0,
    };

    let alerts = anomaly.check(&reading);
    store.push(reading);
}

Architecture

wifi-densepose-vitals/src/
  lib.rs            -- Re-exports, module declarations
  types.rs          -- CsiFrame, VitalReading, VitalEstimate, VitalStatus
  preprocessor.rs   -- CsiVitalPreprocessor (EMA static suppression)
  breathing.rs      -- BreathingExtractor (0.1-0.5 Hz bandpass)
  heartrate.rs      -- HeartRateExtractor (0.8-2.0 Hz autocorrelation)
  anomaly.rs        -- VitalAnomalyDetector (Z-score, Welford stats)
  store.rs          -- VitalSignStore, VitalStats (historical retention)
Crate Role
wifi-densepose-hardware Provides raw CSI frames from ESP32
wifi-densepose-mat Uses vital signs for survivor triage
wifi-densepose-signal Advanced signal processing algorithms

License

MIT OR Apache-2.0