Files
autopilot/crates/movement_detector/src/lib.rs
T
Oleksandr Bezdieniezhnykh db844db232 [AZ-662] [AZ-669] Implement ego-motion estimator and primitive graph
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>
2026-05-20 19:00:39 +03:00

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
));
}
}