Based on ADR-014 (SOTA Signal Processing) and the wifi-densepose-signal crate.
| Term | Definition |
|---|---|
| CsiFrame | A single CSI measurement: amplitude + phase per antenna per subcarrier at one timestamp |
| Conjugate Multiplication | H_ref[k] * conj(H_target[k]) — cancels CFO/SFO/PDD, isolating environment-induced phase |
| CSI Ratio | The complex result of conjugate multiplication between two antenna streams |
| Hampel Filter | Running median +/- scaled MAD outlier detector; resists up to 50% contamination |
| Phase Sanitization | Pipeline of unwrapping, outlier removal, smoothing, and noise filtering on raw CSI phase |
| Spectrogram | 2D time-frequency matrix from STFT, standard CNN input for WiFi activity recognition |
| Subcarrier Sensitivity | Variance ratio (motion var / static var) ranking how responsive a subcarrier is to motion |
| Body Velocity Profile (BVP) | Doppler-derived velocity x time 2D matrix; domain-independent motion representation |
| Fresnel Zone | Ellipsoidal region between TX and RX where signal reflection/diffraction occurs |
| Breathing Estimate | BPM + amplitude + confidence derived from Fresnel zone boundary crossings |
| Motion Score | Composite (0.0-1.0) from variance, correlation, phase, and optional Doppler components |
| Presence State | Binary detection result: human present/absent with smoothed confidence |
| Calibration | Recording baseline variance during a known-empty period for adaptive detection |
Responsibility: Produce clean, hardware-artifact-free CSI data from raw measurements.
+-----------------------------------------------------------+
| CSI Preprocessing Context |
+-----------------------------------------------------------+
| |
| +--------------+ +--------------+ +------------+ |
| | Conjugate | | Hampel | | Phase | |
| | Multiplication| | Filter | | Sanitizer | |
| +------+-------+ +------+-------+ +-----+------+ |
| | | | |
| v v v |
| +------+-------+ +------+-------+ +-----+------+ |
| | CsiRatio | | HampelResult | | Sanitized | |
| | (clean phase)| |(outlier-free)| | Phase | |
| +--------------+ +--------------+ +------------+ |
| | | | |
| +-------------------+------------------+ |
| | |
| v |
| +-------+--------+ |
| | CsiProcessor |--> CleanedCsiData |
| +----------------+ |
| |
+-----------------------------------------------------------+
Aggregates: CsiProcessor (Aggregate Root)
Value Objects: CsiData, CsiRatio, HampelResult, HampelConfig, PhaseSanitizerConfig
Domain Services: CsiPreprocessor, PhaseSanitizer
Responsibility: Transform clean CSI data into ML-ready feature representations.
+-----------------------------------------------------------+
| Feature Extraction Context |
+-----------------------------------------------------------+
| |
| +--------------+ +--------------+ +------------+ |
| | STFT | | Subcarrier | | Doppler | |
| | Spectrogram | | Selection | | BVP Engine | |
| +------+-------+ +------+-------+ +-----+------+ |
| | | | |
| v v v |
| +------+-------+ +------+-------+ +-----+------+ |
| | Spectrogram | | Subcarrier | | BodyVel | |
| | (2D TF) | | Selection | | Profile | |
| +--------------+ +--------------+ +------------+ |
| | | | |
| +-------------------+------------------+ |
| | |
| v |
| +----------+----------+ |
| | FeatureExtractor |--> CsiFeatures |
| +---------------------+ |
| |
+-----------------------------------------------------------+
Aggregates: FeatureExtractor (Aggregate Root)
Value Objects: Spectrogram, SubcarrierSelection, BodyVelocityProfile, CsiFeatures
Domain Services: SpectrogramConfig, SubcarrierSelectionConfig, BvpConfig
Responsibility: Detect and classify human motion and vital signs from CSI features.
+-----------------------------------------------------------+
| Motion Analysis Context |
+-----------------------------------------------------------+
| |
| +--------------+ +--------------+ |
| | Motion | | Fresnel | |
| | Detector | | Breathing | |
| +------+-------+ +------+-------+ |
| | | |
| v v |
| +------+-------+ +------+-------+ |
| | MotionScore | | Breathing | |
| |+ Detection | | Estimate | |
| +--------------+ +--------------+ |
| | | |
| +-------------------+ |
| | |
| v |
| +--------+--------+ |
| | HumanDetection |--> PresenceState |
| | Result | |
| +-----------------+ |
| |
+-----------------------------------------------------------+
Aggregates: MotionDetector (Aggregate Root)
Value Objects: MotionScore, MotionAnalysis, HumanDetectionResult, BreathingEstimate, FresnelGeometry
Domain Services: FresnelBreathingEstimator
pub struct CsiProcessor {
config: CsiProcessorConfig,
preprocessor: CsiPreprocessor,
history: VecDeque<CsiData>,
previous_detection_confidence: f64,
statistics: ProcessingStatistics,
}
impl CsiProcessor {
/// Create with validated configuration
pub fn new(config: CsiProcessorConfig) -> Result<Self, CsiProcessorError>;
/// Full preprocessing pipeline: noise removal -> windowing -> normalization
pub fn preprocess(&self, csi_data: &CsiData) -> Result<CsiData, CsiProcessorError>;
/// Maintain temporal history for downstream feature extraction
pub fn add_to_history(&mut self, csi_data: CsiData);
/// Apply exponential moving average to detection confidence
pub fn apply_temporal_smoothing(&mut self, raw_confidence: f64) -> f64;
}pub struct FeatureExtractor {
config: FeatureExtractorConfig,
}
impl FeatureExtractor {
/// Extract all feature types from a single CsiData snapshot
pub fn extract(&self, csi_data: &CsiData) -> CsiFeatures;
}pub struct MotionDetector {
config: MotionDetectorConfig,
previous_confidence: f64,
motion_history: VecDeque<MotionScore>,
baseline_variance: Option<f64>,
}
impl MotionDetector {
/// Analyze motion from extracted features
pub fn analyze_motion(&self, features: &CsiFeatures) -> MotionAnalysis;
/// Full detection pipeline: analyze -> score -> smooth -> threshold
pub fn detect_human(&mut self, features: &CsiFeatures) -> HumanDetectionResult;
/// Record baseline variance for adaptive detection
pub fn calibrate(&mut self, features: &CsiFeatures);
}pub struct CsiData {
pub timestamp: DateTime<Utc>,
pub amplitude: Array2<f64>, // (num_antennas x num_subcarriers)
pub phase: Array2<f64>, // (num_antennas x num_subcarriers), radians
pub frequency: f64, // center frequency in Hz
pub bandwidth: f64, // bandwidth in Hz
pub num_subcarriers: usize,
pub num_antennas: usize,
pub snr: f64, // signal-to-noise ratio in dB
pub metadata: CsiMetadata,
}pub struct Spectrogram {
pub data: Array2<f64>, // (n_freq x n_time) power/magnitude
pub n_freq: usize, // frequency bins (window_size/2 + 1)
pub n_time: usize, // time frames
pub freq_resolution: f64, // Hz per bin
pub time_resolution: f64, // seconds per frame
}pub struct SubcarrierSelection {
pub selected_indices: Vec<usize>, // ranked by sensitivity, descending
pub sensitivity_scores: Vec<f64>, // variance ratio for ALL subcarriers
pub selected_data: Option<Array2<f64>>, // filtered matrix (optional)
}pub struct BodyVelocityProfile {
pub data: Array2<f64>, // (n_velocity_bins x n_time_frames)
pub velocity_bins: Vec<f64>, // velocity value for each row (m/s)
pub n_time: usize,
pub time_resolution: f64, // seconds per frame
pub velocity_resolution: f64, // m/s per bin
}pub struct BreathingEstimate {
pub rate_bpm: f64, // breaths per minute
pub confidence: f64, // combined confidence (0.0-1.0)
pub period_seconds: f64, // estimated breathing period
pub autocorrelation_peak: f64, // periodicity quality
pub fresnel_confidence: f64, // Fresnel model match
pub amplitude_variation: f64, // observed amplitude variation
}pub struct MotionScore {
pub total: f64, // weighted composite (0.0-1.0)
pub variance_component: f64,
pub correlation_component: f64,
pub phase_component: f64,
pub doppler_component: Option<f64>,
}pub struct HampelResult {
pub filtered: Vec<f64>, // outliers replaced with local median
pub outlier_indices: Vec<usize>,
pub medians: Vec<f64>, // local median at each sample
pub sigma_estimates: Vec<f64>, // estimated local sigma at each sample
}pub struct FresnelGeometry {
pub d_tx_body: f64, // TX to body distance (meters)
pub d_body_rx: f64, // body to RX distance (meters)
pub frequency: f64, // carrier frequency (Hz)
}
impl FresnelGeometry {
pub fn wavelength(&self) -> f64;
pub fn fresnel_radius(&self, n: u32) -> f64;
pub fn phase_change(&self, displacement_m: f64) -> f64;
pub fn expected_amplitude_variation(&self, displacement_m: f64) -> f64;
}pub enum PreprocessingEvent {
/// Raw CSI frame cleaned through the full pipeline
FrameCleaned {
timestamp: DateTime<Utc>,
num_antennas: usize,
num_subcarriers: usize,
noise_filtered: bool,
windowed: bool,
normalized: bool,
},
/// Outliers detected and replaced by Hampel filter
OutliersDetected {
subcarrier_indices: Vec<usize>,
replacement_values: Vec<f64>,
contamination_ratio: f64,
},
/// Phase sanitization completed
PhaseSanitized {
method: UnwrappingMethod,
outliers_removed: usize,
smoothing_applied: bool,
},
}pub enum FeatureExtractionEvent {
/// Spectrogram computed from temporal CSI stream
SpectrogramGenerated {
n_time: usize,
n_freq: usize,
window_size: usize,
window_fn: WindowFunction,
},
/// Top-K sensitive subcarriers selected
SubcarriersSelected {
top_k_indices: Vec<usize>,
sensitivity_scores: Vec<f64>,
min_sensitivity_threshold: f64,
},
/// Body Velocity Profile extracted
BvpExtracted {
n_velocity_bins: usize,
n_time_frames: usize,
max_velocity: f64,
carrier_frequency: f64,
},
}pub enum MotionAnalysisEvent {
/// Human motion detected above threshold
MotionDetected {
score: MotionScore,
confidence: f64,
threshold: f64,
timestamp: DateTime<Utc>,
},
/// Breathing detected via Fresnel zone model
BreathingDetected {
rate_bpm: f64,
amplitude_variation: f64,
fresnel_confidence: f64,
autocorrelation_peak: f64,
},
/// Presence state changed (entered or left)
PresenceChanged {
previous: bool,
current: bool,
smoothed_confidence: f64,
timestamp: DateTime<Utc>,
},
/// Detector calibrated with baseline variance
BaselineCalibrated {
baseline_variance: f64,
timestamp: DateTime<Utc>,
},
}-
Conjugate multiplication requires >= 2 antenna elements.
compute_ratio_matrixreturnsCsiRatioError::InsufficientAntennasifn_ant < 2. Without two antennas, there is no pair to cancel common-mode offsets. -
Hampel filter window must be >= 1 (half_window > 0). A zero-width window cannot compute a local median. Enforced by
HampelError::InvalidWindow. -
Phase data must be within configured range before sanitization. Default range is
[-pi, pi]. Enforced byPhaseSanitizer::validate_phase_data. -
Antenna stream lengths must match for conjugate multiplication.
conjugate_multiplyreturnsCsiRatioError::LengthMismatchifh_ref.len() != h_target.len().
-
Spectrogram window size must be > 0 and signal must be >= window_size samples. Enforced by
SpectrogramError::SignalTooShortandSpectrogramError::InvalidWindowSize. -
Subcarrier selection must receive matching subcarrier counts. Motion and static data must have the same number of columns. Enforced by
SelectionError::SubcarrierCountMismatch. -
BVP requires >= window_size temporal samples. Insufficient history prevents STFT computation. Enforced by
BvpError::InsufficientSamples. -
BVP carrier frequency must be > 0 for wavelength calculation. Zero frequency would produce a division-by-zero in the Doppler-to-velocity mapping.
-
Fresnel geometry requires positive distances (d_tx_body > 0, d_body_rx > 0). Zero or negative distances are physically impossible. Enforced by
FresnelError::InvalidDistance. -
Fresnel frequency must be positive. Required for wavelength computation. Enforced by
FresnelError::InvalidFrequency. -
Breathing estimation requires >= 10 amplitude samples. Fewer samples cannot support autocorrelation analysis. Enforced by
FresnelError::InsufficientData. -
Motion detector history does not exceed configured max size. Oldest entries are evicted via
VecDeque::pop_frontwhen capacity is reached.
Orchestrates the cleaning pipeline for a single CSI frame.
pub struct CsiPreprocessor {
noise_threshold: f64,
}
impl CsiPreprocessor {
/// Remove subcarriers below noise floor (amplitude in dB < threshold)
pub fn remove_noise(&self, csi_data: &CsiData) -> Result<CsiData, CsiProcessorError>;
/// Apply Hamming window to reduce spectral leakage
pub fn apply_windowing(&self, csi_data: &CsiData) -> Result<CsiData, CsiProcessorError>;
/// Normalize amplitude to unit variance
pub fn normalize_amplitude(&self, csi_data: &CsiData) -> Result<CsiData, CsiProcessorError>;
}Full phase cleaning pipeline: unwrap -> outlier removal -> smoothing -> noise filtering.
pub struct PhaseSanitizer {
config: PhaseSanitizerConfig,
statistics: SanitizationStatistics,
}
impl PhaseSanitizer {
/// Complete sanitization pipeline (all four stages)
pub fn sanitize_phase(
&mut self,
phase_data: &Array2<f64>,
) -> Result<Array2<f64>, PhaseSanitizationError>;
}Physics-based breathing detection using Fresnel zone geometry.
pub struct FresnelBreathingEstimator {
geometry: FresnelGeometry,
min_displacement: f64, // 3mm default
max_displacement: f64, // 15mm default
}
impl FresnelBreathingEstimator {
/// Check if amplitude variation matches Fresnel breathing model
pub fn breathing_confidence(&self, observed_amplitude_variation: f64) -> f64;
/// Estimate breathing rate via autocorrelation + Fresnel validation
pub fn estimate_breathing_rate(
&self,
amplitude_signal: &[f64],
sample_rate: f64,
) -> Result<BreathingEstimate, FresnelError>;
}+--------------------------------------------------------------+
| Signal Processing System |
+--------------------------------------------------------------+
| |
| +----------------+ Published +------------------+ |
| | CSI | Language | Feature | |
| | Preprocessing |------------>| Extraction | |
| | Context | CsiData | Context | |
| +-------+--------+ +--------+---------+ |
| | | |
| | Publishes | Publishes |
| | CleanedCsiData | CsiFeatures |
| v v |
| +-------+-------------------------------+---------+ |
| | Event Bus (Domain Events) | |
| +---------------------------+---------------------+ |
| | |
| | Subscribes |
| v |
| +---------+---------+ |
| | Motion | |
| | Analysis | |
| | Context | |
| +-------------------+ |
| |
+---------------------------------------------------------------+
| DOWNSTREAM (Customer/Supplier) |
| +-----------------+ +------------------+ +--------------+ |
| | wifi-densepose | | wifi-densepose | |wifi-densepose| |
| | -nn | | -mat | | -train | |
| | (consumes | | (consumes | |(consumes | |
| | CsiFeatures, | | BreathingEst, | | CsiFeatures) | |
| | Spectrogram) | | MotionScore) | | | |
| +-----------------+ +------------------+ +--------------+ |
+---------------------------------------------------------------+
| UPSTREAM (Conformist) |
| +-----------------+ +------------------+ |
| | wifi-densepose | | wifi-densepose | |
| | -core | | -hardware | |
| | (CsiFrame | | (ESP32 raw CSI | |
| | primitives) | | data ingestion) | |
| +-----------------+ +------------------+ |
+---------------------------------------------------------------+
Relationship Types:
- Preprocessing -> Feature Extraction: Published Language (CsiData is the shared contract)
- Preprocessing -> Motion Analysis: Customer/Supplier (Preprocessing supplies cleaned data)
- Feature Extraction -> Motion Analysis: Customer/Supplier (Features supplies CsiFeatures)
- Signal -> wifi-densepose-nn: Customer/Supplier (Signal publishes Spectrogram, BVP)
- Signal -> wifi-densepose-mat: Customer/Supplier (Signal publishes BreathingEstimate, MotionScore)
- Signal <- wifi-densepose-core: Conformist (Signal adapts to core CsiFrame types)
- Signal <- wifi-densepose-hardware: Conformist (Signal adapts to raw ESP32 CSI format)
Translates raw ESP32 CSI packets into the signal crate's CsiData value object, normalizing hardware-specific quirks (LLTF/HT-LTF format differences, antenna mapping, null subcarrier handling).
/// Normalizes vendor-specific CSI frames to canonical CsiData
pub struct HardwareNormalizer {
hardware_type: HardwareType,
}
impl HardwareNormalizer {
/// Convert raw hardware bytes to canonical CsiData
pub fn normalize(
&self,
raw_csi: &[u8],
hardware_type: HardwareType,
) -> Result<CanonicalCsiFrame, HardwareNormError>;
}
pub enum HardwareType {
Esp32S3,
Intel5300,
AtherosAr9580,
Simulation,
}Adapts signal processing outputs (Spectrogram, BVP, CsiFeatures) into tensor formats expected by the wifi-densepose-nn crate. This boundary prevents neural network model details from leaking into the signal processing domain.
/// Adapts signal crate types to neural network tensor format
pub struct SignalToTensorAdapter;
impl SignalToTensorAdapter {
/// Convert Spectrogram to CNN-ready 2D tensor
pub fn spectrogram_to_tensor(spec: &Spectrogram) -> Array2<f32> {
spec.data.mapv(|v| v as f32)
}
/// Convert BVP to domain-independent velocity tensor
pub fn bvp_to_tensor(bvp: &BodyVelocityProfile) -> Array2<f32> {
bvp.data.mapv(|v| v as f32)
}
/// Convert selected subcarrier data to reduced-dimension input
pub fn selected_csi_to_tensor(
selection: &SubcarrierSelection,
data: &Array2<f64>,
) -> Result<Array2<f32>, SelectionError> {
let extracted = extract_selected(data, selection)?;
Ok(extracted.mapv(|v| v as f32))
}
}Adapts motion analysis outputs for the Mass Casualty Assessment Tool, translating domain-generic motion scores and breathing estimates into disaster-context vital signs.
/// Adapts signal processing outputs for disaster assessment
pub struct SignalToMatAdapter;
impl SignalToMatAdapter {
/// Convert BreathingEstimate to MAT-domain BreathingPattern
pub fn to_breathing_pattern(est: &BreathingEstimate) -> BreathingPattern {
BreathingPattern {
rate_bpm: est.rate_bpm as f32,
amplitude: est.amplitude_variation as f32,
regularity: est.autocorrelation_peak as f32,
pattern_type: classify_breathing_type(est.rate_bpm),
}
}
/// Convert MotionScore to MAT-domain presence indicator
pub fn to_presence_indicator(score: &MotionScore) -> PresenceIndicator {
PresenceIndicator {
detected: score.total > 0.3,
confidence: score.total,
motion_level: classify_motion_level(score),
}
}
}