Skip to main content

nv_view/
provider.rs

1//! [`ViewStateProvider`] trait and motion report types.
2
3use crate::camera_motion::CameraMotionState;
4use crate::ptz::PtzTelemetry;
5use crate::transform::GlobalTransformEstimate;
6use crate::view_state::ViewState;
7use nv_core::MonotonicTs;
8use nv_frame::FrameEnvelope;
9
10/// What the view system receives from the provider each frame.
11///
12/// The provider fills in whichever fields are available. The view system
13/// derives [`MotionSource`](crate::MotionSource) from the field contents
14/// (see the architecture spec §9 for derivation rules).
15#[derive(Clone, Debug, Default)]
16pub struct MotionReport {
17    /// PTZ telemetry, if available from the camera's control interface.
18    pub ptz: Option<PtzTelemetry>,
19
20    /// Frame-to-frame transform estimate (from optical flow, homography, etc.).
21    ///
22    /// This is the primary egomotion signal when PTZ telemetry is absent.
23    pub frame_transform: Option<GlobalTransformEstimate>,
24
25    /// Optional hint about whether the camera is moving.
26    ///
27    /// If `None`, the view system infers motion from the `ptz` deltas
28    /// or `frame_transform` displacement magnitude.
29    pub motion_hint: Option<CameraMotionState>,
30
31    /// Discrete PTZ control events received since the previous frame.
32    ///
33    /// Empty when no PTZ command stream is available. Providers that
34    /// monitor an ONVIF event channel or serial command bus populate
35    /// this with the events that arrived between frames.
36    ///
37    /// The epoch policy considers these events alongside telemetry and
38    /// inferred motion to make segmentation decisions.
39    pub ptz_events: Vec<crate::ptz::PtzEvent>,
40}
41
42/// Context given to [`ViewStateProvider::poll`].
43pub struct MotionPollContext<'a> {
44    /// Monotonic timestamp of the current frame.
45    pub ts: MonotonicTs,
46    /// The current video frame (available for egomotion providers that
47    /// need pixel data for optical flow or feature matching).
48    pub frame: &'a FrameEnvelope,
49    /// The previous frame's view state.
50    pub previous_view: &'a ViewState,
51}
52
53/// User-implementable trait: provides camera motion information each frame.
54///
55/// Required when [`CameraMode::Observed`](nv_core::CameraMode::Observed) is configured.
56///
57/// ## Implementation categories
58///
59/// - **Telemetry providers**: poll ONVIF/serial, populate `ptz` field.
60/// - **Egomotion providers**: run optical flow or feature matching on the frame,
61///   populate `frame_transform` field.
62/// - **External providers**: receive transforms or hints from an outside system.
63///
64/// ## Performance
65///
66/// `poll()` is called on the stage thread, synchronously, **before** any stage
67/// executes. Its latency adds directly to every frame's pipeline latency.
68///
69/// - Telemetry providers should pre-fetch asynchronously and return cached data.
70/// - Egomotion providers run computation directly (typically 1–5ms).
71/// - External providers should return pre-computed data.
72pub trait ViewStateProvider: Send + Sync + 'static {
73    /// Called once per frame. Return the current motion report.
74    fn poll(&self, ctx: &MotionPollContext<'_>) -> MotionReport;
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn motion_report_default_is_empty() {
83        let report = MotionReport::default();
84        assert!(report.ptz.is_none());
85        assert!(report.frame_transform.is_none());
86        assert!(report.motion_hint.is_none());
87        assert!(report.ptz_events.is_empty());
88    }
89
90    #[test]
91    fn motion_report_with_ptz() {
92        use crate::ptz::PtzTelemetry;
93        let report = MotionReport {
94            ptz: Some(PtzTelemetry {
95                pan: 90.0,
96                tilt: 0.0,
97                zoom: 0.5,
98                ts: MonotonicTs::from_nanos(100),
99            }),
100            ..Default::default()
101        };
102        assert!(report.ptz.is_some());
103        assert!(report.frame_transform.is_none());
104    }
105}