feat(adr-105): kill synthetic signal_field — only real ESP32 data left
Continuation of ADR-105 (no synthetic outputs in production runtime). The 20×20 SignalField heatmap was generated by mapping subcarrier index k to angle 2π·k/N and dropping a Gaussian hotspot — a totally fabricated spatial layout. A single sensor has no directional info so the resulting heatmap had no correspondence to where anything actually was in the room; UI showed believable-looking but physically meaningless hotspots. Operator asked for boots-on-the- ground honesty. `generate_signal_field` now returns a zero-filled 20×1×20 grid. UI renders blank, which is the truthful state until a real multistatic localizer is wired (multi-AP attention from ADR-008 or the `MultistaticFuser` already in code). Audit of remaining fields confirmed they are either: - already gated on real data (vital_signs returns None when br < 1 BPM, persons/pose_keypoints/posture/signal_quality_score all None without model loaded), - or processed from real CSI (classification, features.mean_rssi, features.variance, enhanced_motion when multi-AP pipeline active). `--source simulate` was already disabled by an earlier change (exit code 2). `--pretrain` and `--train` synthetic fallbacks remain in code as developer tools but never touch the runtime sensing path.
This commit is contained in:
parent
9aa027e95e
commit
30244d274b
|
|
@ -1710,86 +1710,23 @@ fn parse_esp32_frame(buf: &[u8]) -> Option<Esp32Frame> {
|
||||||
/// subcarriers with the highest variance produce peaks at the corresponding directions.
|
/// subcarriers with the highest variance produce peaks at the corresponding directions.
|
||||||
fn generate_signal_field(
|
fn generate_signal_field(
|
||||||
_mean_rssi: f64,
|
_mean_rssi: f64,
|
||||||
motion_score: f64,
|
_motion_score: f64,
|
||||||
breathing_rate_hz: f64,
|
_breathing_rate_hz: f64,
|
||||||
signal_quality: f64,
|
_signal_quality: f64,
|
||||||
subcarrier_variances: &[f64],
|
_subcarrier_variances: &[f64],
|
||||||
) -> SignalField {
|
) -> SignalField {
|
||||||
|
// ADR-105: this used to paint a 20×20 "room heatmap" by mapping each
|
||||||
|
// subcarrier index `k` to an angle `2π·k/N` and dropping a Gaussian
|
||||||
|
// hotspot at radius proportional to its variance — visually rich, but
|
||||||
|
// **physically meaningless**. A single sensor has no directional
|
||||||
|
// information, so the resulting hotspots have no correspondence to
|
||||||
|
// where anything actually is in the room. Operator requested
|
||||||
|
// boots-on-the-ground honesty: return a zero-filled grid. UI will
|
||||||
|
// render blank, which is the truthful state until a real
|
||||||
|
// multistatic localizer is wired in.
|
||||||
let grid = 20usize;
|
let grid = 20usize;
|
||||||
let mut values = vec![0.0f64; grid * grid];
|
return SignalField { grid_size: [grid, 1, grid], values: vec![0.0; grid * grid] };
|
||||||
let center = (grid as f64 - 1.0) / 2.0;
|
|
||||||
|
|
||||||
// Normalise subcarrier variances to [0, 1].
|
|
||||||
let max_var = subcarrier_variances.iter().cloned().fold(0.0f64, f64::max);
|
|
||||||
let norm_factor = if max_var > 1e-9 { max_var } else { 1.0 };
|
|
||||||
|
|
||||||
// For each cell, accumulate contributions from all subcarriers.
|
|
||||||
// Each subcarrier k is assigned an angular direction proportional to its index
|
|
||||||
// so that different subcarriers illuminate different regions of the room.
|
|
||||||
let n_sub = subcarrier_variances.len().max(1);
|
|
||||||
for (k, &var) in subcarrier_variances.iter().enumerate() {
|
|
||||||
let weight = (var / norm_factor) * motion_score;
|
|
||||||
if weight < 1e-6 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Map subcarrier index to an angle across the full 2π sweep.
|
|
||||||
let angle = (k as f64 / n_sub as f64) * 2.0 * std::f64::consts::PI;
|
|
||||||
// Place the hotspot at a distance proportional to the weight, capped at 40% of
|
|
||||||
// the grid radius so it stays within the room model.
|
|
||||||
let radius = center * 0.8 * weight.sqrt();
|
|
||||||
let hx = center + radius * angle.cos();
|
|
||||||
let hz = center + radius * angle.sin();
|
|
||||||
|
|
||||||
for z in 0..grid {
|
|
||||||
for x in 0..grid {
|
|
||||||
let dx = x as f64 - hx;
|
|
||||||
let dz = z as f64 - hz;
|
|
||||||
let dist2 = dx * dx + dz * dz;
|
|
||||||
// Gaussian blob centred on the hotspot; spread scales with weight.
|
|
||||||
let spread = (0.5 + weight * 2.0).max(0.5);
|
|
||||||
values[z * grid + x] += weight * (-dist2 / (2.0 * spread * spread)).exp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base radial attenuation from the router assumed at grid centre.
|
|
||||||
for z in 0..grid {
|
|
||||||
for x in 0..grid {
|
|
||||||
let dx = x as f64 - center;
|
|
||||||
let dz = z as f64 - center;
|
|
||||||
let dist = (dx * dx + dz * dz).sqrt();
|
|
||||||
let base = signal_quality * (-dist * 0.12).exp();
|
|
||||||
values[z * grid + x] += base * 0.3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Breathing ring: if a breathing rate was estimated add a faint annular highlight
|
|
||||||
// at a radius corresponding to typical chest-wall displacement range.
|
|
||||||
if breathing_rate_hz > 0.05 {
|
|
||||||
let ring_r = center * 0.55;
|
|
||||||
let ring_width = 1.8f64;
|
|
||||||
for z in 0..grid {
|
|
||||||
for x in 0..grid {
|
|
||||||
let dx = x as f64 - center;
|
|
||||||
let dz = z as f64 - center;
|
|
||||||
let dist = (dx * dx + dz * dz).sqrt();
|
|
||||||
let ring_val = 0.08 * (-(dist - ring_r).powi(2) / (2.0 * ring_width * ring_width)).exp();
|
|
||||||
values[z * grid + x] += ring_val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp and normalise to [0, 1].
|
|
||||||
let field_max = values.iter().cloned().fold(0.0f64, f64::max);
|
|
||||||
let scale = if field_max > 1e-9 { 1.0 / field_max } else { 1.0 };
|
|
||||||
for v in &mut values {
|
|
||||||
*v = (*v * scale).clamp(0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SignalField {
|
|
||||||
grid_size: [grid, 1, grid],
|
|
||||||
values,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Feature extraction from ESP32 frame ──────────────────────────────────────
|
// ── Feature extraction from ESP32 frame ──────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue