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#[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#[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 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 pub fn rotate_mut(&mut self, angle: f32) {
203 self.angle = Some(angle)
204 }
205
206 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
383impl 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}