mirror of
https://github.com/azaion/autopilot.git
synced 2026-06-21 10:21:10 +00:00
db844db232
AZ-662: movement_detector ego-motion - Add opencv + petgraph to workspace dependencies - internal/zoom_bands: per-band telemetry skew tolerances - internal/telemetry_sync: skew gate (check_skew) - internal/optical_flow: frame→gray, degenerate detection, LK sparse flow + RANSAC homography estimation - internal/ego_motion: EgoMotionEstimator + atomic counters AZ-669: semantic_analyzer primitive graph - internal/primitive_graph: NodeType, PrimitiveNode, PrimitiveGraph, PrimitiveGraphBuilder with proximity-adjacency + BFS connectivity check - internal/scoring/freshness: FreshnessScorer (Laplacian variance, texture stddev, undisturbed-surroundings heuristic) - All ACs covered by unit tests (AC-1/2/3 per task) Note: native OpenCV not installed on macOS; authoritative test is cargo test --workspace on Jetson (ssh jetson-e2e). Co-authored-by: Cursor <cursoragent@cursor.com>
84 lines
2.1 KiB
Rust
84 lines
2.1 KiB
Rust
//! `movement_detector` — ego-motion compensated residual-motion clustering.
|
|
//!
|
|
//! AZ-662: ego-motion estimator + telemetry-skew gate (this batch).
|
|
//! AZ-663: residual clustering + candidate emission (next batch).
|
|
//! AZ-664: FP cap + Q14 learned-CV fallback.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use tokio::sync::broadcast;
|
|
|
|
use shared::health::{ComponentHealth, HealthLevel};
|
|
use shared::models::movement::MovementCandidate;
|
|
|
|
pub(crate) mod internal;
|
|
|
|
use internal::ego_motion::EgoMotionCounters;
|
|
|
|
const NAME: &str = "movement_detector";
|
|
|
|
pub struct MovementDetector {
|
|
tx: broadcast::Sender<MovementCandidate>,
|
|
counters: Arc<EgoMotionCounters>,
|
|
}
|
|
|
|
impl MovementDetector {
|
|
pub fn new(channel_capacity: usize) -> Self {
|
|
let (tx, _rx) = broadcast::channel(channel_capacity);
|
|
Self { tx, counters: Arc::new(EgoMotionCounters::new()) }
|
|
}
|
|
|
|
pub fn handle(&self) -> MovementDetectorHandle {
|
|
MovementDetectorHandle {
|
|
tx: self.tx.clone(),
|
|
counters: Arc::clone(&self.counters),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct MovementDetectorHandle {
|
|
tx: broadcast::Sender<MovementCandidate>,
|
|
counters: Arc<EgoMotionCounters>,
|
|
}
|
|
|
|
impl MovementDetectorHandle {
|
|
pub fn candidates(&self) -> broadcast::Receiver<MovementCandidate> {
|
|
self.tx.subscribe()
|
|
}
|
|
|
|
pub fn health(&self) -> ComponentHealth {
|
|
let skew_drops = self.counters.skew_drops_total();
|
|
let degenerate = self.counters.degenerate_total();
|
|
|
|
if skew_drops > 0 || degenerate > 0 {
|
|
ComponentHealth::yellow(
|
|
NAME,
|
|
format!(
|
|
"skew_drops_total={skew_drops} optical_flow_degenerate_total={degenerate}"
|
|
),
|
|
)
|
|
} else {
|
|
ComponentHealth {
|
|
level: HealthLevel::Disabled,
|
|
component: NAME,
|
|
detail: None,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn it_compiles() {
|
|
let h = MovementDetector::new(16).handle();
|
|
assert!(matches!(
|
|
h.health().level,
|
|
HealthLevel::Disabled | HealthLevel::Yellow
|
|
));
|
|
}
|
|
}
|