cecile_supercool_tracker/trackers/visual_sort/
options.rs

1use crate::trackers::sort::{PositionalMetricType, SortAttributesOptions};
2use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;
3use crate::trackers::visual_sort::metric::builder::VisualMetricBuilder;
4use crate::trackers::visual_sort::metric::{VisualMetric, VisualSortMetricType};
5use std::collections::HashMap;
6use std::sync::RwLock;
7
8/// Class that is used to configure the Visual Tracker
9#[derive(Debug, Clone)]
10pub struct VisualSortOptions {
11    max_idle_epochs: usize,
12    kept_history_length: usize,
13    spatio_temporal_constraints: SpatioTemporalConstraints,
14    metric_builder: VisualMetricBuilder,
15    kalman_position_weight: f32,
16    kalman_velocity_weight: f32,
17}
18
19impl VisualSortOptions {
20    pub(crate) fn build(self) -> (SortAttributesOptions, VisualMetric) {
21        (
22            SortAttributesOptions::new(
23                Some(RwLock::new(HashMap::default())),
24                self.max_idle_epochs,
25                self.kept_history_length,
26                self.spatio_temporal_constraints,
27                self.kalman_position_weight,
28                self.kalman_velocity_weight,
29            ),
30            self.metric_builder.build(),
31        )
32    }
33
34    /// The number of epochs the track remains active.
35    ///
36    /// Lets the Frame Rate per second is `30`, setting `max_idle_epochs` to `30` means that the
37    /// track in store will be active even if only one new observation was merged with it during the
38    /// second. If during `30` invocations of `predict` for the scene, where the track is defined,
39    /// no observations are merged with it, the track will be marked as wasted, and no further
40    /// observations will be merged with it.
41    ///
42    pub fn max_idle_epochs(mut self, n: usize) -> Self {
43        self.max_idle_epochs = n;
44        self
45    }
46
47    /// The number of last observations, predictions, and features kept within the track attributes.
48    ///
49    /// The track's attributes may accumulate last observations, predictions, and features for the
50    /// caller's purpose. To protect the system from overflow `kept_history_length` parameter is used.
51    /// It forces the track to keep only the last `N` values instead of unlimited history. The parameter
52    /// is important when one uses the tracker in offline mode -  when the wasted tracks are used to get
53    /// the history. If the tracker is an online tracker, setting `1` is a reasonable value to keep memory
54    /// utilization low.
55    ///
56    pub fn kept_history_length(mut self, n: usize) -> Self {
57        assert!(n > 0, "History length must be a positive number");
58        self.kept_history_length = n;
59        self
60    }
61
62    /// The method is used to calculate the distance for visual_sort feature vectors.
63    ///
64    /// Currently, cosine and euclidean metrics are supported. The one you choose
65    /// is defined by the ReID model used.
66    ///    
67    pub fn visual_metric(mut self, metric: VisualSortMetricType) -> Self {
68        self.metric_builder = self.metric_builder.visual_metric(metric);
69        self
70    }
71
72    /// The minimal number of votes that is required to allow a track candidate to surpass the enabling
73    /// threshold of the visual_sort voting. The maximum allowed number of visual_sort features kept for the track
74    /// is defined by `visual_max_observations`.
75    ///
76    /// _Don't confuse `visual_max_observations` with `kept_history_length` - they have no relation.
77    /// The later is only used for caller purposes, not for track prediction._
78    ///
79    /// When the track candidate consisting of the single observation is compared versus tracks kept in
80    /// the store the system calculates up to `N` distances (`1 X N`), where `N` at most is equal to
81    /// `visual_max_observations`, but can be less if the track is short or previous observations were
82    /// ignored due to quality or other constraints.
83    ///
84    /// Only when `N >= visual_min_votes`, the track candidate is used in leader selection.
85    ///
86    pub fn visual_min_votes(mut self, n: usize) -> Self {
87        self.metric_builder = self.metric_builder.visual_min_votes(n);
88        self
89    }
90
91    /// The maximum number of visual_sort observations kept in the track for visual_sort estimations. The features
92    /// are collected in the track from the candidates, and when the `visual_max_observations` is
93    /// reached, the features with lower quality are wiped from the track.
94    ///
95    pub fn visual_max_observations(mut self, n: usize) -> Self {
96        self.metric_builder = self.metric_builder.visual_max_observations(n);
97        self
98    }
99
100    /// Minimal allowed confidence for bounding boxes. If the confidence is less than specified it is
101    /// corrected to be the minimal
102    ///
103    pub fn positional_min_confidence(mut self, conf: f32) -> Self {
104        self.metric_builder = self.metric_builder.positional_min_confidence(conf);
105        self
106    }
107
108    /// The constraints define how far the candidate is allowed to be from a track’s last box to
109    /// participate in the selection for the track. If the track candidate is too far from the
110    /// track kept in the store, it is skipped from the comparison.
111    ///
112    pub fn spatio_temporal_constraints(mut self, constraints: SpatioTemporalConstraints) -> Self {
113        self.spatio_temporal_constraints = constraints;
114        self
115    }
116
117    /// The parameter defines which positional metric is used to calculate distances between the track
118    /// candidate and tracks kept in the store. There are two metrics are supported - the Mahalanobis metric
119    /// and the IoU metric.
120    ///
121    pub fn positional_metric(mut self, metric: PositionalMetricType) -> Self {
122        self.metric_builder = self.metric_builder.positional_metric(metric);
123        self
124    }
125
126    /// The minimally required number of visual_sort features in the track that enables their usage in
127    /// candidates estimation. If the track is short and there are fewer features collected than
128    /// `visual_minimal_track_length` then candidates are estimated against it only by positional
129    /// distance. Keep in mind that this parameter must be less than or equal
130    /// to `visual_max_observations` to have sense.
131    ///
132    pub fn visual_minimal_track_length(mut self, length: usize) -> Self {
133        self.metric_builder = self.metric_builder.visual_minimal_track_length(length);
134        self
135    }
136
137    /// The minimal required area of track candidate's bounding box to use the visual_sort feature in estimation.
138    /// This parameter protects from the low-quality features received from the smallish boxes.
139    ///
140    pub fn visual_minimal_area(mut self, area: f32) -> Self {
141        self.metric_builder = self.metric_builder.visual_minimal_area(area);
142        self
143    }
144
145    /// The visual_sort quality threshold of a feature that activates the visual_sort estimation of a candidate
146    /// versus the tracks kept in the store.
147    ///
148    pub fn visual_minimal_quality_use(mut self, q: f32) -> Self {
149        self.metric_builder = self.metric_builder.visual_minimal_quality_use(q);
150        self
151    }
152
153    /// The visual_sort quality threshold of a feature that activates the adding of the visual_sort feature
154    /// to the track's visual_sort features.
155    ///
156    pub fn visual_minimal_quality_collect(mut self, q: f32) -> Self {
157        self.metric_builder = self.metric_builder.visual_minimal_quality_collect(q);
158        self
159    }
160
161    /// The threshold is calculated as `solely_owned_area / all_area` of the bounding box that
162    /// prevents low-quality visual_sort features received in a messy environment from being used in
163    /// visual_sort predictions.
164    ///
165    pub fn visual_minimal_own_area_percentage_use(mut self, area: f32) -> Self {
166        self.metric_builder = self
167            .metric_builder
168            .visual_minimal_own_area_percentage_use(area);
169        self
170    }
171
172    /// The threshold is calculated as `solely_owned_area / all_area` of the bounding box that prevents
173    /// low-quality visual_sort features received in a messy environment from being collected to a track
174    /// for making visual_sort predictions.
175    ///
176    pub fn visual_minimal_own_area_percentage_collect(mut self, area: f32) -> Self {
177        self.metric_builder = self
178            .metric_builder
179            .visual_minimal_own_area_percentage_collect(area);
180        self
181    }
182
183    pub fn kalman_position_weight(mut self, weight: f32) -> Self {
184        self.kalman_position_weight = weight;
185        self
186    }
187
188    pub fn kalman_velocity_weight(mut self, weight: f32) -> Self {
189        self.kalman_velocity_weight = weight;
190        self
191    }
192}
193
194impl Default for VisualSortOptions {
195    fn default() -> Self {
196        Self {
197            max_idle_epochs: 2,
198            kept_history_length: 10,
199            metric_builder: VisualMetricBuilder::default(),
200            spatio_temporal_constraints: SpatioTemporalConstraints::default(),
201            kalman_position_weight: 1.0 / 20.0,
202            kalman_velocity_weight: 1.0 / 160.0,
203        }
204    }
205}
206
207#[cfg(feature = "python")]
208pub mod python {
209    use crate::trackers::sort::python::PyPositionalMetricType;
210    use crate::trackers::spatio_temporal_constraints::python::PySpatioTemporalConstraints;
211    use crate::trackers::visual_sort::metric::python::PyVisualSortMetricType;
212
213    use super::VisualSortOptions;
214    use pyo3::prelude::*;
215
216    #[pyclass]
217    #[pyo3(name = "VisualSortOptions")]
218    pub struct PyVisualSortOptions(pub(crate) VisualSortOptions);
219
220    #[pymethods]
221    impl PyVisualSortOptions {
222        #[new]
223        pub(crate) fn new() -> Self {
224            Self(VisualSortOptions::default())
225        }
226
227        #[pyo3(text_signature = "($self, n)")]
228        pub(crate) fn max_idle_epochs(&mut self, n: i64) {
229            self.0.max_idle_epochs = n.try_into().expect("Parameter must be a positive number");
230        }
231
232        #[pyo3(text_signature = "($self, n)")]
233        pub(crate) fn kept_history_length(&mut self, n: i64) {
234            self.0.kept_history_length = n.try_into().expect("Parameter must be a positive number");
235        }
236
237        #[pyo3(text_signature = "($self, n)")]
238        pub(crate) fn visual_min_votes(&mut self, n: i64) {
239            self.0.metric_builder.set_visual_min_votes(n as _);
240        }
241
242        #[pyo3(text_signature = "($self, metric)")]
243        pub(crate) fn visual_metric(&mut self, metric: PyVisualSortMetricType) {
244            self.0.metric_builder.set_visual_kind(metric.0);
245        }
246
247        #[pyo3(text_signature = "($self, constraints)")]
248        pub(crate) fn spatio_temporal_constraints(
249            &mut self,
250            constraints: PySpatioTemporalConstraints,
251        ) {
252            self.0.spatio_temporal_constraints = constraints.0;
253        }
254
255        #[pyo3(text_signature = "($self, metric)")]
256        pub(crate) fn positional_metric(&mut self, metric: PyPositionalMetricType) {
257            self.0.metric_builder.set_positional_kind(metric.0);
258        }
259
260        #[pyo3(text_signature = "($self, length)")]
261        pub(crate) fn visual_minimal_track_length(&mut self, length: i64) {
262            self.0.metric_builder.set_visual_minimal_track_length(
263                length
264                    .try_into()
265                    .expect("Parameter must be a positive number"),
266            );
267        }
268
269        #[pyo3(text_signature = "($self, area)")]
270        pub(crate) fn visual_minimal_area(&mut self, area: f32) {
271            self.0.metric_builder.set_visual_minimal_area(area);
272        }
273
274        #[pyo3(text_signature = "($self, q)")]
275        pub(crate) fn visual_minimal_quality_use(&mut self, q: f32) {
276            self.0.metric_builder.set_visual_minimal_quality_use(q);
277        }
278
279        #[pyo3(text_signature = "($self, conf)")]
280        pub(crate) fn positional_min_confidence(&mut self, conf: f32) {
281            self.0.metric_builder.set_positional_min_confidence(conf);
282        }
283
284        #[pyo3(text_signature = "($self, n)")]
285        pub(crate) fn visual_max_observations(&mut self, n: i64) {
286            self.0.metric_builder.set_visual_max_observations(
287                n.try_into().expect("Parameter must be a positive number"),
288            );
289        }
290
291        #[pyo3(text_signature = "($self, q)")]
292        pub(crate) fn visual_minimal_quality_collect(&mut self, q: f32) {
293            self.0.metric_builder.set_visual_minimal_quality_collect(q);
294        }
295
296        #[pyo3(text_signature = "($self, area)")]
297        pub(crate) fn visual_minimal_own_area_percentage_use(&mut self, area: f32) {
298            self.0
299                .metric_builder
300                .set_visual_minimal_own_area_percentage_use(area);
301        }
302
303        #[pyo3(text_signature = "($self, area)")]
304        pub(crate) fn visual_minimal_own_area_percentage_collect(&mut self, area: f32) {
305            self.0
306                .metric_builder
307                .set_visual_minimal_own_area_percentage_collect(area);
308        }
309
310        #[pyo3(text_signature = "($self, weight)")]
311        pub(crate) fn kalman_position_weight(&mut self, weight: f32) {
312            self.0.kalman_position_weight = weight;
313        }
314
315        #[pyo3(text_signature = "($self, weight)")]
316        pub(crate) fn kalman_velocity_weight(&mut self, weight: f32) {
317            self.0.kalman_velocity_weight = weight;
318        }
319
320        #[classattr]
321        const __hash__: Option<Py<PyAny>> = None;
322
323        fn __repr__(&self) -> String {
324            format!("{:?}", self.0)
325        }
326
327        fn __str__(&self) -> String {
328            format!("{:#?}", self.0)
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use crate::trackers::sort::python::PyPositionalMetricType;
336    use crate::trackers::sort::PositionalMetricType;
337    use crate::trackers::spatio_temporal_constraints::python::PySpatioTemporalConstraints;
338    use crate::trackers::spatio_temporal_constraints::SpatioTemporalConstraints;
339    use crate::trackers::visual_sort::metric::python::PyVisualSortMetricType;
340    use crate::trackers::visual_sort::metric::VisualSortMetricType;
341    use crate::trackers::visual_sort::options::python::PyVisualSortOptions;
342    use crate::trackers::visual_sort::options::VisualSortOptions;
343
344    #[test]
345    fn visual_sort_options_builder() {
346        let (opts, metric) = dbg!(VisualSortOptions::default()
347            .max_idle_epochs(3)
348            .kept_history_length(10)
349            .visual_metric(VisualSortMetricType::Euclidean(100.0))
350            .positional_metric(PositionalMetricType::Mahalanobis)
351            .visual_minimal_track_length(3)
352            .visual_minimal_area(5.0)
353            .visual_minimal_quality_use(0.45)
354            .visual_minimal_quality_collect(0.5)
355            .visual_max_observations(25)
356            .visual_min_votes(5)
357            .positional_min_confidence(0.13)
358            .visual_minimal_own_area_percentage_use(0.1)
359            .visual_minimal_own_area_percentage_collect(0.2)
360            .spatio_temporal_constraints(
361                SpatioTemporalConstraints::default().constraints(&[(5, 7.0)])
362            )
363            .build());
364
365        let mut opts_builder = PyVisualSortOptions::new();
366        opts_builder.max_idle_epochs(3);
367        opts_builder.kept_history_length(10);
368        opts_builder.visual_metric(PyVisualSortMetricType::euclidean(100.0));
369        opts_builder.positional_metric(PyPositionalMetricType::maha());
370        opts_builder.visual_minimal_track_length(3);
371        opts_builder.visual_minimal_area(5.0);
372        opts_builder.visual_minimal_quality_use(0.45);
373        opts_builder.visual_minimal_quality_collect(0.5);
374        opts_builder.visual_max_observations(25);
375        opts_builder.positional_min_confidence(0.13);
376        opts_builder.visual_minimal_own_area_percentage_use(0.1);
377        opts_builder.visual_minimal_own_area_percentage_collect(0.2);
378        opts_builder.visual_min_votes(5);
379        let mut constraints = PySpatioTemporalConstraints::new();
380        constraints.add_constraints(vec![(5, 7.0)]);
381        opts_builder.spatio_temporal_constraints(constraints);
382        let (opts_py, metric_py) = dbg!(opts_builder.0.build());
383
384        assert_eq!(format!("{:?}", opts), format!("{:?}", opts_py));
385        assert_eq!(format!("{:?}", metric), format!("{:?}", metric_py));
386    }
387}