Back to Ruview

Medical & Health Modules -- WiFi-DensePose Edge Intelligence

docs/edge-modules/medical.md

0.7.039.5 KB
Original Source

Medical & Health Modules -- WiFi-DensePose Edge Intelligence

Contactless health monitoring using WiFi signals. No wearables, no cameras -- just an ESP32 sensor reading WiFi reflections off a person's body to detect breathing problems, heart rhythm issues, walking difficulties, and seizures.

Important Disclaimer

These modules are research tools, not FDA-approved medical devices. They should supplement -- not replace -- professional medical monitoring. WiFi CSI-derived vital signs are inherently noisier than clinical instruments (ECG, pulse oximetry, respiratory belts). False positives and false negatives will occur. Always validate findings against clinical-grade equipment before acting on alerts.

Overview

ModuleFileWhat It DoesEvent IDsBudget
Sleep Apnea Detectionmed_sleep_apnea.rsDetects apnea episodes when breathing ceases for >10s; tracks AHI score100-102L (< 2 ms)
Cardiac Arrhythmiamed_cardiac_arrhythmia.rsDetects tachycardia, bradycardia, missed beats, HRV anomalies110-113S (< 5 ms)
Respiratory Distressmed_respiratory_distress.rsDetects tachypnea, labored breathing, Cheyne-Stokes, composite distress score120-123H (< 10 ms)
Gait Analysismed_gait_analysis.rsExtracts step cadence, asymmetry, shuffling, festination, fall-risk score130-134H (< 10 ms)
Seizure Detectionmed_seizure_detect.rsDetects tonic-clonic seizures with phase discrimination (fall vs tremor)140-143S (< 5 ms)

All modules:

  • Compile to no_std for WASM (ESP32 WASM3 runtime)
  • Use const fn new() for zero-cost initialization
  • Return events via &[(i32, f32)] slices (no heap allocation)
  • Include NaN and division-by-zero protections
  • Implement cooldown timers to prevent event flooding

Modules

Sleep Apnea Detection (med_sleep_apnea.rs)

What it does: Monitors breathing rate from the host CSI pipeline and detects when breathing drops below 4 BPM for more than 10 consecutive seconds, indicating an apnea episode. It tracks all episodes and computes the Apnea-Hypopnea Index (AHI) -- the number of apnea events per hour of monitored sleep time. AHI is the standard clinical metric for sleep apnea severity.

Clinical basis: Obstructive and central sleep apnea are defined by cessation of airflow for 10 seconds or more. The module uses a breathing rate threshold of 4 BPM (essentially near-zero breathing) with a 10-second onset delay to confirm cessation is sustained. AHI severity classification: < 5 normal, 5-15 mild, 15-30 moderate, > 30 severe.

How it works:

  1. Each second, checks if breathing BPM is below 4.0
  2. Increments a consecutive-low-breath counter
  3. After 10 consecutive seconds, declares apnea onset (backdated to when breathing first dropped)
  4. When breathing resumes above 4 BPM, records the episode with its duration
  5. Every 5 minutes, computes AHI = (total episodes) / (monitoring hours)
  6. Only monitors when presence is detected; if subject leaves during apnea, the episode is ended

API

ItemTypeDescription
SleepApneaDetectorstructMain detector state
SleepApneaDetector::new()const fnCreate detector with zeroed state
process_frame(breathing_bpm, presence, variance)methodProcess one frame at ~1 Hz; returns event slice
ahi()methodCurrent AHI value
episode_count()methodTotal recorded apnea episodes
monitoring_seconds()methodTotal seconds with presence active
in_apnea()methodWhether currently in an apnea episode
APNEA_BPM_THRESHconst4.0 BPM -- below this counts as apnea
APNEA_ONSET_SECSconst10 seconds -- minimum duration to declare apnea
AHI_REPORT_INTERVALconst300 seconds (5 min) -- how often AHI is recalculated
MAX_EPISODESconst256 -- maximum episodes stored per session

Events Emitted

Event IDConstantValueClinical Meaning
100EVENT_APNEA_STARTCurrent breathing BPMBreathing has ceased or dropped below 4 BPM for >10 seconds
101EVENT_APNEA_ENDDuration in secondsBreathing has resumed after an apnea episode
102EVENT_AHI_UPDATEAHI score (events/hour)Periodic severity metric; >5 = mild, >15 = moderate, >30 = severe

State Machine

                          presence lost
    [Monitoring] -----> [Not Monitoring] (no events, counter paused)
         |                    |
         | bpm < 4.0          | presence regained
         v                    v
    [Low Breath Counter]  [Monitoring]
         |
         | count >= 10s
         v
    [In Apnea] ---------> [Episode End] (bpm >= 4.0 or presence lost)
         |                      |
         |                      v
         |               [Record Episode, emit APNEA_END]
         |
         +-- emit APNEA_START (once)

Configuration

