Files
wifi-densepose/rust-port/wifi-densepose-rs/crates/wifi-densepose-mat/src/integration/neural_adapter.rs
Claude a17b630c02 feat: Add wifi-densepose-mat disaster detection module
Implements WiFi-Mat (Mass Casualty Assessment Tool) for detecting and
localizing survivors trapped in rubble, earthquakes, and natural disasters.

Architecture:
- Domain-Driven Design with bounded contexts (Detection, Localization, Alerting)
- Modular Rust crate integrating with existing wifi-densepose-* crates
- Event-driven architecture for audit trails and distributed deployments

Features:
- Breathing pattern detection from CSI amplitude variations
- Heartbeat detection using micro-Doppler analysis
- Movement classification (gross, fine, tremor, periodic)
- START protocol-compatible triage classification
- 3D position estimation via triangulation and depth estimation
- Real-time alert generation with priority escalation

Documentation:
- ADR-001: Architecture Decision Record for wifi-Mat
- DDD domain model specification
2026-01-13 17:24:50 +00:00

294 lines
8.6 KiB
Rust

//! Adapter for wifi-densepose-nn crate (neural network inference).
use super::AdapterError;
use crate::domain::{BreathingPattern, BreathingType, HeartbeatSignature, SignalStrength};
use super::signal_adapter::VitalFeatures;
/// Adapter for neural network-based vital signs detection
pub struct NeuralAdapter {
/// Whether to use GPU acceleration
use_gpu: bool,
/// Confidence threshold for valid detections
confidence_threshold: f32,
/// Model loaded status
models_loaded: bool,
}
impl NeuralAdapter {
/// Create a new neural adapter
pub fn new(use_gpu: bool) -> Self {
Self {
use_gpu,
confidence_threshold: 0.5,
models_loaded: false,
}
}
/// Create with default settings (CPU)
pub fn with_defaults() -> Self {
Self::new(false)
}
/// Load neural network models
pub fn load_models(&mut self, _model_path: &str) -> Result<(), AdapterError> {
// In production, this would load ONNX models using wifi-densepose-nn
// For now, mark as loaded for simulation
self.models_loaded = true;
Ok(())
}
/// Classify breathing pattern using neural network
pub fn classify_breathing(
&self,
features: &VitalFeatures,
) -> Result<Option<BreathingPattern>, AdapterError> {
if !self.models_loaded {
// Fall back to rule-based classification
return Ok(self.classify_breathing_rules(features));
}
// In production, this would run ONNX inference
// For now, use rule-based approach
Ok(self.classify_breathing_rules(features))
}
/// Classify heartbeat using neural network
pub fn classify_heartbeat(
&self,
features: &VitalFeatures,
) -> Result<Option<HeartbeatSignature>, AdapterError> {
if !self.models_loaded {
return Ok(self.classify_heartbeat_rules(features));
}
// In production, run ONNX inference
Ok(self.classify_heartbeat_rules(features))
}
/// Combined vital signs classification
pub fn classify_vitals(
&self,
features: &VitalFeatures,
) -> Result<VitalsClassification, AdapterError> {
let breathing = self.classify_breathing(features)?;
let heartbeat = self.classify_heartbeat(features)?;
// Calculate overall confidence
let confidence = self.calculate_confidence(
&breathing,
&heartbeat,
features.signal_quality,
);
Ok(VitalsClassification {
breathing,
heartbeat,
confidence,
signal_quality: features.signal_quality,
})
}
/// Rule-based breathing classification (fallback)
fn classify_breathing_rules(&self, features: &VitalFeatures) -> Option<BreathingPattern> {
if features.breathing_features.len() < 3 {
return None;
}
let peak_freq = features.breathing_features[0];
let power_ratio = features.breathing_features.get(1).copied().unwrap_or(0.0);
let band_ratio = features.breathing_features.get(2).copied().unwrap_or(0.0);
// Check if there's significant energy in breathing band
if power_ratio < 0.05 || band_ratio < 0.1 {
return None;
}
let rate_bpm = (peak_freq * 60.0) as f32;
// Validate rate
if rate_bpm < 4.0 || rate_bpm > 60.0 {
return None;
}
let pattern_type = if rate_bpm < 6.0 {
BreathingType::Agonal
} else if rate_bpm < 10.0 {
BreathingType::Shallow
} else if rate_bpm > 30.0 {
BreathingType::Labored
} else if band_ratio < 0.3 {
BreathingType::Irregular
} else {
BreathingType::Normal
};
Some(BreathingPattern {
rate_bpm,
amplitude: power_ratio as f32,
regularity: band_ratio as f32,
pattern_type,
})
}
/// Rule-based heartbeat classification (fallback)
fn classify_heartbeat_rules(&self, features: &VitalFeatures) -> Option<HeartbeatSignature> {
if features.heartbeat_features.len() < 3 {
return None;
}
let peak_freq = features.heartbeat_features[0];
let power_ratio = features.heartbeat_features.get(1).copied().unwrap_or(0.0);
let band_ratio = features.heartbeat_features.get(2).copied().unwrap_or(0.0);
// Heartbeat detection requires stronger signal
if power_ratio < 0.03 || band_ratio < 0.08 {
return None;
}
let rate_bpm = (peak_freq * 60.0) as f32;
// Validate rate (30-200 BPM)
if rate_bpm < 30.0 || rate_bpm > 200.0 {
return None;
}
let strength = if power_ratio > 0.15 {
SignalStrength::Strong
} else if power_ratio > 0.08 {
SignalStrength::Moderate
} else if power_ratio > 0.04 {
SignalStrength::Weak
} else {
SignalStrength::VeryWeak
};
Some(HeartbeatSignature {
rate_bpm,
variability: band_ratio as f32 * 0.5,
strength,
})
}
/// Calculate overall confidence from detections
fn calculate_confidence(
&self,
breathing: &Option<BreathingPattern>,
heartbeat: &Option<HeartbeatSignature>,
signal_quality: f64,
) -> f32 {
let mut confidence = signal_quality as f32 * 0.3;
if let Some(b) = breathing {
confidence += 0.4 * b.confidence() as f32;
}
if let Some(h) = heartbeat {
confidence += 0.3 * h.confidence() as f32;
}
confidence.clamp(0.0, 1.0)
}
}
impl Default for NeuralAdapter {
fn default() -> Self {
Self::with_defaults()
}
}
/// Result of neural network vital signs classification
#[derive(Debug, Clone)]
pub struct VitalsClassification {
/// Detected breathing pattern
pub breathing: Option<BreathingPattern>,
/// Detected heartbeat
pub heartbeat: Option<HeartbeatSignature>,
/// Overall classification confidence
pub confidence: f32,
/// Signal quality indicator
pub signal_quality: f64,
}
impl VitalsClassification {
/// Check if any vital signs were detected
pub fn has_vitals(&self) -> bool {
self.breathing.is_some() || self.heartbeat.is_some()
}
/// Check if detection confidence is sufficient
pub fn is_confident(&self, threshold: f32) -> bool {
self.confidence >= threshold
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_good_features() -> VitalFeatures {
VitalFeatures {
breathing_features: vec![0.25, 0.2, 0.4], // 15 BPM, good signal
heartbeat_features: vec![1.2, 0.1, 0.15], // 72 BPM, moderate signal
movement_features: vec![0.1, 0.05, 0.01],
signal_quality: 0.8,
}
}
fn create_weak_features() -> VitalFeatures {
VitalFeatures {
breathing_features: vec![0.25, 0.02, 0.05], // Weak
heartbeat_features: vec![1.2, 0.01, 0.02], // Very weak
movement_features: vec![0.01, 0.005, 0.001],
signal_quality: 0.3,
}
}
#[test]
fn test_classify_breathing() {
let adapter = NeuralAdapter::with_defaults();
let features = create_good_features();
let result = adapter.classify_breathing(&features);
assert!(result.is_ok());
assert!(result.unwrap().is_some());
}
#[test]
fn test_weak_signal_no_detection() {
let adapter = NeuralAdapter::with_defaults();
let features = create_weak_features();
let result = adapter.classify_breathing(&features);
assert!(result.is_ok());
// Weak signals may or may not be detected depending on thresholds
}
#[test]
fn test_classify_vitals() {
let adapter = NeuralAdapter::with_defaults();
let features = create_good_features();
let result = adapter.classify_vitals(&features);
assert!(result.is_ok());
let classification = result.unwrap();
assert!(classification.has_vitals());
assert!(classification.confidence > 0.3);
}
#[test]
fn test_confidence_calculation() {
let adapter = NeuralAdapter::with_defaults();
let breathing = Some(BreathingPattern {
rate_bpm: 16.0,
amplitude: 0.8,
regularity: 0.9,
pattern_type: BreathingType::Normal,
});
let confidence = adapter.calculate_confidence(&breathing, &None, 0.8);
assert!(confidence > 0.5);
}
}