mirror of
https://github.com/ruvnet/RuView
synced 2026-07-28 18:21:42 +00:00
fix(vitals): HeartRateExtractor silently dropped every frame with phases=[]
HeartRateExtractor::extract() computed n = residuals.len().min(n_subcarriers).min(phases.len()), so an empty (or short) phases slice truncated n to 0 and every single frame was rejected via the n == 0 guard -- silently, with no way to distinguish it from any other None. BreathingExtractor documents weights=[] as "equal weighting"; HeartRateExtractor's phases=[] must mean the same thing (no per-subcarrier coherence data available), not "refuse all input". Fix: n now derives from residuals/n_subcarriers only. compute_phase_coherence_signal looks up phases via .get() and treats any missing entry as full coherence (weight 1.0), mirroring breathing::fuse_weighted_residuals's uniform-weight fallback for a missing/partial weights slice. Updated the now-inaccurate PyO3 docstrings that claimed phases=[] was invalid. Fixes #1423 Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -252,8 +252,9 @@ impl PyBreathingExtractor {
|
||||
/// hr = HeartRateExtractor.esp32_default() # 56 subcarriers, 100 Hz, 15s window
|
||||
///
|
||||
/// # Feed residuals and matching unwrapped phases from your preprocessor.
|
||||
/// # Unlike BreathingExtractor weights, phases=[] is invalid for heart-rate
|
||||
/// # extraction because the Rust core requires phase data for each subcarrier.
|
||||
/// # Like BreathingExtractor's weights, phases=[] means "no per-subcarrier
|
||||
/// # coherence information available" and falls back to equal weighting
|
||||
/// # across all subcarriers -- it does NOT silently drop every frame.
|
||||
/// est = hr.extract(residuals=[0.01, -0.02, …], phases=[0.0, 0.01, …])
|
||||
/// if est is not None:
|
||||
/// print(est.value_bpm, est.confidence)
|
||||
@@ -281,9 +282,11 @@ impl PyHeartRateExtractor {
|
||||
}
|
||||
|
||||
/// Extract heart rate from per-subcarrier residuals and matching
|
||||
/// per-subcarrier unwrapped phases (radians). Empty phases are invalid
|
||||
/// and return `None` because the Rust extractor requires phase data.
|
||||
/// GIL released during DSP.
|
||||
/// per-subcarrier unwrapped phases (radians). A short or empty `phases`
|
||||
/// slice falls back to equal weighting for any subcarrier missing phase
|
||||
/// data (issue #1423) -- it does not truncate the number of subcarriers
|
||||
/// fused, and does not silently return `None` for every frame. GIL
|
||||
/// released during DSP.
|
||||
fn extract(
|
||||
&mut self,
|
||||
py: Python<'_>,
|
||||
|
||||
Reference in New Issue
Block a user