From 5fa61ba7ea5a6968db1e9bb386844b9f0765c6f0 Mon Sep 17 00:00:00 2001 From: rUv Date: Thu, 5 Mar 2026 10:15:18 -0500 Subject: [PATCH] feat: adaptive CSI classifier with signal smoothing pipeline (ADR-048) (#144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add environment-tuned activity classification that learns from labeled ESP32 CSI recordings, replacing brittle static thresholds. - Adaptive classifier: 15-feature logistic regression trained from JSONL recordings (variance, motion band, subcarrier stats: skew, kurtosis, entropy, IQR). Trains in <1s, persists as JSON, auto-loads on restart. - Three-stage signal smoothing: adaptive baseline subtraction (α=0.003), EMA + trimmed-mean median filter (21-frame window), hysteresis debounce (4 frames). Motion classification now stable across seconds, not frames. - Vital signs stabilization: outlier rejection (±8 BPM HR, ±2 BPM BR), trimmed mean, dead-band (±2 BPM HR), EMA α=0.02. HR holds steady for 10+ seconds instead of jumping 50 BPM every frame. - Observatory auto-detect: always probes /health on startup, connects WebSocket to live ESP32 data automatically. - New API endpoints: POST /api/v1/adaptive/train, GET /adaptive/status, POST /adaptive/unload for runtime model management. - Updated user guide with Observatory, adaptive classifier tutorial, signal smoothing docs, and new troubleshooting entries. --- docs/adr/ADR-048-adaptive-csi-classifier.md | 140 ++++ docs/user-guide.md | 239 +++++- .../src/adaptive_classifier.rs | 461 +++++++++++ .../wifi-densepose-sensing-server/src/main.rs | 362 ++++++++- ui/observatory/js/hud-controller.js | 567 ++++++++++++++ ui/observatory/js/main.js | 715 ++++++++++++++++++ 6 files changed, 2435 insertions(+), 49 deletions(-) create mode 100644 docs/adr/ADR-048-adaptive-csi-classifier.md create mode 100644 rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/adaptive_classifier.rs create mode 100644 ui/observatory/js/hud-controller.js create mode 100644 ui/observatory/js/main.js diff --git a/docs/adr/ADR-048-adaptive-csi-classifier.md b/docs/adr/ADR-048-adaptive-csi-classifier.md new file mode 100644 index 00000000..86fa0066 --- /dev/null +++ b/docs/adr/ADR-048-adaptive-csi-classifier.md @@ -0,0 +1,140 @@ +# ADR-048: Adaptive CSI Activity Classifier + +| Field | Value | +|-------|-------| +| Status | Accepted | +| Date | 2026-03-05 | +| Deciders | ruv | +| Depends on | ADR-024 (AETHER Embeddings), ADR-039 (Edge Processing), ADR-045 (AMOLED Display) | + +## Context + +WiFi-based activity classification using ESP32 Channel State Information (CSI) relies on hand-tuned thresholds to distinguish between activity states (absent, present_still, present_moving, active). These static thresholds are brittle — they don't account for: + +- **Environment-specific signal patterns**: Room geometry, furniture, wall materials, and ESP32 placement all affect how CSI signals respond to human activity. +- **Temporal noise characteristics**: Real ESP32 CSI data at ~10 FPS has significant frame-to-frame jitter that causes classification to jump between states. +- **Vital signs estimation noise**: Heart rate and breathing rate estimates from Goertzel filter banks produce large swings (50+ BPM frame-to-frame) at low confidence levels. + +The existing threshold-based approach produces noisy, unstable classifications that degrade the user experience in the Observatory visualization and the main dashboard. + +## Decision + +### 1. Three-Stage Signal Smoothing Pipeline + +All CSI-derived metrics pass through a three-stage pipeline before reaching the UI: + +#### Stage 1: Adaptive Baseline Subtraction +- EMA with α=0.003 (~30s time constant) tracks the "quiet room" noise floor +- Only updates during low-motion periods to avoid inflating baseline during activity +- 50-frame warm-up period for initial baseline learning +- Subtracts 70% of baseline from raw motion score to remove environmental drift + +#### Stage 2: EMA + Median Filtering +- **Motion score**: Blended from 4 signals (temporal diff 40%, variance 20%, motion band power 25%, change points 15%), then EMA-smoothed with α=0.15 +- **Vital signs**: 21-frame sliding window → trimmed mean (drop top/bottom 25%) → EMA with α=0.02 (~5s time constant) +- **Dead-band**: HR won't update unless trimmed mean differs by >2 BPM; BR needs >0.5 BPM +- **Outlier rejection**: HR jumps >8 BPM/frame and BR jumps >2 BPM/frame are discarded + +#### Stage 3: Hysteresis Debounce +- Activity state transitions require 4 consecutive frames (~0.4s) of agreement before committing +- Prevents rapid flickering between states +- Independent candidate tracking resets on new direction changes + +### 2. Adaptive Classifier Module (`adaptive_classifier.rs`) + +A Rust-native environment-tuned classifier that learns from labeled JSONL recordings: + +#### Feature Extraction (15 features) +| # | Feature | Source | Discriminative Power | +|---|---------|--------|---------------------| +| 0 | variance | Server | Medium — temporal CSI spread | +| 1 | motion_band_power | Server | Medium — high-frequency subcarrier energy | +| 2 | breathing_band_power | Server | Low — respiratory band energy | +| 3 | spectral_power | Server | Low — mean squared amplitude | +| 4 | dominant_freq_hz | Server | Low — peak subcarrier index | +| 5 | change_points | Server | Medium — threshold crossing count | +| 6 | mean_rssi | Server | Low — received signal strength | +| 7 | amp_mean | Subcarrier | Medium — mean amplitude across 56 subcarriers | +| 8 | amp_std | Subcarrier | **High** — amplitude spread (motion increases spread) | +| 9 | amp_skew | Subcarrier | Medium — asymmetry of amplitude distribution | +| 10 | amp_kurt | Subcarrier | **High** — peakedness (presence creates peaks) | +| 11 | amp_iqr | Subcarrier | Medium — inter-quartile range | +| 12 | amp_entropy | Subcarrier | **High** — spectral entropy (motion increases disorder) | +| 13 | amp_max | Subcarrier | Medium — peak amplitude value | +| 14 | amp_range | Subcarrier | Medium — amplitude dynamic range | + +#### Training Algorithm +- **Multiclass logistic regression** with softmax output +- **Mini-batch SGD** (batch size 32, 200 epochs, linear learning rate decay) +- **Z-score normalisation** using global mean/stddev computed from all training data +- Per-class statistics (mean, stddev) stored for Mahalanobis distance fallback +- Deterministic shuffling (LCG PRNG, seed 42) for reproducible results + +#### Training Data Pipeline +1. Record labeled CSI sessions via `POST /api/v1/recording/start {"id":"train_