cecile_supercool_tracker/utils/
bbox.rs

1use crate::track::ObservationAttributes;
2use crate::utils::clipping::sutherland_hodgman_clip;
3use crate::Errors::GenericBBoxConversionError;
4use crate::{Errors, EPS};
5use geo::{Area, Coord, LineString, Polygon};
6use std::f32::consts::PI;
7
8/// Bounding box in the format (left, top, width, height)
9///
10#[derive(Clone, Default, Debug, Copy)]
11pub struct BoundingBox {
12    pub left: f32,
13    pub top: f32,
14    pub width: f32,
15    pub height: f32,
16    pub confidence: f32,
17}
18
19impl BoundingBox {
20    pub fn new(left: f32, top: f32, width: f32, height: f32) -> Self {
21        Self {
22            left,
23            top,
24            width,
25            height,
26            confidence: 1.0,
27        }
28    }
29
30    pub fn new_with_confidence(
31        left: f32,
32        top: f32,
33        width: f32,
34        height: f32,
35        confidence: f32,
36    ) -> Self {
37        assert!(
38            (0.0..=1.0).contains(&confidence),
39            "Confidence must lay between 0.0 and 1.0"
40        );
41        Self {
42            left,
43            top,
44            width,
45            height,
46            confidence,
47        }
48    }
49
50    pub fn as_xyaah(&self) -> Universal2DBox {
51        Universal2DBox::from(self)
52    }
53
54    pub fn intersection(l: &BoundingBox, r: &BoundingBox) -> f64 {
55        assert!(l.width > 0.0);
56        assert!(l.height > 0.0);
57        assert!(r.width > 0.0);
58        assert!(r.height > 0.0);
59
60        let (ax0, ay0, ax1, ay1) = (l.left, l.top, l.left + l.width, l.top + l.height);
61        let (bx0, by0, bx1, by1) = (r.left, r.top, r.left + r.width, r.top + r.height);
62
63        let (x1, y1) = (ax0.max(bx0), ay0.max(by0));
64        let (x2, y2) = (ax1.min(bx1), ay1.min(by1));
65
66        let int_width = x2 - x1;
67        let int_height = y2 - y1;
68
69        if int_width > 0.0 && int_height > 0.0 {
70            (int_width * int_height) as f64
71        } else {
72            0.0_f64
73        }
74    }
75}
76
77/// Bounding box in the format (x, y, angle, aspect, height)
78#[derive(Default, Debug)]
79pub struct Universal2DBox {
80    pub xc: f32,
81    pub yc: f32,
82    pub angle: Option<f32>,
83    pub aspect: f32,
84    pub height: f32,
85    pub confidence: f32,
86    _vertex_cache: Option<Polygon<f64>>,
87}
88
89impl Clone for Universal2DBox {
90    fn clone(&self) -> Self {
91        Universal2DBox::new_with_confidence(
92            self.xc,
93            self.yc,
94            self.angle,
95            self.aspect,
96            self.height,
97            self.confidence,
98        )
99    }
100}
101
102impl Universal2DBox {
103    pub fn new(xc: f32, yc: f32, angle: Option<f32>, aspect: f32, height: f32) -> Self {
104        Self {
105            xc,
106            yc,
107            angle,
108            aspect,
109            height,
110            confidence: 1.0,
111            _vertex_cache: None,
112        }
113    }
114
115    pub fn new_with_confidence(
116        xc: f32,
117        yc: f32,
118        angle: Option<f32>,
119        aspect: f32,
120        height: f32,
121        confidence: f32,
122    ) -> Self {
123        assert!(
124            (0.0..=1.0).contains(&confidence),
125            "Confidence must lay between 0.0 and 1.0"
126        );
127
128        Self {
129            xc,
130            yc,
131            angle,
132            aspect,
133            height,
134            confidence,
135            _vertex_cache: None,
136        }
137    }
138
139    pub fn ltwh(left: f32, top: f32, width: f32, height: f32) -> Self {
140        Self::from(BoundingBox::new_with_confidence(
141            left, top, width, height, 1.0,
142        ))
143    }
144
145    pub fn ltwh_with_confidence(
146        left: f32,
147        top: f32,
148        width: f32,
149        height: f32,
150        confidence: f32,
151    ) -> Self {
152        Self::from(BoundingBox::new_with_confidence(
153            left, top, width, height, confidence,
154        ))
155    }
156
157    pub fn get_radius(&self) -> f32 {
158        let hw = self.aspect * self.height / 2.0_f32;
159        let hh = self.height / 2.0_f32;
160        (hw * hw + hh * hh).sqrt()
161    }
162
163    pub fn area(&self) -> f32 {
164        let w = self.height * self.aspect;
165        w * self.height
166    }
167
168    #[inline]
169    pub fn get_vertices(&self) -> Polygon {
170        Polygon::from(self)
171    }
172
173    #[inline]
174    pub fn get_cached_vertices(&self) -> &Option<Polygon<f64>> {
175        &self._vertex_cache
176    }
177
178    #[inline]
179    pub fn gen_vertices(&mut self) -> &Self {
180        if self.angle.is_some() {
181            self._vertex_cache = Some(self.get_vertices());
182        }
183        self
184    }
185
186    /// Sets the angle
187    ///
188    pub fn rotate(self, angle: f32) -> Self {
189        Self {
190            xc: self.xc,
191            yc: self.yc,
192            angle: Some(angle),
193            aspect: self.aspect,
194            height: self.height,
195            confidence: self.confidence,
196            _vertex_cache: None,
197        }
198    }
199
200    /// Sets the angle
201    ///
202    pub fn rotate_mut(&mut self, angle: f32) {
203        self.angle = Some(angle)
204    }
205
206    /// Sets the angle
207    ///
208    pub fn set_confidence(&mut self, confidence: f32) {
209        assert!(
210            (0.0..=1.0).contains(&confidence),
211            "Confidence must lay between 0.0 and 1.0"
212        );
213        self.confidence = confidence;
214    }
215
216    pub fn sutherland_hodgman_clip(mut self, mut clipping: Universal2DBox) -> Polygon<f64> {
217        if self.angle.is_none() {
218            self.rotate_mut(0.0);
219        }
220
221        if clipping.angle.is_none() {
222            clipping.rotate_mut(0.0);
223        }
224
225        if self.get_cached_vertices().is_none() {
226            self.gen_vertices();
227        }
228
229        if clipping.get_cached_vertices().is_none() {
230            clipping.gen_vertices();
231        }
232
233        sutherland_hodgman_clip(
234            self.get_cached_vertices().as_ref().unwrap(),
235            clipping.get_cached_vertices().as_ref().unwrap(),
236        )
237    }
238}
239
240impl From<BoundingBox> for Universal2DBox {
241    fn from(f: BoundingBox) -> Self {
242        Self::from(&f)
243    }
244}
245
246impl From<&BoundingBox> for Universal2DBox {
247    fn from(f: &BoundingBox) -> Self {
248        Universal2DBox {
249            xc: f.left + f.width / 2.0,
250            yc: f.top + f.height / 2.0,
251            angle: None,
252            aspect: f.width / f.height,
253            height: f.height,
254            confidence: f.confidence,
255            _vertex_cache: None,
256        }
257    }
258}
259
260impl TryFrom<Universal2DBox> for BoundingBox {
261    type Error = Errors;
262
263    fn try_from(value: Universal2DBox) -> Result<Self, Self::Error> {
264        BoundingBox::try_from(&value)
265    }
266}
267
268impl TryFrom<&Universal2DBox> for BoundingBox {
269    type Error = Errors;
270
271    fn try_from(f: &Universal2DBox) -> Result<Self, Self::Error> {
272        if f.angle.is_some() {
273            Err(GenericBBoxConversionError)
274        } else {
275            let width = f.height * f.aspect;
276            Ok(BoundingBox {
277                left: f.xc - width / 2.0,
278                top: f.yc - f.height / 2.0,
279                width,
280                height: f.height,
281                confidence: f.confidence,
282            })
283        }
284    }
285}
286
287impl From<&Universal2DBox> for Polygon<f64> {
288    fn from(b: &Universal2DBox) -> Self {
289        let angle = b.angle.unwrap_or(0.0) as f64;
290        let height = b.height as f64;
291        let aspect = b.aspect as f64;
292
293        let c = angle.cos();
294        let s = angle.sin();
295
296        let half_width = height * aspect / 2.0;
297        let half_height = height / 2.0;
298
299        let r1x = -half_width * c - half_height * s;
300        let r1y = -half_width * s + half_height * c;
301
302        let r2x = half_width * c - half_height * s;
303        let r2y = half_width * s + half_height * c;
304
305        let x = b.xc as f64;
306        let y = b.yc as f64;
307
308        Polygon::new(
309            LineString(vec![
310                Coord {
311                    x: x + r1x,
312                    y: y + r1y,
313                },
314                Coord {
315                    x: x + r2x,
316                    y: y + r2y,
317                },
318                Coord {
319                    x: x - r1x,
320                    y: y - r1y,
321                },
322                Coord {
323                    x: x - r2x,
324                    y: y - r2y,
325                },
326            ]),
327            vec![],
328        )
329    }
330}
331
332#[cfg(test)]
333mod polygons {
334    use crate::track::ObservationAttributes;
335    use crate::utils::bbox::Universal2DBox;
336    use crate::utils::clipping::sutherland_hodgman_clip;
337    use crate::EPS;
338    use geo::{Area, BooleanOps, Polygon};
339    use std::f32::consts::PI;
340
341    #[test]
342    fn transform() {
343        let bbox1 = Universal2DBox::new(0.0, 0.0, Some(2.0), 0.5, 2.0);
344        let polygon1 = Polygon::from(&bbox1);
345        let bbox2 = Universal2DBox::new(0.0, 0.0, Some(2.0 + PI / 2.0), 0.5, 2.0);
346        let polygon2 = Polygon::from(&bbox2);
347        let clip = sutherland_hodgman_clip(&polygon1, &polygon2);
348        let int_area = clip.unsigned_area();
349        let int = polygon1.intersection(&polygon2).unsigned_area();
350        assert!((int - int_area).abs() < EPS as f64);
351
352        let union = polygon1.union(&polygon2).unsigned_area();
353        assert!((union - 3.0).abs() < EPS as f64);
354
355        let res =
356            Universal2DBox::calculate_metric_object(&Some(&bbox1), &Some(&bbox2)).unwrap() as f64;
357        assert!((res - int / union).abs() < EPS as f64);
358
359        let bbox3 = Universal2DBox::new(10.0, 0.0, Some(2.0 + PI / 2.0), 0.5, 2.0);
360        let polygon3 = Polygon::from(&bbox3);
361
362        let int = polygon1.intersection(&polygon3).unsigned_area();
363        assert!((int - 0.0).abs() < EPS as f64);
364
365        let union = polygon1.union(&polygon3).unsigned_area();
366        assert!((union - 4.0).abs() < EPS as f64);
367
368        assert!(Universal2DBox::calculate_metric_object(&Some(&bbox1), &Some(&bbox3)).is_none());
369    }
370
371    #[test]
372    fn corner_case_f32() {
373        let x = Universal2DBox::new(8044.315, 8011.0454, Some(2.678_774_8), 1.00801, 49.8073);
374        let polygon_x = Polygon::from(&x);
375
376        let y = Universal2DBox::new(8044.455, 8011.338, Some(2.678_774_8), 1.0083783, 49.79979);
377        let polygon_y = Polygon::from(&y);
378
379        dbg!(&polygon_x, &polygon_y);
380    }
381}
382
383// impl From<&Universal2DBox> for BoundingBox {
384//     /// This is a lossy translation. It is valid only when the angle is 0
385//     fn from(f: &Universal2DBox) -> Self {
386//         let width = f.height * f.aspect;
387//         BoundingBox {
388//             left: f.xc - width / 2.0,
389//             top: f.yc - f.height / 2.0,
390//             width,
391//             height: f.height,
392//             confidence: f.confidence,
393//         }
394//     }
395// }
396
397impl ObservationAttributes for BoundingBox {
398    type MetricObject = f32;
399
400    fn calculate_metric_object(
401        left: &Option<&Self>,
402        right: &Option<&Self>,
403    ) -> Option<Self::MetricObject> {
404        match (left, right) {
405            (Some(l), Some(r)) => {
406                let intersection = BoundingBox::intersection(l, r);
407                let union = (l.height * l.width + r.height * r.width) as f64 - intersection;
408                let res = intersection / union;
409                Some(res as f32)
410            }
411            _ => None,
412        }
413    }
414}
415
416impl PartialEq<Self> for BoundingBox {
417    fn eq(&self, other: &Self) -> bool {
418        (self.left - other.left).abs() < EPS
419            && (self.top - other.top).abs() < EPS
420            && (self.width - other.width) < EPS
421            && (self.height - other.height) < EPS
422            && (self.confidence - other.confidence) < EPS
423    }
424}
425
426pub fn normalize_angle(a: f32) -> f32 {
427    let pix2 = 2.0 * PI;
428    let n = (a / pix2).floor();
429    let a = a - n * pix2;
430    if a < 0.0 {
431        a + pix2
432    } else {
433        a
434    }
435}
436
437#[cfg(test)]
438mod tests_normalize_angle {
439    use crate::utils::bbox::normalize_angle;
440    use crate::EPS;
441
442    #[test]
443    fn normalize() {
444        assert!((normalize_angle(0.3) - 0.3).abs() < EPS);
445        assert!((normalize_angle(-0.3) - 5.983184).abs() < EPS);
446        assert!((normalize_angle(-0.3) - 5.983184).abs() < EPS);
447        assert!((normalize_angle(6.583184) - 0.3).abs() < EPS);
448    }
449}
450
451impl Universal2DBox {
452    pub fn too_far(l: &Universal2DBox, r: &Universal2DBox) -> bool {
453        assert!(l.aspect > 0.0);
454        assert!(l.height > 0.0);
455        assert!(r.aspect > 0.0);
456        assert!(r.height > 0.0);
457
458        let max_distance = l.get_radius() + r.get_radius();
459        let x = l.xc - r.xc;
460        let y = l.yc - r.yc;
461        x * x + y * y > max_distance * max_distance
462    }
463
464    pub fn dist_in_2r(l: &Universal2DBox, r: &Universal2DBox) -> f32 {
465        assert!(l.aspect > 0.0);
466        assert!(l.height > 0.0);
467        assert!(r.aspect > 0.0);
468        assert!(r.height > 0.0);
469
470        let radial_distance = l.get_radius() + r.get_radius();
471        let x = l.xc - r.xc;
472        let y = l.yc - r.yc;
473        (x * x + y * y).sqrt() / (radial_distance * radial_distance + EPS).sqrt()
474    }
475
476    pub fn intersection(l: &Universal2DBox, r: &Universal2DBox) -> f64 {
477        // REMOVED DUE TO: Github #84
478        // need to implement better way to run simplified IoU
479        // now it runs in a general way
480        //
481        // if (normalize_angle(l.angle.unwrap_or(0.0)) - normalize_angle(r.angle.unwrap_or(0.0))).abs()
482        //     < EPS
483        // {
484        //     BoundingBox::intersection(&new_l.try_into().unwrap(), &new_r.try_into().unwrap())
485        // } else
486        if Universal2DBox::too_far(l, r) {
487            0.0
488        } else {
489            let mut l = l.clone();
490            let mut r = r.clone();
491
492            if l.get_cached_vertices().is_none() {
493                let angle = l.angle.unwrap_or(0.0);
494                l.rotate_mut(angle);
495                l.gen_vertices();
496            }
497
498            if r.get_cached_vertices().is_none() {
499                let angle = r.angle.unwrap_or(0.0);
500                r.rotate_mut(angle);
501                r.gen_vertices();
502            }
503
504            let p1 = l.get_cached_vertices().as_ref().unwrap();
505            let p2 = r.get_cached_vertices().as_ref().unwrap();
506
507            sutherland_hodgman_clip(p1, p2).unsigned_area()
508        }
509    }
510}
511
512impl ObservationAttributes for Universal2DBox {
513    type MetricObject = f32;
514
515    fn calculate_metric_object(
516        left: &Option<&Self>,
517        right: &Option<&Self>,
518    ) -> Option<Self::MetricObject> {
519        match (left, right) {
520            (Some(l), Some(r)) => {
521                let intersection = Universal2DBox::intersection(l, r);
522                if intersection == 0.0 {
523                    None
524                } else {
525                    let union = (l.height * l.height * l.aspect + r.height * r.height * r.aspect)
526                        as f64
527                        - intersection;
528                    let res = intersection / union;
529                    Some(res as f32)
530                }
531            }
532            _ => None,
533        }
534    }
535}
536
537impl PartialEq<Self> for Universal2DBox {
538    fn eq(&self, other: &Self) -> bool {
539        (self.xc - other.xc).abs() < EPS
540            && (self.yc - other.yc).abs() < EPS
541            && (self.angle.unwrap_or(0.0) - other.angle.unwrap_or(0.0)) < EPS
542            && (self.aspect - other.aspect) < EPS
543            && (self.height - other.height) < EPS
544    }
545}
546
547#[cfg(feature = "python")]
548pub mod python {
549    use crate::utils::clipping::clipping_py::PyPolygon;
550
551    use super::{BoundingBox, Universal2DBox};
552    use pyo3::{exceptions::PyAttributeError, prelude::*};
553
554    #[derive(Clone, Default, Debug, Copy)]
555    #[repr(transparent)]
556    #[pyclass]
557    #[pyo3(name = "BoundingBox")]
558    pub struct PyBoundingBox(pub(crate) BoundingBox);
559
560    #[pymethods]
561    impl PyBoundingBox {
562        #[classattr]
563        const __hash__: Option<Py<PyAny>> = None;
564
565        fn __repr__(&self) -> String {
566            format!("{:?}", self.0)
567        }
568
569        fn __str__(&self) -> String {
570            self.__repr__()
571        }
572
573        #[getter]
574        pub fn get_left(&self) -> f32 {
575            self.0.left
576        }
577
578        #[setter]
579        pub fn set_left(&mut self, left: f32) {
580            self.0.left = left;
581        }
582
583        #[getter]
584        pub fn get_top(&self) -> f32 {
585            self.0.top
586        }
587
588        #[setter]
589        pub fn set_top(&mut self, top: f32) {
590            self.0.top = top;
591        }
592
593        #[getter]
594        pub fn get_width(&mut self) -> f32 {
595            self.0.width
596        }
597
598        #[setter]
599        pub fn set_width(&mut self, width: f32) {
600            self.0.width = width;
601        }
602
603        #[getter]
604        pub fn get_height(&mut self) -> f32 {
605            self.0.height
606        }
607
608        #[setter]
609        pub fn set_height(&mut self, height: f32) {
610            self.0.height = height;
611        }
612
613        #[getter]
614        pub fn get_confidence(&mut self) -> f32 {
615            self.0.confidence
616        }
617
618        #[setter]
619        pub fn set_confidence(&mut self, confidence: f32) {
620            self.0.confidence = confidence;
621        }
622
623        pub fn as_xyaah(&self) -> PyUniversal2DBox {
624            PyUniversal2DBox(self.0.as_xyaah())
625        }
626        /// Constructor. Confidence is set to 1.0
627        ///
628        #[new]
629        pub fn new(left: f32, top: f32, width: f32, height: f32) -> Self {
630            Self(BoundingBox::new(left, top, width, height))
631        }
632
633        /// Creates the bbox with custom confidence
634        ///
635        #[staticmethod]
636        pub fn new_with_confidence(
637            left: f32,
638            top: f32,
639            width: f32,
640            height: f32,
641            confidence: f32,
642        ) -> Self {
643            Self(BoundingBox::new_with_confidence(
644                left, top, width, height, confidence,
645            ))
646        }
647    }
648
649    #[derive(Default, Debug, Clone)]
650    #[repr(transparent)]
651    #[pyclass]
652    #[pyo3(name = "Universal2DBox")]
653    pub struct PyUniversal2DBox(pub(crate) Universal2DBox);
654
655    #[pymethods]
656    impl PyUniversal2DBox {
657        #[classattr]
658        const __hash__: Option<Py<PyAny>> = None;
659
660        fn __repr__(&self) -> String {
661            format!("{:?}", self.0)
662        }
663
664        fn __str__(&self) -> String {
665            self.__repr__()
666        }
667
668        pub fn get_radius(&self) -> f32 {
669            self.0.get_radius()
670        }
671
672        pub fn as_ltwh(&self) -> PyResult<PyBoundingBox> {
673            let r = BoundingBox::try_from(&self.0);
674            if let Ok(res) = r {
675                Ok(PyBoundingBox(res))
676            } else {
677                Err(PyAttributeError::new_err(format!("{r:?}")))
678            }
679        }
680
681        pub fn gen_vertices(&mut self) {
682            self.0.gen_vertices();
683        }
684
685        pub fn get_vertices(&self) -> PyPolygon {
686            PyPolygon(self.0.get_vertices())
687        }
688
689        /// Sets the angle
690        ///
691        #[pyo3(signature = (angle))]
692        pub fn rotate(&mut self, angle: f32) {
693            self.0.rotate_mut(angle)
694        }
695
696        #[getter]
697        pub fn get_confidence(&self) -> f32 {
698            self.0.confidence
699        }
700
701        #[setter]
702        pub fn set_confidence(&mut self, confidence: f32) {
703            self.0.set_confidence(confidence)
704        }
705
706        #[getter]
707        pub fn get_xc(&self) -> f32 {
708            self.0.xc
709        }
710
711        #[setter]
712        pub fn set_xc(&mut self, xc: f32) {
713            self.0.xc = xc;
714        }
715
716        #[getter]
717        pub fn get_yc(&self) -> f32 {
718            self.0.yc
719        }
720
721        #[setter]
722        pub fn set_yc(&mut self, yc: f32) {
723            self.0.yc = yc;
724        }
725
726        #[getter]
727        pub fn get_angle(&self) -> Option<f32> {
728            self.0.angle
729        }
730
731        #[setter]
732        pub fn set_angle(&mut self, angle: Option<f32>) {
733            self.0.angle = angle;
734        }
735
736        #[getter]
737        pub fn get_aspect(&self) -> f32 {
738            self.0.aspect
739        }
740
741        #[setter]
742        pub fn set_aspect(&mut self, aspect: f32) {
743            self.0.aspect = aspect;
744        }
745
746        #[getter]
747        pub fn get_height(&self) -> f32 {
748            self.0.height
749        }
750
751        #[setter]
752        pub fn set_height(&mut self, height: f32) {
753            self.0.height = height;
754        }
755
756        /// Constructor. Creates new generic bbox and doesn't generate vertex cache
757        ///
758        #[new]
759        #[pyo3(signature = (xc, yc, angle, aspect, height))]
760        pub fn new(xc: f32, yc: f32, angle: Option<f32>, aspect: f32, height: f32) -> Self {
761            Self(Universal2DBox::new(xc, yc, angle, aspect, height))
762        }
763
764        /// Constructor. Creates new generic bbox and doesn't generate vertex cache
765        ///
766        #[staticmethod]
767        #[pyo3(signature = (xc, yc, angle, aspect, height, confidence))]
768        pub fn new_with_confidence(
769            xc: f32,
770            yc: f32,
771            angle: Option<f32>,
772            aspect: f32,
773            height: f32,
774            confidence: f32,
775        ) -> Self {
776            assert!(
777                (0.0..=1.0).contains(&confidence),
778                "Confidence must lay between 0.0 and 1.0"
779            );
780
781            Self(Universal2DBox::new_with_confidence(
782                xc, yc, angle, aspect, height, confidence,
783            ))
784        }
785
786        /// Constructor. Creates new generic bbox and doesn't generate vertex cache
787        ///
788        #[staticmethod]
789        pub fn ltwh(left: f32, top: f32, width: f32, height: f32) -> Self {
790            Self(Universal2DBox::ltwh_with_confidence(
791                left, top, width, height, 1.0,
792            ))
793        }
794
795        /// Constructor. Creates new generic bbox and doesn't generate vertex cache
796        ///
797        #[staticmethod]
798        pub fn ltwh_with_confidence(
799            left: f32,
800            top: f32,
801            width: f32,
802            height: f32,
803            confidence: f32,
804        ) -> Self {
805            Self(Universal2DBox::ltwh_with_confidence(
806                left, top, width, height, confidence,
807            ))
808        }
809
810        pub fn area(&self) -> f32 {
811            self.0.area()
812        }
813    }
814}
815
816#[cfg(test)]
817mod tests {
818    use crate::prelude::Universal2DBox;
819    use crate::track::ObservationAttributes;
820    use crate::utils::bbox::BoundingBox;
821    use crate::EPS;
822
823    #[test]
824    fn test_radius() {
825        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
826        let r = bb1.get_radius();
827        assert!((r - 5.0).abs() < EPS);
828    }
829
830    #[test]
831    fn test_not_too_far() {
832        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
833        let bb2 = BoundingBox::new(6.0, 0.0, 6.0, 8.0).as_xyaah();
834        assert!(!Universal2DBox::too_far(&bb1, &bb2));
835    }
836
837    #[test]
838    fn test_same() {
839        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
840        assert!(!Universal2DBox::too_far(&bb1, &bb1));
841    }
842
843    #[test]
844    fn test_too_far() {
845        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
846        let bb2 = BoundingBox::new(10.1, 0.0, 6.0, 8.0).as_xyaah();
847        assert!(Universal2DBox::too_far(&bb1, &bb2));
848    }
849
850    #[test]
851    fn dist_same() {
852        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
853        assert!(Universal2DBox::dist_in_2r(&bb1, &bb1) < EPS);
854    }
855
856    #[test]
857    fn dist_less_1() {
858        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
859        let bb2 = BoundingBox::new(6.0, 0.0, 6.0, 8.0).as_xyaah();
860        let d = Universal2DBox::dist_in_2r(&bb1, &bb2);
861        assert!((d - 0.6).abs() < EPS);
862    }
863
864    #[test]
865    fn dist_is_1() {
866        let bb1 = BoundingBox::new(0.0, 0.0, 6.0, 8.0).as_xyaah();
867        let bb2 = BoundingBox::new(10.0, 0.0, 6.0, 8.0).as_xyaah();
868        let d = Universal2DBox::dist_in_2r(&bb1, &bb2);
869        assert!((d - 1.0).abs() < EPS);
870    }
871
872    #[test]
873    fn test_iou() {
874        let bb1 = BoundingBox::new(-1.0, -1.0, 2.0, 2.0);
875
876        let bb2 = BoundingBox::new(-0.9, -0.9, 2.0, 2.0);
877        let bb3 = BoundingBox::new(1.0, 1.0, 3.0, 3.0);
878
879        assert!(BoundingBox::calculate_metric_object(&Some(&bb1), &Some(&bb1)).unwrap() > 0.999);
880        assert!(BoundingBox::calculate_metric_object(&Some(&bb2), &Some(&bb2)).unwrap() > 0.999);
881        assert!(BoundingBox::calculate_metric_object(&Some(&bb1), &Some(&bb2)).unwrap() > 0.8);
882        assert!(BoundingBox::calculate_metric_object(&Some(&bb1), &Some(&bb3)).unwrap() < 0.001);
883        assert!(BoundingBox::calculate_metric_object(&Some(&bb2), &Some(&bb3)).unwrap() < 0.001);
884    }
885}