nv_perception/
artifact.rs1use crate::detection::DetectionSet;
4use crate::scene::SceneFeature;
5use crate::signal::DerivedSignal;
6use crate::track::Track;
7use nv_core::TypedMetadata;
8
9#[derive(Clone, Debug, Default)]
36pub struct PerceptionArtifacts {
37 pub detections: DetectionSet,
39 pub tracks: Vec<Track>,
46 pub tracks_authoritative: bool,
53 pub signals: Vec<DerivedSignal>,
55 pub scene_features: Vec<SceneFeature>,
57 pub stage_artifacts: TypedMetadata,
64}
65
66impl PerceptionArtifacts {
67 #[must_use]
69 pub fn empty() -> Self {
70 Self::default()
71 }
72
73 pub fn merge(&mut self, output: super::StageOutput) {
77 if let Some(detections) = output.detections {
78 self.detections = detections;
79 }
80 if let Some(tracks) = output.tracks {
81 self.tracks = tracks;
82 self.tracks_authoritative = true;
83 }
84 self.signals.extend(output.signals);
85 self.scene_features.extend(output.scene_features);
86 self.stage_artifacts.merge(output.artifacts);
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::detection::Detection;
94 use crate::signal::{DerivedSignal, SignalValue};
95 use crate::stage::StageOutput;
96 use crate::track::Track;
97 use nv_core::id::{DetectionId, TrackId};
98 use nv_core::{BBox, MonotonicTs, TypedMetadata};
99
100 fn make_detection(id: u64) -> Detection {
101 Detection::builder(DetectionId::new(id), 0, 0.9, BBox::new(0.1, 0.2, 0.3, 0.4)).build()
102 }
103
104 fn make_track(id: u64) -> Track {
105 use crate::track::{TrackObservation, TrackState};
106 let obs = TrackObservation::new(
107 MonotonicTs::from_nanos(0),
108 BBox::new(0.1, 0.2, 0.3, 0.4),
109 0.9,
110 TrackState::Confirmed,
111 None,
112 );
113 Track::new(TrackId::new(id), 0, TrackState::Confirmed, obs)
114 }
115
116 fn make_signal(name: &'static str) -> DerivedSignal {
117 DerivedSignal {
118 name,
119 value: SignalValue::Scalar(1.0),
120 ts: MonotonicTs::from_nanos(0),
121 }
122 }
123
124 #[test]
125 fn merge_empty_output_is_noop() {
126 let mut arts = PerceptionArtifacts::empty();
127 arts.merge(StageOutput::empty());
128
129 assert!(arts.detections.is_empty());
130 assert!(arts.tracks.is_empty());
131 assert!(!arts.tracks_authoritative);
132 assert!(arts.signals.is_empty());
133 assert!(arts.scene_features.is_empty());
134 }
135
136 #[test]
137 fn merge_detections_replace() {
138 let mut arts = PerceptionArtifacts::empty();
139
140 let dets1 = DetectionSet::from(vec![make_detection(1), make_detection(2)]);
142 arts.merge(StageOutput::with_detections(dets1));
143 assert_eq!(arts.detections.len(), 2);
144
145 let dets2 = DetectionSet::from(vec![make_detection(3)]);
147 arts.merge(StageOutput::with_detections(dets2));
148 assert_eq!(arts.detections.len(), 1);
149 assert_eq!(arts.detections.detections[0].id, DetectionId::new(3));
150 }
151
152 #[test]
153 fn merge_none_detections_preserves_existing() {
154 let mut arts = PerceptionArtifacts::empty();
155
156 let dets = DetectionSet::from(vec![make_detection(1)]);
157 arts.merge(StageOutput::with_detections(dets));
158 assert_eq!(arts.detections.len(), 1);
159
160 arts.merge(StageOutput::empty());
162 assert_eq!(arts.detections.len(), 1);
163 }
164
165 #[test]
166 fn merge_tracks_replace_and_set_authoritative() {
167 let mut arts = PerceptionArtifacts::empty();
168 assert!(!arts.tracks_authoritative);
169
170 let tracks = vec![make_track(1), make_track(2)];
171 arts.merge(StageOutput::with_tracks(tracks));
172 assert_eq!(arts.tracks.len(), 2);
173 assert!(arts.tracks_authoritative);
174
175 let tracks2 = vec![make_track(3)];
177 arts.merge(StageOutput::with_tracks(tracks2));
178 assert_eq!(arts.tracks.len(), 1);
179 assert_eq!(arts.tracks[0].id, TrackId::new(3));
180 }
181
182 #[test]
183 fn merge_signals_append() {
184 let mut arts = PerceptionArtifacts::empty();
185
186 arts.merge(StageOutput::with_signal(make_signal("sig_a")));
187 assert_eq!(arts.signals.len(), 1);
188
189 arts.merge(StageOutput::with_signal(make_signal("sig_b")));
190 assert_eq!(arts.signals.len(), 2);
191 assert_eq!(arts.signals[0].name, "sig_a");
192 assert_eq!(arts.signals[1].name, "sig_b");
193 }
194
195 #[test]
196 fn merge_stage_artifacts_last_writer_wins() {
197 #[derive(Clone, Debug, PartialEq)]
198 struct MyData(u32);
199
200 let mut arts = PerceptionArtifacts::empty();
201
202 let mut meta1 = TypedMetadata::new();
203 meta1.insert(MyData(1));
204 arts.merge(StageOutput {
205 artifacts: meta1,
206 ..StageOutput::default()
207 });
208 assert_eq!(arts.stage_artifacts.get::<MyData>(), Some(&MyData(1)));
209
210 let mut meta2 = TypedMetadata::new();
212 meta2.insert(MyData(42));
213 arts.merge(StageOutput {
214 artifacts: meta2,
215 ..StageOutput::default()
216 });
217 assert_eq!(arts.stage_artifacts.get::<MyData>(), Some(&MyData(42)));
218 }
219}