#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BBox {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
impl BBox {
#[inline]
pub fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
Self { x, y, width, height }
}
#[inline]
pub fn from_center(cx: f32, cy: f32, width: f32, height: f32) -> Self {
Self {
x: (cx - width / 2.0).round() as i32,
y: (cy - height / 2.0).round() as i32,
width: width.round() as i32,
height: height.round() as i32,
}
}
#[inline]
pub fn area(&self) -> i32 {
self.width * self.height
}
#[inline]
pub fn center(&self) -> (f32, f32) {
(
self.x as f32 + self.width as f32 / 2.0,
self.y as f32 + self.height as f32 / 2.0,
)
}
pub fn iou(&self, other: &BBox) -> f32 {
let x1 = self.x.max(other.x);
let y1 = self.y.max(other.y);
let x2 = (self.x + self.width).min(other.x + other.width);
let y2 = (self.y + self.height).min(other.y + other.height);
if x2 <= x1 || y2 <= y1 {
return 0.0;
}
let intersection = (x2 - x1) * (y2 - y1);
let union = self.area() + other.area() - intersection;
if union <= 0 {
return 0.0;
}
intersection as f32 / union as f32
}
pub fn clamp(&self, img_width: i32, img_height: i32) -> Self {
let x = self.x.max(0);
let y = self.y.max(0);
let width = (self.width).min(img_width - x);
let height = (self.height).min(img_height - y);
Self { x, y, width, height }
}
}
#[cfg(any(feature = "opencv-backend", feature = "ort-opencv-compat"))]
mod opencv_impl {
use super::BBox;
use opencv::core::Rect;
impl From<BBox> for Rect {
fn from(bbox: BBox) -> Self {
Rect::new(bbox.x, bbox.y, bbox.width, bbox.height)
}
}
impl From<Rect> for BBox {
fn from(rect: Rect) -> Self {
BBox::new(rect.x, rect.y, rect.width, rect.height)
}
}
impl From<&Rect> for BBox {
fn from(rect: &Rect) -> Self {
BBox::new(rect.x, rect.y, rect.width, rect.height)
}
}
impl From<&BBox> for Rect {
fn from(bbox: &BBox) -> Self {
Rect::new(bbox.x, bbox.y, bbox.width, bbox.height)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bbox_new() {
let bbox = BBox::new(10, 20, 100, 200);
assert_eq!(bbox.x, 10);
assert_eq!(bbox.y, 20);
assert_eq!(bbox.width, 100);
assert_eq!(bbox.height, 200);
}
#[test]
fn test_bbox_from_center() {
let bbox = BBox::from_center(50.0, 100.0, 100.0, 200.0);
assert_eq!(bbox.x, 0);
assert_eq!(bbox.y, 0);
assert_eq!(bbox.width, 100);
assert_eq!(bbox.height, 200);
}
#[test]
fn test_bbox_area() {
let bbox = BBox::new(0, 0, 100, 200);
assert_eq!(bbox.area(), 20000);
}
#[test]
fn test_bbox_iou_no_overlap() {
let a = BBox::new(0, 0, 10, 10);
let b = BBox::new(20, 20, 10, 10);
assert_eq!(a.iou(&b), 0.0);
}
#[test]
fn test_bbox_iou_full_overlap() {
let a = BBox::new(0, 0, 10, 10);
let b = BBox::new(0, 0, 10, 10);
assert_eq!(a.iou(&b), 1.0);
}
#[test]
fn test_bbox_iou_partial_overlap() {
let a = BBox::new(0, 0, 10, 10);
let b = BBox::new(5, 5, 10, 10);
let iou = a.iou(&b);
assert!((iou - 25.0 / 175.0).abs() < 0.001);
}
#[test]
fn test_bbox_clamp() {
let bbox = BBox::new(-10, -10, 100, 100);
let clamped = bbox.clamp(50, 50);
assert_eq!(clamped.x, 0);
assert_eq!(clamped.y, 0);
assert_eq!(clamped.width, 50);
assert_eq!(clamped.height, 50);
}
}
#[cfg(all(test, any(feature = "opencv-backend", feature = "ort-opencv-compat")))]
mod opencv_tests {
use super::*;
use opencv::core::Rect;
#[test]
fn test_bbox_to_rect() {
let bbox = BBox::new(10, 20, 100, 200);
let rect: Rect = bbox.into();
assert_eq!(rect.x, 10);
assert_eq!(rect.y, 20);
assert_eq!(rect.width, 100);
assert_eq!(rect.height, 200);
}
#[test]
fn test_rect_to_bbox() {
let rect = Rect::new(10, 20, 100, 200);
let bbox: BBox = rect.into();
assert_eq!(bbox.x, 10);
assert_eq!(bbox.y, 20);
assert_eq!(bbox.width, 100);
assert_eq!(bbox.height, 200);
}
}