ParameterDefaultClinical RangeDescription
APNEA_BPM_THRESH4.00-6 BPMBreathing rate below which apnea is suspected
APNEA_ONSET_SECS1010-20 sSeconds of low breathing before apnea is declared
AHI_REPORT_INTERVAL30060-3600 sHow often AHI is recalculated and emitted
MAX_EPISODES256--Fixed buffer size for episode history
PRESENCE_ACTIVE1--Minimum presence flag value for monitoring

Example Usage

rust
use wifi_densepose_wasm_edge::med_sleep_apnea::*;

let mut detector = SleepApneaDetector::new();

// Normal breathing -- no events
let events = detector.process_frame(14.0, 1, 0.1);
assert!(events.is_empty());

// Simulate apnea: feed low BPM for 15 seconds
for _ in 0..15 {
    let events = detector.process_frame(1.0, 1, 0.1);
    for &(event_id, value) in events {
        match event_id {
            EVENT_APNEA_START => println!("Apnea detected! BPM: {}", value),
            _ => {}
        }
    }
}
assert!(detector.in_apnea());

// Resume normal breathing
let events = detector.process_frame(14.0, 1, 0.1);
for &(event_id, value) in events {
    match event_id {
        EVENT_APNEA_END => println!("Apnea ended after {} seconds", value),
        _ => {}
    }
}

println!("Episodes: {}", detector.episode_count());
println!("AHI: {:.1}", detector.ahi());

Tutorial: Setting Up Bedroom Sleep Monitoring

  1. ESP32 placement: Mount the ESP32-S3 on the wall or ceiling 1-2 meters from the bed, at chest height. The sensor should have line-of-sight to the sleeping area. Avoid placing near metal objects or moving fans that create CSI interference.

  2. WiFi router: Ensure a stable WiFi AP is within range. The ESP32 monitors the CSI (Channel State Information) of WiFi signals reflected off the person's body. The AP should be on the opposite side of the bed from the sensor for best body reflection capture.

  3. Firmware configuration: Flash the ESP32 firmware with Tier 2 edge processing enabled (provides breathing BPM). The sleep apnea WASM module runs as a Tier 3 algorithm on top of the Tier 2 vitals output.

  4. Threshold tuning: The default 4 BPM threshold is conservative (near-complete cessation). For a more sensitive detector, lower to 6-8 BPM, but expect more false positives from shallow breathing. The 10-second onset delay matches clinical apnea definitions.

  5. Reading AHI results: AHI is emitted every 5 minutes. After a full night (7-8 hours), the final AHI value represents the overnight severity. Compare against clinical thresholds: < 5 (normal), 5-15 (mild), 15-30 (moderate), > 30 (severe).

  6. Limitations: WiFi-based breathing detection works best when the subject is relatively still (sleeping). Tossing and turning may cause momentary breathing detection loss, which could either mask or falsely trigger apnea events. A single-night study should always be confirmed with clinical polysomnography.


Cardiac Arrhythmia Detection (med_cardiac_arrhythmia.rs)

What it does: Monitors heart rate from the host CSI pipeline and detects four types of cardiac rhythm abnormalities: tachycardia (sustained fast heart rate), bradycardia (sustained slow heart rate), missed beats (sudden HR drops), and HRV anomalies (heart rate variability outside normal bounds).

Clinical basis: Tachycardia is defined as HR > 100 BPM sustained for 10+ seconds. Bradycardia is HR < 50 BPM sustained for 10+ seconds (the 50 BPM threshold is used instead of the typical 60 BPM to account for CSI measurement noise and to avoid false positives in athletes with naturally low resting HR). Missed beats are detected as a >30% drop from the running average. HRV is assessed via RMSSD (root mean square of successive differences) with a widened normal band (10-120 ms equivalent) to account for the coarser CSI-derived HR measurement compared to ECG.

How it works:

  1. Maintains an exponential moving average (EMA) of heart rate with alpha=0.1
  2. Tracks consecutive seconds above 100 BPM (tachycardia) or below 50 BPM (bradycardia)
  3. After 10 consecutive seconds in an abnormal range, emits the corresponding alert
  4. Computes fractional drop from EMA to detect missed beats
  5. Maintains a 30-second ring buffer of successive HR differences for RMSSD calculation
  6. RMSSD is converted from BPM units to approximate ms-equivalent (scale factor ~17)
  7. All alerts have a 30-second cooldown to prevent event flooding
  8. Invalid readings (< 1 BPM or NaN) are silently ignored to prevent contamination

API

ItemTypeDescription
CardiacArrhythmiaDetectorstructMain detector state
CardiacArrhythmiaDetector::new()const fnCreate detector with zeroed state
process_frame(hr_bpm, phase)methodProcess one frame at ~1 Hz; returns event slice
hr_ema()methodCurrent EMA heart rate
frame_count()methodTotal frames processed
TACHY_THRESHconst100.0 BPM
BRADY_THRESHconst50.0 BPM
SUSTAINED_SECSconst10 seconds
MISSED_BEAT_DROPconst0.30 (30% drop from EMA)
HRV_WINDOWconst30 seconds
RMSSD_LOW / RMSSD_HIGHconst10.0 / 120.0 ms (widened for CSI)
COOLDOWN_SECSconst30 seconds

