# Signal Processing Domain Model ## Domain-Driven Design Specification Based on ADR-014 (SOTA Signal Processing) and the `wifi-densepose-signal` crate. ### Ubiquitous Language | 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 | --- ## Bounded Contexts ### 1. CSI Preprocessing Context **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` --- ### 2. Feature Extraction Context **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` --- ### 3. Motion Analysis Context **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` --- ## Aggregates ### CsiProcessor (CSI Preprocessing Root) ```rust pub struct CsiProcessor { config: CsiProcessorConfig, preprocessor: CsiPreprocessor, history: VecDeque, previous_detection_confidence: f64, statistics: ProcessingStatistics, } impl CsiProcessor { /// Create with validated configuration pub fn new(config: CsiProcessorConfig) -> Result; /// Full preprocessing pipeline: noise removal -> windowing -> normalization pub fn preprocess(&self, csi_data: &CsiData) -> Result; /// 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; } ``` ### FeatureExtractor (Feature Extraction Root) ```rust pub struct FeatureExtractor { config: FeatureExtractorConfig, } impl FeatureExtractor { /// Extract all feature types from a single CsiData snapshot pub fn extract(&self, csi_data: &CsiData) -> CsiFeatures; } ``` ### MotionDetector (Motion Analysis Root) ```rust pub struct MotionDetector { config: MotionDetectorConfig, previous_confidence: f64, motion_history: VecDeque, baseline_variance: Option, } 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); } ``` --- ## Value Objects ### CsiData ```rust pub struct CsiData { pub timestamp: DateTime, pub amplitude: Array2, // (num_antennas x num_subcarriers) pub phase: Array2, // (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, } ``` ### Spectrogram ```rust pub struct Spectrogram { pub data: Array2, // (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 } ``` ### SubcarrierSelection ```rust pub struct SubcarrierSelection { pub selected_indices: Vec, // ranked by sensitivity, descending pub sensitivity_scores: Vec, // variance ratio for ALL subcarriers pub selected_data: Option>, // filtered matrix (optional) } ``` ### BodyVelocityProfile ```rust pub struct BodyVelocityProfile { pub data: Array2, // (n_velocity_bins x n_time_frames) pub velocity_bins: Vec, // 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 } ``` ### BreathingEstimate ```rust 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 } ``` ### MotionScore ```rust 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, } ``` ### HampelResult ```rust pub struct HampelResult { pub filtered: Vec, // outliers replaced with local median pub outlier_indices: Vec, pub medians: Vec, // local median at each sample pub sigma_estimates: Vec, // estimated local sigma at each sample } ``` ### FresnelGeometry ```rust 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; } ``` --- ## Domain Events ### Preprocessing Events ```rust pub enum PreprocessingEvent { /// Raw CSI frame cleaned through the full pipeline FrameCleaned { timestamp: DateTime, num_antennas: usize, num_subcarriers: usize, noise_filtered: bool, windowed: bool, normalized: bool, }, /// Outliers detected and replaced by Hampel filter OutliersDetected { subcarrier_indices: Vec, replacement_values: Vec, contamination_ratio: f64, }, /// Phase sanitization completed PhaseSanitized { method: UnwrappingMethod, outliers_removed: usize, smoothing_applied: bool, }, } ``` ### Feature Extraction Events ```rust 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, sensitivity_scores: Vec, min_sensitivity_threshold: f64, }, /// Body Velocity Profile extracted BvpExtracted { n_velocity_bins: usize, n_time_frames: usize, max_velocity: f64, carrier_frequency: f64, }, } ``` ### Motion Analysis Events ```rust pub enum MotionAnalysisEvent { /// Human motion detected above threshold MotionDetected { score: MotionScore, confidence: f64, threshold: f64, timestamp: DateTime, }, /// 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, }, /// Detector calibrated with baseline variance BaselineCalibrated { baseline_variance: f64, timestamp: DateTime, }, } ``` --- ## Invariants ### CSI Preprocessing Invariants 1. **Conjugate multiplication requires >= 2 antenna elements.** `compute_ratio_matrix` returns `CsiRatioError::InsufficientAntennas` if `n_ant < 2`. Without two antennas, there is no pair to cancel common-mode offsets. 2. **Hampel filter window must be >= 1 (half_window > 0).** A zero-width window cannot compute a local median. Enforced by `HampelError::InvalidWindow`. 3. **Phase data must be within configured range before sanitization.** Default range is `[-pi, pi]`. Enforced by `PhaseSanitizer::validate_phase_data`. 4. **Antenna stream lengths must match for conjugate multiplication.** `conjugate_multiply` returns `CsiRatioError::LengthMismatch` if `h_ref.len() != h_target.len()`. ### Feature Extraction Invariants 5. **Spectrogram window size must be > 0 and signal must be >= window_size samples.** Enforced by `SpectrogramError::SignalTooShort` and `SpectrogramError::InvalidWindowSize`. 6. **Subcarrier selection must receive matching subcarrier counts.** Motion and static data must have the same number of columns. Enforced by `SelectionError::SubcarrierCountMismatch`. 7. **BVP requires >= window_size temporal samples.** Insufficient history prevents STFT computation. Enforced by `BvpError::InsufficientSamples`. 8. **BVP carrier frequency must be > 0 for wavelength calculation.** Zero frequency would produce a division-by-zero in the Doppler-to-velocity mapping. ### Motion Analysis Invariants 9. **Fresnel geometry requires positive distances (d_tx_body > 0, d_body_rx > 0).** Zero or negative distances are physically impossible. Enforced by `FresnelError::InvalidDistance`. 10. **Fresnel frequency must be positive.** Required for wavelength computation. Enforced by `FresnelError::InvalidFrequency`. 11. **Breathing estimation requires >= 10 amplitude samples.** Fewer samples cannot support autocorrelation analysis. Enforced by `FresnelError::InsufficientData`. 12. **Motion detector history does not exceed configured max size.** Oldest entries are evicted via `VecDeque::pop_front` when capacity is reached. --- ## Domain Services ### CsiPreprocessor Orchestrates the cleaning pipeline for a single CSI frame. ```rust 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; /// Apply Hamming window to reduce spectral leakage pub fn apply_windowing(&self, csi_data: &CsiData) -> Result; /// Normalize amplitude to unit variance pub fn normalize_amplitude(&self, csi_data: &CsiData) -> Result; } ``` ### PhaseSanitizer Full phase cleaning pipeline: unwrap -> outlier removal -> smoothing -> noise filtering. ```rust pub struct PhaseSanitizer { config: PhaseSanitizerConfig, statistics: SanitizationStatistics, } impl PhaseSanitizer { /// Complete sanitization pipeline (all four stages) pub fn sanitize_phase( &mut self, phase_data: &Array2, ) -> Result, PhaseSanitizationError>; } ``` ### FresnelBreathingEstimator Physics-based breathing detection using Fresnel zone geometry. ```rust 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; } ``` --- ## Context Map ``` +--------------------------------------------------------------+ | 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) --- ## Anti-Corruption Layers ### Hardware ACL (Upstream) 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). ```rust /// 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; } pub enum HardwareType { Esp32S3, Intel5300, AtherosAr9580, Simulation, } ``` ### Neural Network ACL (Downstream) 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. ```rust /// 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 { spec.data.mapv(|v| v as f32) } /// Convert BVP to domain-independent velocity tensor pub fn bvp_to_tensor(bvp: &BodyVelocityProfile) -> Array2 { 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, ) -> Result, SelectionError> { let extracted = extract_selected(data, selection)?; Ok(extracted.mapv(|v| v as f32)) } } ``` ### MAT ACL (Downstream) Adapts motion analysis outputs for the Mass Casualty Assessment Tool, translating domain-generic motion scores and breathing estimates into disaster-context vital signs. ```rust /// 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), } } } ```