1use crate::core::{Detection, Detections};
15use crate::tracker::assignment::assign_by_iou;
16use crate::tracker::kalman::KalmanBoxFilter;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19enum TrackState {
20 Tracked,
23 Lost,
27}
28
29struct Track {
30 id: usize,
31 filter: KalmanBoxFilter,
32 state: TrackState,
33 time_since_update: usize,
34}
35
36pub struct ByteTracker {
49 tracks: Vec<Track>,
50 next_id: usize,
51 track_activation_threshold: f32,
54 high_confidence_matching_threshold: f32,
56 low_confidence_matching_threshold: f32,
61 lost_track_buffer: usize,
64}
65
66impl ByteTracker {
67 pub fn new(
76 track_activation_threshold: f32,
77 lost_track_buffer: usize,
78 high_confidence_matching_threshold: f32,
79 low_confidence_matching_threshold: f32,
80 ) -> Self {
81 Self {
82 tracks: Vec::new(),
83 next_id: 1,
84 track_activation_threshold,
85 high_confidence_matching_threshold,
86 low_confidence_matching_threshold,
87 lost_track_buffer,
88 }
89 }
90
91 pub fn active_tracks(&self) -> usize {
94 self.tracks.len()
95 }
96
97 pub fn update(&mut self, detections: &Detections) -> Detections {
104 let predicted_boxes: Vec<[f32; 4]> =
105 self.tracks.iter_mut().map(|t| t.filter.predict()).collect();
106
107 let (high_idx, low_idx): (Vec<usize>, Vec<usize>) = (0..detections.len())
108 .partition(|&i| detections.detections[i].confidence >= self.track_activation_threshold);
109 let high_boxes: Vec<[f32; 4]> = high_idx
110 .iter()
111 .map(|&i| detections.detections[i].bbox)
112 .collect();
113 let low_boxes: Vec<[f32; 4]> = low_idx
114 .iter()
115 .map(|&i| detections.detections[i].bbox)
116 .collect();
117
118 let mut track_matched = vec![false; self.tracks.len()];
119 let mut assigned_track_id: Vec<Option<usize>> = vec![None; detections.len()];
120
121 let stage1 = assign_by_iou(
123 &predicted_boxes,
124 &high_boxes,
125 self.high_confidence_matching_threshold,
126 );
127 for (k, matched_track) in stage1.iter().enumerate() {
128 if let Some(track_index) = matched_track {
129 let detection_index = high_idx[k];
130 let track = &mut self.tracks[*track_index];
131 track
132 .filter
133 .update(detections.detections[detection_index].bbox);
134 track.time_since_update = 0;
135 track.state = TrackState::Tracked;
136 assigned_track_id[detection_index] = Some(track.id);
137 track_matched[*track_index] = true;
138 }
139 }
140
141 let stage2_track_indices: Vec<usize> = (0..self.tracks.len())
144 .filter(|&i| !track_matched[i] && self.tracks[i].state == TrackState::Tracked)
145 .collect();
146 let stage2_track_boxes: Vec<[f32; 4]> = stage2_track_indices
147 .iter()
148 .map(|&i| predicted_boxes[i])
149 .collect();
150 let stage2 = assign_by_iou(
151 &stage2_track_boxes,
152 &low_boxes,
153 self.low_confidence_matching_threshold,
154 );
155 for (k, matched_track) in stage2.iter().enumerate() {
156 if let Some(local_index) = matched_track {
157 let track_index = stage2_track_indices[*local_index];
158 let detection_index = low_idx[k];
159 let track = &mut self.tracks[track_index];
160 track
161 .filter
162 .update(detections.detections[detection_index].bbox);
163 track.time_since_update = 0;
164 track.state = TrackState::Tracked;
165 assigned_track_id[detection_index] = Some(track.id);
166 track_matched[track_index] = true;
167 }
168 }
169
170 for (i, track) in self.tracks.iter_mut().enumerate() {
172 if !track_matched[i] {
173 track.time_since_update += 1;
174 track.state = TrackState::Lost;
175 }
176 }
177 self.tracks
178 .retain(|t| t.time_since_update <= self.lost_track_buffer);
179
180 for (k, matched_track) in stage1.iter().enumerate() {
183 if matched_track.is_none() {
184 let detection_index = high_idx[k];
185 let id = self.next_id;
186 self.next_id += 1;
187 let filter = KalmanBoxFilter::new(detections.detections[detection_index].bbox);
188 self.tracks.push(Track {
189 id,
190 filter,
191 state: TrackState::Tracked,
192 time_since_update: 0,
193 });
194 assigned_track_id[detection_index] = Some(id);
195 }
196 }
197
198 let output: Vec<Detection> = detections
199 .iter()
200 .enumerate()
201 .filter_map(|(i, d)| {
202 assigned_track_id[i].map(|id| {
203 let mut d = d.clone();
204 d.tracker_id = Some(id);
205 d
206 })
207 })
208 .collect();
209 Detections::new(output)
210 }
211}
212
213impl Default for ByteTracker {
214 fn default() -> Self {
219 Self::new(0.25, 30, 0.3, 0.2)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::core::Detection;
227
228 fn det(bbox: [f32; 4], confidence: f32) -> Detection {
229 Detection::new(bbox, confidence, 0)
230 }
231
232 #[test]
233 fn high_confidence_detection_spawns_a_track() {
234 let mut tracker = ByteTracker::default();
235 let detections = Detections::new(vec![det([0.0, 0.0, 10.0, 10.0], 0.9)]);
236 let out = tracker.update(&detections);
237 assert_eq!(out.len(), 1);
238 assert!(out.detections[0].tracker_id.is_some());
239 assert_eq!(tracker.active_tracks(), 1);
240 }
241
242 #[test]
243 fn low_confidence_unmatched_detection_is_dropped_and_spawns_nothing() {
244 let mut tracker = ByteTracker::default();
245 let detections = Detections::new(vec![det([0.0, 0.0, 10.0, 10.0], 0.1)]);
246 let out = tracker.update(&detections);
247 assert_eq!(out.len(), 0);
248 assert_eq!(tracker.active_tracks(), 0);
249 }
250
251 #[test]
252 fn matching_high_confidence_detection_keeps_same_id() {
253 let mut tracker = ByteTracker::default();
254 let first = Detections::new(vec![det([0.0, 0.0, 10.0, 10.0], 0.9)]);
255 let out1 = tracker.update(&first);
256 let id = out1.detections[0].tracker_id.unwrap();
257
258 let second = Detections::new(vec![det([1.0, 1.0, 11.0, 11.0], 0.9)]);
259 let out2 = tracker.update(&second);
260 assert_eq!(out2.detections[0].tracker_id, Some(id));
261 }
262
263 #[test]
264 fn low_confidence_detection_recovers_an_existing_track() {
265 let mut tracker = ByteTracker::default();
266 let first = Detections::new(vec![det([0.0, 0.0, 10.0, 10.0], 0.9)]);
267 let out1 = tracker.update(&first);
268 let id = out1.detections[0].tracker_id.unwrap();
269
270 let second = Detections::new(vec![det([1.0, 1.0, 11.0, 11.0], 0.15)]);
274 let out2 = tracker.update(&second);
275 assert_eq!(out2.len(), 1);
276 assert_eq!(out2.detections[0].tracker_id, Some(id));
277 }
278
279 #[test]
280 fn track_survives_briefly_missing_and_is_dropped_after_lost_track_buffer() {
281 let mut tracker = ByteTracker::new(0.25, 2, 0.3, 0.2);
282 let first = Detections::new(vec![det([0.0, 0.0, 10.0, 10.0], 0.9)]);
283 tracker.update(&first);
284 assert_eq!(tracker.active_tracks(), 1);
285
286 for _ in 0..2 {
287 tracker.update(&Detections::empty());
288 }
289 assert_eq!(
290 tracker.active_tracks(),
291 1,
292 "should survive up to lost_track_buffer misses"
293 );
294
295 tracker.update(&Detections::empty());
296 assert_eq!(
297 tracker.active_tracks(),
298 0,
299 "should be dropped once lost_track_buffer is exceeded"
300 );
301 }
302
303 #[test]
304 fn two_independent_objects_maintain_separate_identities() {
305 let mut tracker = ByteTracker::default();
306 let first = Detections::new(vec![
307 det([0.0, 0.0, 10.0, 10.0], 0.9),
308 det([100.0, 0.0, 110.0, 10.0], 0.9),
309 ]);
310 let out1 = tracker.update(&first);
311 let id_left = out1.detections[0].tracker_id.unwrap();
312 let id_right = out1.detections[1].tracker_id.unwrap();
313 assert_ne!(id_left, id_right);
314
315 let second = Detections::new(vec![
316 det([2.0, 0.0, 12.0, 10.0], 0.9),
317 det([102.0, 0.0, 112.0, 10.0], 0.9),
318 ]);
319 let out2 = tracker.update(&second);
320 assert_eq!(out2.detections[0].tracker_id, Some(id_left));
321 assert_eq!(out2.detections[1].tracker_id, Some(id_right));
322 }
323}