1use crate::core::Detections;
5use crate::tracker::assignment::assign_by_iou;
6use crate::tracker::kalman::KalmanBoxFilter;
7
8struct Track {
9 id: usize,
10 filter: KalmanBoxFilter,
11 time_since_update: usize,
12}
13
14pub struct SortTracker {
26 tracks: Vec<Track>,
27 next_id: usize,
28 max_age: usize,
29 iou_threshold: f32,
30}
31
32impl SortTracker {
33 pub fn new(max_age: usize, iou_threshold: f32) -> Self {
40 Self {
41 tracks: Vec::new(),
42 next_id: 1,
43 max_age,
44 iou_threshold,
45 }
46 }
47
48 pub fn active_tracks(&self) -> usize {
51 self.tracks.len()
52 }
53
54 pub fn update(&mut self, detections: &mut Detections) {
57 let predicted_boxes: Vec<[f32; 4]> =
58 self.tracks.iter_mut().map(|t| t.filter.predict()).collect();
59
60 let num_tracks = self.tracks.len();
61 let detection_boxes: Vec<[f32; 4]> = detections.detections.iter().map(|d| d.bbox).collect();
62 let matches = assign_by_iou(&predicted_boxes, &detection_boxes, self.iou_threshold);
63
64 let mut track_matched = vec![false; num_tracks];
65 for (detection_index, track_index) in matches.iter().enumerate() {
66 if let Some(track_index) = track_index {
67 let track = &mut self.tracks[*track_index];
68 track
69 .filter
70 .update(detections.detections[detection_index].bbox);
71 track.time_since_update = 0;
72 detections.detections[detection_index].tracker_id = Some(track.id);
73 track_matched[*track_index] = true;
74 }
75 }
76
77 for (track, matched) in self.tracks.iter_mut().zip(track_matched.iter()) {
78 if !matched {
79 track.time_since_update += 1;
80 }
81 }
82
83 for (detection_index, track_index) in matches.iter().enumerate() {
84 if track_index.is_none() {
85 let id = self.next_id;
86 self.next_id += 1;
87 let filter = KalmanBoxFilter::new(detections.detections[detection_index].bbox);
88 self.tracks.push(Track {
89 id,
90 filter,
91 time_since_update: 0,
92 });
93 detections.detections[detection_index].tracker_id = Some(id);
94 }
95 }
96
97 self.tracks.retain(|t| t.time_since_update <= self.max_age);
98 }
99}
100
101impl Default for SortTracker {
102 fn default() -> Self {
105 Self::new(30, 0.3)
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::core::Detection;
113
114 #[test]
115 fn new_detection_gets_a_fresh_tracker_id() {
116 let mut tracker = SortTracker::default();
117 let mut detections = Detections::new(vec![Detection::new([0.0, 0.0, 10.0, 10.0], 0.9, 0)]);
118 tracker.update(&mut detections);
119 assert!(detections.detections[0].tracker_id.is_some());
120 assert_eq!(tracker.active_tracks(), 1);
121 }
122
123 #[test]
124 fn matching_detection_keeps_same_tracker_id() {
125 let mut tracker = SortTracker::new(5, 0.3);
126
127 let mut first = Detections::new(vec![Detection::new([0.0, 0.0, 10.0, 10.0], 0.9, 0)]);
128 tracker.update(&mut first);
129 let id = first.detections[0].tracker_id.unwrap();
130
131 let mut second = Detections::new(vec![Detection::new([1.0, 1.0, 11.0, 11.0], 0.9, 0)]);
132 tracker.update(&mut second);
133
134 assert_eq!(second.detections[0].tracker_id, Some(id));
135 assert_eq!(tracker.active_tracks(), 1);
136 }
137
138 #[test]
139 fn two_independent_objects_maintain_separate_identities() {
140 let mut tracker = SortTracker::new(5, 0.3);
141
142 let mut first = Detections::new(vec![
143 Detection::new([0.0, 0.0, 10.0, 10.0], 0.9, 0),
144 Detection::new([100.0, 0.0, 110.0, 10.0], 0.9, 0),
145 ]);
146 tracker.update(&mut first);
147 let id_left = first.detections[0].tracker_id.unwrap();
148 let id_right = first.detections[1].tracker_id.unwrap();
149 assert_ne!(id_left, id_right);
150
151 let mut second = Detections::new(vec![
152 Detection::new([2.0, 0.0, 12.0, 10.0], 0.9, 0),
153 Detection::new([102.0, 0.0, 112.0, 10.0], 0.9, 0),
154 ]);
155 tracker.update(&mut second);
156
157 assert_eq!(second.detections[0].tracker_id, Some(id_left));
158 assert_eq!(second.detections[1].tracker_id, Some(id_right));
159 }
160
161 #[test]
162 fn far_apart_detection_does_not_match_existing_track() {
163 let mut tracker = SortTracker::new(5, 0.3);
164
165 let mut first = Detections::new(vec![Detection::new([0.0, 0.0, 10.0, 10.0], 0.9, 0)]);
166 tracker.update(&mut first);
167 let id1 = first.detections[0].tracker_id.unwrap();
168
169 let mut second =
170 Detections::new(vec![Detection::new([200.0, 200.0, 210.0, 210.0], 0.9, 0)]);
171 tracker.update(&mut second);
172 let id2 = second.detections[0].tracker_id.unwrap();
173
174 assert_ne!(id1, id2);
175 assert_eq!(tracker.active_tracks(), 2);
178 }
179
180 #[test]
181 fn track_is_removed_after_max_age_missed_frames() {
182 let mut tracker = SortTracker::new(2, 0.3);
183
184 let mut first = Detections::new(vec![Detection::new([0.0, 0.0, 10.0, 10.0], 0.9, 0)]);
185 tracker.update(&mut first);
186 assert_eq!(tracker.active_tracks(), 1);
187
188 for _ in 0..2 {
189 let mut empty = Detections::empty();
190 tracker.update(&mut empty);
191 }
192 assert_eq!(
193 tracker.active_tracks(),
194 1,
195 "should survive up to max_age misses"
196 );
197
198 let mut empty = Detections::empty();
199 tracker.update(&mut empty);
200 assert_eq!(
201 tracker.active_tracks(),
202 0,
203 "should be dropped once max_age is exceeded"
204 );
205 }
206}