mirror of
https://github.com/ruvnet/RuView
synced 2026-07-24 17:43:20 +00:00
fix(occupancy): bound eigenvalue person-count to single-link max — #894
field_bridge::occupancy_or_fallback returned FieldModel::estimate_occupancy
unbounded (internal ceiling 10), while the perturbation fallback below it
and score_to_person_count both cap at 3 ("1-3 for single ESP32"). On noisy
or under-calibrated CSI the eigenvalue count inflated → "10 persons when 1
present" (#894, seen when --model fails to load → heuristic mode). Bound the
eigenvalue path to a shared MAX_SINGLE_LINK_OCCUPANCY const (3) so every
single-link estimator agrees. Genuine higher counts come from the
multistatic fusion path. Build clean, field_bridge tests pass.
This commit is contained in:
@@ -21,6 +21,15 @@ const ENERGY_THRESH_2: f64 = 12.0;
|
||||
/// Perturbation energy threshold for detecting a third person.
|
||||
const ENERGY_THRESH_3: f64 = 25.0;
|
||||
|
||||
/// Maximum occupancy a single ESP32 link can plausibly resolve (#894).
|
||||
/// The score heuristic (`score_to_person_count`) and the perturbation-energy
|
||||
/// fallback below both cap here; the eigenvalue path is bounded to match,
|
||||
/// rather than leaking its internal `min(10)` ceiling on noisy / under-
|
||||
/// calibrated CSI (the "10 persons reported when 1 present" symptom).
|
||||
/// Resolving more than this from one link's subcarrier covariance is not
|
||||
/// reliable — genuine higher counts come from the multistatic fusion path.
|
||||
const MAX_SINGLE_LINK_OCCUPANCY: usize = 3;
|
||||
|
||||
/// Create a FieldModelConfig for single-link mode (one ESP32 node = one link).
|
||||
/// This avoids the DimensionMismatch error when feeding single-frame observations.
|
||||
pub fn single_link_config() -> FieldModelConfig {
|
||||
@@ -55,9 +64,15 @@ pub fn occupancy_or_fallback(
|
||||
return score_to_person_count(smoothed_score, prev_count);
|
||||
}
|
||||
|
||||
// Try eigenvalue-based occupancy first (best accuracy).
|
||||
// Try eigenvalue-based occupancy first (best accuracy). Bound it to
|
||||
// the same single-link maximum the sibling estimators use — the
|
||||
// perturbation fallback below and score_to_person_count both cap at
|
||||
// MAX_SINGLE_LINK_OCCUPANCY. Without this, estimate_occupancy's
|
||||
// internal min(10) ceiling leaks up to 10 persons on noisy / under-
|
||||
// calibrated CSI (#894), while every other path on the same data
|
||||
// would report ≤3.
|
||||
if let Ok(count) = field.estimate_occupancy(&frames) {
|
||||
return count;
|
||||
return count.min(MAX_SINGLE_LINK_OCCUPANCY);
|
||||
} // else fall through to perturbation energy
|
||||
|
||||
// Fallback: perturbation energy thresholds.
|
||||
|
||||
Reference in New Issue
Block a user