Events Emitted

Event IDConstantValueClinical Meaning
110EVENT_TACHYCARDIACurrent HR in BPMHeart rate sustained above 100 BPM for 10+ seconds
111EVENT_BRADYCARDIACurrent HR in BPMHeart rate sustained below 50 BPM for 10+ seconds
112EVENT_MISSED_BEATCurrent HR in BPMSudden HR drop >30% from running average
113EVENT_HRV_ANOMALYRMSSD value (ms)Heart rate variability outside 10-120 ms normal range

State Machine

The cardiac module does not have a formal state machine -- it uses independent detectors with cooldown timers:

For each frame:
  1. Tick cooldowns (4 independent timers)
  2. Reject invalid inputs (< 1 BPM or NaN)
  3. Update EMA (alpha = 0.1)
  4. Update RR-diff ring buffer
  5. Check tachycardia (HR > 100 for 10+ consecutive seconds)
  6. Check bradycardia (HR < 50 for 10+ consecutive seconds)
  7. Check missed beat (>30% drop from EMA)
  8. Check HRV anomaly (RMSSD outside 10-120 ms, requires full 30s window)
  9. Each check respects its own 30-second cooldown

Configuration

ParameterDefaultClinical RangeDescription
TACHY_THRESH100.090-120 BPMHR threshold for tachycardia
BRADY_THRESH50.040-60 BPMHR threshold for bradycardia
SUSTAINED_SECS105-30 sConsecutive seconds required for alert
MISSED_BEAT_DROP0.300.20-0.40Fractional HR drop to flag missed beat
RMSSD_LOW10.05-20 msMinimum normal RMSSD
RMSSD_HIGH120.080-150 msMaximum normal RMSSD
EMA_ALPHA0.10.05-0.2EMA smoothing coefficient
COOLDOWN_SECS3010-60 sMinimum time between repeated alerts

Example Usage

rust
use wifi_densepose_wasm_edge::med_cardiac_arrhythmia::*;

let mut detector = CardiacArrhythmiaDetector::new();

// Normal heart rate -- no events
for _ in 0..60 {
    let events = detector.process_frame(72.0, 0.0);
    assert!(events.is_empty() || events.iter().all(|&(t, _)| t == EVENT_HRV_ANOMALY));
}

// Sustained tachycardia
for _ in 0..15 {
    let events = detector.process_frame(120.0, 0.0);
    for &(event_id, value) in events {
        if event_id == EVENT_TACHYCARDIA {
            println!("Tachycardia alert! HR: {} BPM", value);
        }
    }
}

Respiratory Distress Detection (med_respiratory_distress.rs)

What it does: Detects four types of respiratory abnormalities from the host CSI pipeline: tachypnea (fast breathing), labored breathing (high amplitude variance), Cheyne-Stokes respiration (a crescendo-decrescendo breathing pattern), and a composite respiratory distress severity score from 0-100.

Clinical basis: Tachypnea is defined clinically as > 20 BPM in adults. This module uses a threshold of 25 BPM (more conservative) to reduce false positives from the inherently noisier CSI-derived breathing rate. Labored breathing is detected as a 3x increase in amplitude variance relative to a learned baseline. Cheyne-Stokes respiration is a pathological breathing pattern with 30-90 second periodicity, commonly associated with heart failure and neurological conditions. The module detects it via autocorrelation of the breathing amplitude envelope.

How it works:

  1. Maintains a 120-second ring buffer of breathing BPM for autocorrelation analysis
  2. Maintains a 60-second ring buffer of amplitude variance
  3. Learns a baseline variance over the first 60 seconds (Welford online mean)
  4. Checks for tachypnea: breathing rate > 25 BPM sustained for 8+ seconds
  5. Checks for labored breathing: current variance > 3x baseline variance
  6. Checks for Cheyne-Stokes: significant autocorrelation peak in 30-90s lag range
  7. Computes composite distress score (0-100) every 30 seconds based on: rate deviation from normal (16 BPM center), variance ratio, tachypnea flag, and recent Cheyne-Stokes detection
  8. NaN inputs are excluded from ring buffers to prevent contamination

API

ItemTypeDescription
RespiratoryDistressDetectorstructMain detector state
RespiratoryDistressDetector::new()const fnCreate detector with zeroed state
process_frame(breathing_bpm, phase, variance)methodProcess one frame at ~1 Hz; returns event slice
last_distress_score()methodMost recent composite score (0-100)
frame_count()methodTotal frames processed
TACHYPNEA_THRESHconst25.0 BPM (conservative; clinical is 20 BPM)
SUSTAINED_SECSconst8 seconds
LABORED_VAR_RATIOconst3.0x baseline
CS_LAG_MIN / CS_LAG_MAXconst30 / 90 seconds (Cheyne-Stokes period range)
CS_PEAK_THRESHconst0.35 (normalized autocorrelation)
BASELINE_SECSconst60 seconds (learning period)
COOLDOWN_SECSconst20 seconds

Events Emitted

Event IDConstantValueClinical Meaning
120EVENT_TACHYPNEACurrent breathing BPMBreathing rate sustained above 25 BPM for 8+ seconds
121EVENT_LABORED_BREATHINGVariance ratioBreathing effort > 3x baseline; possible respiratory distress
122EVENT_CHEYNE_STOKESPeriod in secondsCrescendo-decrescendo breathing pattern; associated with heart failure
123EVENT_RESP_DISTRESS_LEVELScore 0-100Composite severity: 0-20 normal, 20-50 mild, 50-80 moderate, 80-100 severe

State Machine

The respiratory distress module uses independent detector tracks with cooldowns rather than a single state machine:

For each frame:
  1. Tick cooldowns (3 independent timers)
  2. Skip NaN inputs for ring buffer updates
  3. Update breathing BPM ring buffer (120s) and variance ring buffer (60s)
  4. Learn baseline variance during first 60 seconds (Welford)
  5. Tachypnea check: BPM > 25 for 8+ consecutive seconds
  6. Labored breathing: current variance mean > 3x baseline (after baseline period)
  7. Cheyne-Stokes: autocorrelation peak > 0.35 in 30-90s lag range (needs full 120s buffer)
  8. Composite distress score emitted every 30 seconds

Configuration

ParameterDefaultClinical RangeDescription
TACHYPNEA_THRESH25.020-30 BPMBreathing rate for tachypnea alert
SUSTAINED_SECS85-15 sDebounce period for tachypnea
LABORED_VAR_RATIO3.02.0-5.0Variance ratio above baseline
AC_WINDOW12090-180 sAutocorrelation buffer for Cheyne-Stokes
CS_PEAK_THRESH0.350.25-0.50Autocorrelation peak threshold
CS_LAG_MIN / CS_LAG_MAX30 / 9020-120 sCheyne-Stokes period search range
BASELINE_SECS6030-120 sDuration to learn baseline variance
DISTRESS_REPORT_INTERVAL3010-60 sHow often composite score is emitted
COOLDOWN_SECS2010-60 sMinimum time between repeated alerts

Example Usage

rust
use wifi_densepose_wasm_edge::med_respiratory_distress::*;

let mut detector = RespiratoryDistressDetector::new();

// Build baseline with normal breathing (60 seconds)
for _ in 0..60 {
    detector.process_frame(16.0, 0.0, 0.5);
}

// Simulate respiratory distress: high rate + high variance
for _ in 0..30 {
    let events = detector.process_frame(30.0, 0.0, 3.0);
    for &(event_id, value) in events {
        match event_id {
            EVENT_TACHYPNEA => println!("Tachypnea! Rate: {} BPM", value),
            EVENT_LABORED_BREATHING => println!("Labored breathing! Variance ratio: {:.1}x", value),
            EVENT_RESP_DISTRESS_LEVEL => println!("Distress score: {:.0}/100", value),
            _ => {}
        }
    }
}

Tutorial: Setting Up ICU/Ward Monitoring

  1. Placement: Mount the ESP32 at the foot of the bed or on the ceiling directly above the patient. The sensor needs clear WiFi signal reflection from the patient's torso.

  2. Baseline learning: The module automatically learns a 60-second baseline variance when first activated. Ensure the patient is breathing normally during this calibration period. If the patient is already in distress at module start, the baseline will be skewed and labored-breathing detection will be unreliable.

  3. Cheyne-Stokes detection: Requires at least 120 seconds of data to begin autocorrelation analysis. The 30-90 second periodicity search range covers the clinically documented Cheyne-Stokes cycle range. In practice, detection typically becomes reliable after 3-4 minutes of monitoring.

  4. Distress score interpretation: The composite score (0-100) combines four factors: rate deviation from normal, variance ratio, tachypnea presence, and Cheyne-Stokes detection. A score above 50 warrants clinical attention. Above 80 suggests acute distress.


Gait Analysis (med_gait_analysis.rs)

