mirror of
https://github.com/azaion/autopilot.git
synced 2026-06-22 12:11:09 +00:00
[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>
This commit is contained in:
@@ -1,30 +1,37 @@
|
||||
//! `movement_detector` — ego-motion compensated residual-motion clustering.
|
||||
//!
|
||||
//! Real implementation lands in:
|
||||
//! - AZ-662 `movement_detector_ego_motion`
|
||||
//! - AZ-663 `movement_detector_clustering_and_emission`
|
||||
//! - AZ-664 `movement_detector_fp_cap_and_q14_fallback`
|
||||
//! 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;
|
||||
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 }
|
||||
Self { tx, counters: Arc::new(EgoMotionCounters::new()) }
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> MovementDetectorHandle {
|
||||
MovementDetectorHandle {
|
||||
tx: self.tx.clone(),
|
||||
counters: Arc::clone(&self.counters),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +39,7 @@ impl MovementDetector {
|
||||
#[derive(Clone)]
|
||||
pub struct MovementDetectorHandle {
|
||||
tx: broadcast::Sender<MovementCandidate>,
|
||||
counters: Arc<EgoMotionCounters>,
|
||||
}
|
||||
|
||||
impl MovementDetectorHandle {
|
||||
@@ -40,7 +48,23 @@ impl MovementDetectorHandle {
|
||||
}
|
||||
|
||||
pub fn health(&self) -> ComponentHealth {
|
||||
ComponentHealth::disabled(NAME)
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +75,9 @@ mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {
|
||||
let h = MovementDetector::new(16).handle();
|
||||
assert_eq!(h.health().level, shared::health::HealthLevel::Disabled);
|
||||
assert!(matches!(
|
||||
h.health().level,
|
||||
HealthLevel::Disabled | HealthLevel::Yellow
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user