What it does: Extracts gait parameters from CSI phase variance periodicity to assess mobility and fall risk. Detects step cadence, gait asymmetry (limping), stride variability, shuffling gait patterns (associated with Parkinson's disease), festination (involuntary acceleration), and computes a composite fall-risk score from 0-100.

Clinical basis: Normal walking cadence is 80-120 steps/min for healthy adults. Shuffling gait (>140 steps/min with low energy) is characteristic of Parkinson's disease and other neurological conditions. Festination (involuntary cadence acceleration) is a Parkinsonian feature. Gait asymmetry (left/right step interval ratio deviating from 1.0 by >15%) indicates limping or musculoskeletal issues. High stride variability (coefficient of variation) is a strong predictor of fall risk in elderly patients.

How it works:

  1. Maintains a 60-second ring buffer of phase variance and motion energy
  2. Detects steps as local maxima in the phase variance signal (peak-to-trough ratio > 1.5)
  3. Records step intervals in a 64-entry buffer
  4. Every 10 seconds, computes: cadence (60 / mean step interval), asymmetry (odd/even step interval ratio), variability (coefficient of variation)
  5. Tracks cadence history over 6 reporting periods for festination detection
  6. Shuffling is flagged when cadence > 140 and motion energy is low
  7. Festination is detected as cadence accelerating by > 1.5 steps/min/sec
  8. Fall-risk score (0-100) is a weighted composite of: abnormal cadence (25%), asymmetry (25%), variability (25%), low energy (15%), festination (10%)

API

ItemTypeDescription
GaitAnalyzerstructMain analyzer state
GaitAnalyzer::new()const fnCreate analyzer with zeroed state
process_frame(phase, amplitude, variance, motion_energy)methodProcess one frame at ~1 Hz; returns event slice
last_cadence()methodMost recent cadence (steps/min)
last_asymmetry()methodMost recent asymmetry ratio (1.0 = symmetric)
last_fall_risk()methodMost recent fall-risk score (0-100)
frame_count()methodTotal frames processed
NORMAL_CADENCE_LOW / HIGHconst80.0 / 120.0 steps/min
SHUFFLE_CADENCE_HIGHconst140.0 steps/min
ASYMMETRY_THRESHconst0.15 (15% deviation from 1.0)
FESTINATION_ACCELconst1.5 steps/min/sec
REPORT_INTERVALconst10 seconds
COOLDOWN_SECSconst15 seconds

Events Emitted

Event IDConstantValueClinical Meaning
130EVENT_STEP_CADENCESteps/minDetected walking cadence; <80 or >120 is abnormal
131EVENT_GAIT_ASYMMETRYRatio (1.0=symmetric)Step interval asymmetry; >1.15 or <0.85 indicates limping
132EVENT_FALL_RISK_SCOREScore 0-100Composite: 0-25 low, 25-50 moderate, 50-75 high, 75-100 critical
133EVENT_SHUFFLING_DETECTEDCadence (steps/min)High-frequency, low-amplitude gait; Parkinson's indicator
134EVENT_FESTINATIONCadence (steps/min)Involuntary cadence acceleration; Parkinsonian feature

State Machine

The gait analyzer operates on a periodic reporting cycle:

Continuous (every frame):
  - Push variance and energy into ring buffers
  - Detect step peaks (local max in variance > 1.5x neighbors)
  - Record step intervals

Every REPORT_INTERVAL (10s), if >= 4 steps detected:
  1. Compute cadence, asymmetry, variability
  2. Emit EVENT_STEP_CADENCE
  3. If asymmetry > threshold: emit EVENT_GAIT_ASYMMETRY
  4. If cadence > 140 and energy < 0.3: emit EVENT_SHUFFLING_DETECTED
  5. If cadence accelerating > 1.5/s over 3 periods: emit EVENT_FESTINATION
  6. Compute and emit EVENT_FALL_RISK_SCORE
  7. Reset step buffer for next window

Configuration

ParameterDefaultClinical RangeDescription
GAIT_WINDOW6030-120 sRing buffer size for phase variance
STEP_PEAK_RATIO1.51.2-2.0Min peak-to-trough ratio for step detection
NORMAL_CADENCE_LOW80.070-90 steps/minLower bound of normal cadence
NORMAL_CADENCE_HIGH120.0110-130 steps/minUpper bound of normal cadence
SHUFFLE_CADENCE_HIGH140.0120-160 steps/minCadence threshold for shuffling
SHUFFLE_ENERGY_LOW0.30.1-0.5Energy ceiling for shuffling detection
FESTINATION_ACCEL1.51.0-3.0 steps/min/sCadence acceleration threshold
ASYMMETRY_THRESH0.150.10-0.25Asymmetry ratio deviation from 1.0
REPORT_INTERVAL105-30 sGait analysis reporting period
MIN_MOTION_ENERGY0.10.05-0.3Minimum energy for step detection
COOLDOWN_SECS1510-30 sCooldown for shuffling/festination alerts

Example Usage

rust
use wifi_densepose_wasm_edge::med_gait_analysis::*;

let mut analyzer = GaitAnalyzer::new();

// Simulate walking with alternating high/low variance (steps)
for i in 0..30 {
    let variance = if i % 2 == 0 { 5.0 } else { 0.5 };
    let events = analyzer.process_frame(0.0, 1.0, variance, 1.0);
    for &(event_id, value) in events {
        match event_id {
            EVENT_STEP_CADENCE => println!("Cadence: {:.0} steps/min", value),
            EVENT_FALL_RISK_SCORE => println!("Fall risk: {:.0}/100", value),
            EVENT_GAIT_ASYMMETRY => println!("Asymmetry: {:.2}", value),
            _ => {}
        }
    }
}

Tutorial: Setting Up Hallway Gait Monitoring

  1. Placement: Mount the ESP32 in a hallway or corridor at waist height on the wall. The walking path should be 3-5 meters long within the sensor's field of view. Position the WiFi AP at the opposite end of the hallway for optimal body reflection.

  2. Calibration: The step detector relies on periodic peaks in phase variance. The STEP_PEAK_RATIO of 1.5 works well for most flooring surfaces. On carpet (which dampens impact signals), consider lowering to 1.2. On hard floors with shoes, 1.5-2.0 is appropriate.

  3. Clinical context: The fall-risk score is most useful for longitudinal monitoring. A single reading provides a snapshot, but tracking trends over days/weeks reveals progressive mobility decline. A rising fall-risk score (e.g., from 20 to 40 over a month) warrants clinical assessment even if individual readings are below the "high risk" threshold.

  4. Limitations: At a 1 Hz timer rate, the module cannot detect cadences above ~60 steps/min via direct peak counting. For higher cadences, the step detection relies on the host's higher-rate CSI processing to pre-compute variance peaks. Shuffling detection at >140 steps/min requires the host to be providing step-level variance data at higher than 1 Hz.


Seizure Detection (med_seizure_detect.rs)

What it does: Detects tonic-clonic (grand mal) seizures by identifying sustained high-energy rhythmic motion in the 3-8 Hz band. Discriminates seizures from falls (single impulse followed by stillness) and tremor (lower amplitude, higher regularity). Tracks seizure phases: tonic (sustained muscle rigidity), clonic (rhythmic jerking), and post-ictal (sudden cessation of movement).

Clinical basis: Tonic-clonic seizures have a characteristic progression: (1) tonic phase with sustained muscle rigidity causing high motion energy with low variance, lasting 10-20 seconds; (2) clonic phase with rhythmic jerking at 3-8 Hz, lasting 30-60 seconds; (3) post-ictal phase with sudden cessation of movement and deep unresponsiveness. Falls produce a brief (<10 frame) high-energy spike followed by stillness. Tremors have lower amplitude than seizure-grade jerking.

How it works:

  1. Operates at ~20 Hz frame rate (higher than other modules) for rhythm detection
  2. Maintains 100-frame ring buffers for motion energy and amplitude
  3. State machine progresses: Monitoring -> PossibleOnset -> Tonic/Clonic -> PostIctal -> Cooldown
  4. Onset requires 10+ consecutive frames of high motion energy (>2.0 normalized)
  5. Fall discrimination: if high energy lasts < 10 frames then drops, it is classified as a fall and ignored
  6. Tonic phase: high energy with low variance (< 0.5)
  7. Clonic phase: detected via autocorrelation of amplitude buffer for 2-7 frame period (3-8 Hz at 20 Hz sampling)
  8. Post-ictal: motion drops below 0.2 for 40+ consecutive frames
  9. After an episode, 200-frame cooldown prevents re-triggering
  10. Presence must be active; loss of presence resets the state machine

API

ItemTypeDescription
SeizureDetectorstructMain detector state
SeizureDetector::new()const fnCreate detector with zeroed state
process_frame(phase, amplitude, motion_energy, presence)methodProcess at ~20 Hz; returns event slice
phase()methodCurrent SeizurePhase enum value
seizure_count()methodTotal seizure episodes detected
frame_count()methodTotal frames processed
SeizurePhaseenumMonitoring, PossibleOnset, Tonic, Clonic, PostIctal, Cooldown
HIGH_ENERGY_THRESHconst2.0 (normalized)
TONIC_MIN_FRAMESconst20 frames (1 second at 20 Hz)
CLONIC_PERIOD_MIN / MAXconst2 / 7 frames (3-8 Hz at 20 Hz)
POST_ICTAL_MIN_FRAMESconst40 frames (2 seconds at 20 Hz)
COOLDOWN_FRAMESconst200 frames (10 seconds at 20 Hz)

Events Emitted

Event IDConstantValueClinical Meaning
140EVENT_SEIZURE_ONSETMotion energySeizure activity detected; immediate clinical attention needed
141EVENT_SEIZURE_TONICDuration in framesTonic phase identified; sustained rigidity
142EVENT_SEIZURE_CLONICPeriod in framesClonic phase identified; rhythmic jerking with detected periodicity
143EVENT_POST_ICTAL1.0Post-ictal phase; movement has ceased after seizure

State Machine

                    presence lost (from any active state)
                    +-----------------------------------------+
                    v                                         |
[Monitoring] --> [PossibleOnset] --> [Tonic] --> [Clonic] --> [PostIctal] --> [Cooldown]
      ^              |    |              |                         |              |
      |              |    |              +------> [PostIctal] -----+              |
      |              |    |                (direct if energy drops)               |
      |              |    +--------> [Clonic]                                    |
      |              |            (skip tonic)                                   |
      |              |                                                           |
      |              +-- timeout (200 frames) --> [Monitoring]                   |
      |              +-- fall (<10 frames) -----> [Monitoring]                   |
      |                                                                          |
      +------ cooldown expires (200 frames) ------------------------------------+

Transitions:

  • Monitoring -> PossibleOnset: 10+ frames of motion energy > 2.0
  • PossibleOnset -> Tonic: Low energy variance + high energy (muscle rigidity pattern)
  • PossibleOnset -> Clonic: Rhythmic autocorrelation peak + amplitude above tremor floor
  • PossibleOnset -> Monitoring: Energy drop within 10 frames (fall) or timeout at 200 frames
  • Tonic -> Clonic: Energy variance increases and rhythm is detected
  • Tonic -> PostIctal: Motion energy drops below 0.2 for 40+ frames
  • Clonic -> PostIctal: Motion energy drops below 0.2 for 40+ frames
  • PostIctal -> Cooldown: After 40 frames in post-ictal
  • Cooldown -> Monitoring: After 200 frames (10 seconds)

Configuration

ParameterDefaultClinical RangeDescription
ENERGY_WINDOW / PHASE_WINDOW10060-200 framesRing buffer sizes for analysis
HIGH_ENERGY_THRESH2.01.5-3.0Motion energy threshold for onset
TONIC_ENERGY_THRESH1.51.0-2.0Energy threshold during tonic phase
TONIC_VAR_CEIL0.50.3-1.0Max energy variance for tonic classification
TONIC_MIN_FRAMES2010-40 framesMin frames to confirm tonic phase
CLONIC_PERIOD_MIN / MAX2 / 72-10 framesPeriod range for 3-8 Hz rhythm
CLONIC_AUTOCORR_THRESH0.300.20-0.50Autocorrelation threshold for rhythm
CLONIC_MIN_FRAMES3020-60 framesMin frames to confirm clonic phase
POST_ICTAL_ENERGY_THRESH0.20.1-0.5Energy threshold for cessation
POST_ICTAL_MIN_FRAMES4020-80 framesMin frames of low energy
FALL_MAX_DURATION105-20 framesMax high-energy duration classified as fall
TREMOR_AMPLITUDE_FLOOR0.80.5-1.5Min amplitude to distinguish from tremor
COOLDOWN_FRAMES200100-400 framesCooldown after episode completes
ONSET_MIN_FRAMES105-20 framesMin high-energy frames before onset

Example Usage

rust
use wifi_densepose_wasm_edge::med_seizure_detect::*;

let mut detector = SeizureDetector::new();

// Normal motion -- no seizure
for _ in 0..200 {
    let events = detector.process_frame(0.0, 0.5, 0.3, 1);
    assert!(events.is_empty());
}
assert_eq!(detector.phase(), SeizurePhase::Monitoring);

// Tonic phase: sustained high energy, low variance
for _ in 0..50 {
    let events = detector.process_frame(0.0, 2.0, 3.0, 1);
    for &(event_id, value) in events {
        match event_id {
            EVENT_SEIZURE_ONSET => println!("SEIZURE ONSET! Energy: {}", value),
            EVENT_SEIZURE_TONIC => println!("Tonic phase: {} frames", value),
            _ => {}
        }
    }
}

// Post-ictal: sudden cessation
for _ in 0..100 {
    let events = detector.process_frame(0.0, 0.05, 0.05, 1);
    for &(event_id, _) in events {
        if event_id == EVENT_POST_ICTAL {
            println!("Post-ictal phase detected -- patient needs immediate assessment");
        }
    }
}

Tutorial: Setting Up Seizure Monitoring

  1. Placement: Mount the ESP32 on the ceiling directly above the bed or monitoring area. Seizure detection requires the highest sensitivity to body motion, so minimize distance to the patient. Ensure no other people or moving objects are in the sensor's field of view (pets, curtains, fans).

  2. Frame rate: Unlike other medical modules that operate at 1 Hz, the seizure detector expects ~20 Hz frame input for accurate rhythm detection in the 3-8 Hz band. Ensure the host firmware is configured for high-rate CSI processing when this module is loaded.

  3. Sensitivity tuning: The HIGH_ENERGY_THRESH of 2.0 and ONSET_MIN_FRAMES of 10 balance sensitivity against false positives. In a quiet bedroom environment, these defaults work well. In noisier environments (shared ward, nearby equipment vibration), consider raising HIGH_ENERGY_THRESH to 2.5-3.0.

  4. Fall vs seizure discrimination: The module automatically distinguishes falls (brief energy spike < 10 frames) from seizures (sustained energy). If the patient is known to be a fall risk, consider running the gait analysis module in parallel for complementary monitoring.

  5. Response protocol: When EVENT_SEIZURE_ONSET fires, immediately notify clinical staff. The EVENT_POST_ICTAL event indicates the active seizure has ended and the patient is entering post-ictal state -- they need assessment but are no longer in the convulsive phase.


Testing

All medical modules include comprehensive unit tests covering initialization, normal operation, clinical scenario detection, edge cases, and cooldown behavior.

bash
cd rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge
cargo test --features std -- med_

Expected output: 38 tests passed, 0 failed.

Test Coverage by Module

ModuleTestsScenarios Covered
Sleep Apnea7Init, normal breathing, apnea onset/end, no monitoring without presence, AHI update, multiple episodes, presence-loss during apnea
Cardiac Arrhythmia7Init, normal HR, tachycardia, bradycardia, missed beat, HRV anomaly (low variability), cooldown flood prevention, EMA convergence
Respiratory Distress6Init, normal breathing, tachypnea, labored breathing, distress score emission, Cheyne-Stokes detection, distress score range
Gait Analysis7Init, no events without steps, cadence extraction, fall-risk score range, asymmetry detection, shuffling detection, variability (uniform + varied)
Seizure Detection7Init, normal motion, fall discrimination, seizure onset with sustained energy, post-ictal detection, no detection without presence, energy variance, cooldown after episode

Clinical Thresholds Reference

ConditionNormal RangeModule ThresholdClinical StandardNotes
Breathing rate12-20 BPM----Normal adult at rest
Bradypnea< 12 BPMNot directly detected< 12 BPMGap: covered implicitly by distress score
Tachypnea> 20 BPM> 25 BPM> 20 BPMConservative threshold for CSI noise tolerance
Apnea0 BPM< 4 BPM for > 10sCessation > 10s4 BPM threshold accounts for CSI noise floor
Bradycardia< 60 BPM< 50 BPM< 60 BPMLower threshold avoids false positives in athletes
Tachycardia> 100 BPM> 100 BPM> 100 BPMMatches clinical standard
Heart rate (normal)60-100 BPM--60-100 BPM--
AHI (mild apnea)--> 5 events/hr> 5 events/hrMatches clinical standard
AHI (moderate)--> 15 events/hr> 15 events/hrMatches clinical standard
AHI (severe)--> 30 events/hr> 30 events/hrMatches clinical standard
RMSSD (normal HRV)20-80 ms10-120 ms19-75 msWidened band for CSI-derived HR
Gait cadence (normal)80-120 steps/min80-120 steps/min90-120 steps/minSlightly wider range
Gait asymmetry1.0 ratio> 0.15 deviation> 0.10 deviationSlightly higher threshold for CSI
Cheyne-Stokes period30-90 s30-90 s lag search30-100 sMatches clinical range
Seizure clonic frequency3-8 Hz3-8 Hz (period 2-7 frames at 20 Hz)3-8 HzMatches clinical standard

Threshold Rationale

Several thresholds differ from strict clinical standards. This is intentional:

  • WiFi CSI is not ECG/pulse oximetry. The signal-to-noise ratio is lower, so thresholds are widened to reduce false positives while maintaining clinical relevance.
  • Conservative thresholds favor specificity over sensitivity. A missed alert is preferable to alert fatigue in a non-clinical-grade system.
  • All thresholds are compile-time constants. To adjust for a specific deployment, modify the constants at the top of each module file and recompile.

Safety Considerations

  1. Not a substitute for medical devices. These modules are research/assistive tools. They have not been validated through clinical trials and are not FDA/CE cleared. Never rely on them as the sole source of patient monitoring.

  2. False positive rates. WiFi CSI is affected by environmental factors: moving objects (fans, pets, curtains), multipath changes (opening doors, people walking nearby), and electromagnetic interference. Expect false positive rates of 5-15% in typical home environments and 1-5% in controlled clinical settings.

  3. False negative rates. The conservative thresholds mean some borderline conditions may not trigger alerts. Specifically:

    • Bradypnea (12-20 BPM dropping to 12-4 BPM) is not directly flagged -- only sub-4 BPM apnea is detected
    • Mild tachycardia (100-120 BPM) is detected, but the 10-second sustained requirement means brief episodes are missed
    • Low-amplitude seizures without strong motor components may not exceed the energy threshold
  4. Environmental factors affecting accuracy:

    • Multi-person environments: All modules assume a single subject. Multiple people in the sensor's field of view will corrupt readings.
    • Distance: CSI sensitivity drops with distance. Place sensor within 2 meters of the subject.
    • Obstructions: Thick walls, metal furniture, and large water bodies (aquariums) between sensor and subject degrade performance.
    • WiFi congestion: Heavy WiFi traffic on the same channel increases noise in CSI measurements.
  5. Power and connectivity: The ESP32 must maintain continuous WiFi connectivity for CSI monitoring. Power loss or WiFi disconnection will silently stop all monitoring. Consider UPS power and redundant AP placement for critical applications.

  6. Data privacy: These modules process health-related data. Ensure compliance with HIPAA, GDPR, or local health data regulations when deploying in clinical or home care settings. CSI data and emitted events should be encrypted in transit and at rest.