feat: Initial commit
This commit is contained in:
708
bbox/src/lib.rs
Normal file
708
bbox/src/lib.rs
Normal file
@@ -0,0 +1,708 @@
|
||||
pub mod traits;
|
||||
|
||||
/// A bounding box of co-ordinates whose origin is at the top-left corner.
|
||||
#[derive(
|
||||
Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Hash, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct BBox<T = f32> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
impl<T> From<[T; 4]> for BBox<T> {
|
||||
fn from([x, y, width, height]: [T; 4]) -> Self {
|
||||
Self {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> BBox<T> {
|
||||
pub fn new(x: T, y: T, width: T, height: T) -> Self {
|
||||
Self {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Casts the internal values to another type using [as] keyword
|
||||
pub fn cast<T2>(self) -> BBox<T2>
|
||||
where
|
||||
T: num::cast::AsPrimitive<T2>,
|
||||
T2: Copy + 'static,
|
||||
{
|
||||
BBox {
|
||||
x: self.x.as_(),
|
||||
y: self.y.as_(),
|
||||
width: self.width.as_(),
|
||||
height: self.height.as_(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clamps all the internal values to the given min and max.
|
||||
pub fn clamp(&self, min: T, max: T) -> Self
|
||||
where
|
||||
T: std::cmp::PartialOrd,
|
||||
{
|
||||
Self {
|
||||
x: num::clamp(self.x, min, max),
|
||||
y: num::clamp(self.y, min, max),
|
||||
width: num::clamp(self.width, min, max),
|
||||
height: num::clamp(self.height, min, max),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_box(&self, bbox: BBox<T>) -> Self
|
||||
where
|
||||
T: std::cmp::PartialOrd,
|
||||
T: num::Zero,
|
||||
T: core::ops::Add<Output = T>,
|
||||
T: core::ops::Sub<Output = T>,
|
||||
{
|
||||
let x1 = num::clamp(self.x1(), bbox.x1(), bbox.x2());
|
||||
let y1 = num::clamp(self.y1(), bbox.y1(), bbox.y2());
|
||||
let x2 = num::clamp(self.x2(), bbox.x1(), bbox.x2());
|
||||
let y2 = num::clamp(self.y2(), bbox.y1(), bbox.y2());
|
||||
Self::new_xyxy(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
pub fn normalize(&self, width: T, height: T) -> Self
|
||||
where
|
||||
T: core::ops::Div<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x / width,
|
||||
y: self.y / height,
|
||||
width: self.width / width,
|
||||
height: self.height / height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize after casting to float
|
||||
pub fn normalize_f64(&self, width: T, height: T) -> BBox<f64>
|
||||
where
|
||||
T: core::ops::Div<Output = T> + Copy,
|
||||
T: num::cast::AsPrimitive<f64>,
|
||||
{
|
||||
BBox {
|
||||
x: self.x.as_() / width.as_(),
|
||||
y: self.y.as_() / height.as_(),
|
||||
width: self.width.as_() / width.as_(),
|
||||
height: self.height.as_() / height.as_(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn denormalize(&self, width: T, height: T) -> Self
|
||||
where
|
||||
T: core::ops::Mul<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x * width,
|
||||
y: self.y * height,
|
||||
width: self.width * width,
|
||||
height: self.height * height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> T {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn width(&self) -> T {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn padding(&self, padding: T) -> Self
|
||||
where
|
||||
T: core::ops::Add<Output = T> + core::ops::Sub<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x - padding,
|
||||
y: self.y - padding,
|
||||
width: self.width + padding + padding,
|
||||
height: self.height + padding + padding,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn padding_height(&self, padding: T) -> Self
|
||||
where
|
||||
T: core::ops::Add<Output = T> + core::ops::Sub<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x,
|
||||
y: self.y - padding,
|
||||
width: self.width,
|
||||
height: self.height + padding + padding,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn padding_width(&self, padding: T) -> Self
|
||||
where
|
||||
T: core::ops::Add<Output = T> + core::ops::Sub<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x - padding,
|
||||
y: self.y,
|
||||
width: self.width + padding + padding,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
// Enlarge / shrink the bounding box by a factor while
|
||||
// keeping the center point and the aspect ratio fixed
|
||||
pub fn scale(&self, factor: T) -> Self
|
||||
where
|
||||
T: core::ops::Mul<Output = T>,
|
||||
T: core::ops::Sub<Output = T>,
|
||||
T: core::ops::Add<Output = T>,
|
||||
T: core::ops::Div<Output = T>,
|
||||
T: num::One + Copy,
|
||||
{
|
||||
let two = num::one::<T>() + num::one::<T>();
|
||||
let width = self.width * factor;
|
||||
let height = self.height * factor;
|
||||
let width_inc = width - self.width;
|
||||
let height_inc = height - self.height;
|
||||
Self {
|
||||
x: self.x - width_inc / two,
|
||||
y: self.y - height_inc / two,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_x(&self, factor: T) -> Self
|
||||
where
|
||||
T: core::ops::Mul<Output = T>
|
||||
+ core::ops::Sub<Output = T>
|
||||
+ core::ops::Add<Output = T>
|
||||
+ core::ops::Div<Output = T>
|
||||
+ num::One
|
||||
+ Copy,
|
||||
{
|
||||
let two = num::one::<T>() + num::one::<T>();
|
||||
let width = self.width * factor;
|
||||
let width_inc = width - self.width;
|
||||
Self {
|
||||
x: self.x - width_inc / two,
|
||||
y: self.y,
|
||||
width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_y(&self, factor: T) -> Self
|
||||
where
|
||||
T: core::ops::Mul<Output = T>
|
||||
+ core::ops::Sub<Output = T>
|
||||
+ core::ops::Add<Output = T>
|
||||
+ core::ops::Div<Output = T>
|
||||
+ num::One
|
||||
+ Copy,
|
||||
{
|
||||
let two = num::one::<T>() + num::one::<T>();
|
||||
let height = self.height * factor;
|
||||
let height_inc = height - self.height;
|
||||
Self {
|
||||
x: self.x,
|
||||
y: self.y - height_inc / two,
|
||||
width: self.width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(&self, offset: Point<T>) -> Self
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x + offset.x,
|
||||
y: self.y + offset.y,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate the bounding box by the given offset
|
||||
/// if they are in the same scale
|
||||
pub fn translate(&self, bbox: Self) -> Self
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: self.x + bbox.x,
|
||||
y: self.y + bbox.y,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_top_left(&self, top_left: Point<T>) -> Self {
|
||||
Self {
|
||||
x: top_left.x,
|
||||
y: top_left.y,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center(&self) -> Point<T>
|
||||
where
|
||||
T: core::ops::Add<Output = T> + core::ops::Div<Output = T> + Copy,
|
||||
T: num::One,
|
||||
{
|
||||
let two = T::one() + T::one();
|
||||
Point::new(self.x + self.width / two, self.y + self.height / two)
|
||||
}
|
||||
|
||||
pub fn area(&self) -> T
|
||||
where
|
||||
T: core::ops::Mul<Output = T> + Copy,
|
||||
{
|
||||
self.width * self.height
|
||||
}
|
||||
|
||||
// Corresponds to self.x1() and self.y1()
|
||||
pub fn top_left(&self) -> Point<T> {
|
||||
Point::new(self.x, self.y)
|
||||
}
|
||||
|
||||
pub fn top_right(&self) -> Point<T>
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
Point::new(self.x + self.width, self.y)
|
||||
}
|
||||
|
||||
pub fn bottom_left(&self) -> Point<T>
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
Point::new(self.x, self.y + self.height)
|
||||
}
|
||||
|
||||
// Corresponds to self.x2() and self.y2()
|
||||
pub fn bottom_right(&self) -> Point<T>
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
Point::new(self.x + self.width, self.y + self.height)
|
||||
}
|
||||
|
||||
pub const fn x1(&self) -> T {
|
||||
self.x
|
||||
}
|
||||
|
||||
pub const fn y1(&self) -> T {
|
||||
self.y
|
||||
}
|
||||
|
||||
pub fn x2(&self) -> T
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
self.x + self.width
|
||||
}
|
||||
|
||||
pub fn y2(&self) -> T
|
||||
where
|
||||
T: core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
self.y + self.height
|
||||
}
|
||||
|
||||
pub fn overlap(&self, other: &Self) -> T
|
||||
where
|
||||
T: std::cmp::PartialOrd
|
||||
+ traits::min::Min
|
||||
+ traits::max::Max
|
||||
+ num::Zero
|
||||
+ core::ops::Add<Output = T>
|
||||
+ core::ops::Sub<Output = T>
|
||||
+ core::ops::Mul<Output = T>
|
||||
+ Copy,
|
||||
{
|
||||
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);
|
||||
let width = (x2 - x1).max(T::zero());
|
||||
let height = (y2 - y1).max(T::zero());
|
||||
width * height
|
||||
}
|
||||
|
||||
pub fn iou(&self, other: &Self) -> T
|
||||
where
|
||||
T: std::cmp::Ord
|
||||
+ num::Zero
|
||||
+ traits::min::Min
|
||||
+ traits::max::Max
|
||||
+ core::ops::Add<Output = T>
|
||||
+ core::ops::Sub<Output = T>
|
||||
+ core::ops::Mul<Output = T>
|
||||
+ core::ops::Div<Output = T>
|
||||
+ Copy,
|
||||
{
|
||||
let overlap = self.overlap(other);
|
||||
let union = self.area() + other.area() - overlap;
|
||||
overlap / union
|
||||
}
|
||||
|
||||
pub fn contains(&self, point: Point<T>) -> bool
|
||||
where
|
||||
T: std::cmp::PartialOrd + core::ops::Add<Output = T> + Copy,
|
||||
{
|
||||
point.x >= self.x
|
||||
&& point.x <= self.x + self.width
|
||||
&& point.y >= self.y
|
||||
&& point.y <= self.y + self.height
|
||||
}
|
||||
|
||||
pub fn contains_bbox(&self, other: Self) -> bool
|
||||
where
|
||||
T: std::cmp::PartialOrd + Copy,
|
||||
T: core::ops::Add<Output = T>,
|
||||
{
|
||||
self.contains(other.top_left())
|
||||
&& self.contains(other.top_right())
|
||||
&& self.contains(other.bottom_left())
|
||||
&& self.contains(other.bottom_right())
|
||||
}
|
||||
|
||||
pub fn new_xywh(x: T, y: T, width: T, height: T) -> Self {
|
||||
Self {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
pub fn new_xyxy(x1: T, y1: T, x2: T, y2: T) -> Self
|
||||
where
|
||||
T: core::ops::Sub<Output = T> + Copy,
|
||||
{
|
||||
Self {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2 - x1,
|
||||
height: y2 - y1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn containing(box1: Self, box2: Self) -> Self
|
||||
where
|
||||
T: traits::min::Min + traits::max::Max + Copy,
|
||||
T: core::ops::Sub<Output = T>,
|
||||
T: core::ops::Add<Output = T>,
|
||||
{
|
||||
let x1 = box1.x.min(box2.x);
|
||||
let y1 = box1.y.min(box2.y);
|
||||
let x2 = box1.x2().max(box2.x2());
|
||||
let y2 = box1.y2().max(box2.y2());
|
||||
Self::new_xyxy(x1, y1, x2, y2)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::ops::Sub<Output = T> + Copy> core::ops::Sub<T> for BBox<T> {
|
||||
type Output = BBox<T>;
|
||||
fn sub(self, rhs: T) -> Self::Output {
|
||||
BBox {
|
||||
x: self.x - rhs,
|
||||
y: self.y - rhs,
|
||||
width: self.width - rhs,
|
||||
height: self.height - rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::ops::Add<Output = T> + Copy> core::ops::Add<T> for BBox<T> {
|
||||
type Output = BBox<T>;
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
BBox {
|
||||
x: self.x + rhs,
|
||||
y: self.y + rhs,
|
||||
width: self.width + rhs,
|
||||
height: self.height + rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: core::ops::Mul<Output = T> + Copy> core::ops::Mul<T> for BBox<T> {
|
||||
type Output = BBox<T>;
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
BBox {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
width: self.width * rhs,
|
||||
height: self.height * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: core::ops::Div<Output = T> + Copy> core::ops::Div<T> for BBox<T> {
|
||||
type Output = BBox<T>;
|
||||
fn div(self, rhs: T) -> Self::Output {
|
||||
BBox {
|
||||
x: self.x / rhs,
|
||||
y: self.y / rhs,
|
||||
width: self.width / rhs,
|
||||
height: self.height / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core::ops::Add<BBox<T>> for BBox<T>
|
||||
where
|
||||
T: core::ops::Sub<Output = T>
|
||||
+ core::ops::Add<Output = T>
|
||||
+ traits::min::Min
|
||||
+ traits::max::Max
|
||||
+ Copy,
|
||||
{
|
||||
type Output = BBox<T>;
|
||||
fn add(self, rhs: BBox<T>) -> Self::Output {
|
||||
let x1 = self.x1().min(rhs.x1());
|
||||
let y1 = self.y1().min(rhs.y1());
|
||||
let x2 = self.x2().max(rhs.x2());
|
||||
let y2 = self.y2().max(rhs.y2());
|
||||
BBox::new_xyxy(x1, y1, x2, y2)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bbox_add() {
|
||||
let bbox1: BBox<usize> = BBox::new_xyxy(0, 0, 10, 10);
|
||||
let bbox2: BBox<usize> = BBox::new_xyxy(5, 5, 15, 15);
|
||||
let bbox3: BBox<usize> = bbox1 + bbox2;
|
||||
assert_eq!(bbox3, BBox::new_xyxy(0, 0, 15, 15).cast());
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash,
|
||||
)]
|
||||
pub struct Point<T = f32> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Point<T> {
|
||||
pub const fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub const fn x(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
self.x
|
||||
}
|
||||
|
||||
pub const fn y(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
self.y
|
||||
}
|
||||
|
||||
pub fn cast<T2>(&self) -> Point<T2>
|
||||
where
|
||||
T: num::cast::AsPrimitive<T2>,
|
||||
T2: Copy + 'static,
|
||||
{
|
||||
Point {
|
||||
x: self.x.as_(),
|
||||
y: self.y.as_(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::ops::Sub<T, Output = T> + Copy> core::ops::Sub<Point<T>> for Point<T> {
|
||||
type Output = Point<T>;
|
||||
fn sub(self, rhs: Point<T>) -> Self::Output {
|
||||
Point {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::ops::Add<T, Output = T> + Copy> core::ops::Add<Point<T>> for Point<T> {
|
||||
type Output = Point<T>;
|
||||
fn add(self, rhs: Point<T>) -> Self::Output {
|
||||
Point {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::ops::Sub<Output = T> + Copy> Point<T> {
|
||||
/// If both the boxes are in the same scale then make the translation of the origin to the
|
||||
/// other box
|
||||
pub fn with_origin(&self, origin: Self) -> Self {
|
||||
*self - origin
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: core::ops::Add<Output = T> + Copy> Point<T> {
|
||||
pub fn translate(&self, point: Point<T>) -> Self {
|
||||
*self + point
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: num::Zero> BBox<I>
|
||||
where
|
||||
I: num::cast::AsPrimitive<usize>,
|
||||
{
|
||||
pub fn zeros_ndarray_2d<T: num::Zero + Copy>(&self) -> ndarray::Array2<T> {
|
||||
ndarray::Array2::<T>::zeros((self.height.as_(), self.width.as_()))
|
||||
}
|
||||
pub fn zeros_ndarray_3d<T: num::Zero + Copy>(&self, channels: usize) -> ndarray::Array3<T> {
|
||||
ndarray::Array3::<T>::zeros((self.height.as_(), self.width.as_(), channels))
|
||||
}
|
||||
pub fn ones_ndarray_2d<T: num::One + Copy>(&self) -> ndarray::Array2<T> {
|
||||
ndarray::Array2::<T>::ones((self.height.as_(), self.width.as_()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: num::Float> BBox<T> {
|
||||
pub fn round(&self) -> Self {
|
||||
Self {
|
||||
x: self.x.round(),
|
||||
y: self.y.round(),
|
||||
width: self.width.round(),
|
||||
height: self.height.round(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bbox_clamp_tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
pub fn bbox_test_clamp_box() {
|
||||
let large_box = BBox::new(0, 0, 100, 100);
|
||||
let small_box = BBox::new(10, 10, 20, 20);
|
||||
let clamped = large_box.clamp_box(small_box);
|
||||
assert_eq!(clamped, small_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_test_clamp_box_offset() {
|
||||
let box_a = BBox::new(0, 0, 100, 100);
|
||||
let box_b = BBox::new(-10, -10, 20, 20);
|
||||
let clamped = box_b.clamp_box(box_a);
|
||||
let expected = BBox::new(0, 0, 10, 10);
|
||||
assert_eq!(expected, clamped);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bbox_padding_tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
pub fn bbox_test_padding() {
|
||||
let bbox = BBox::new(0, 0, 10, 10);
|
||||
let padded = bbox.padding(2);
|
||||
assert_eq!(padded, BBox::new(-2, -2, 14, 14));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_test_padding_height() {
|
||||
let bbox = BBox::new(0, 0, 10, 10);
|
||||
let padded = bbox.padding_height(2);
|
||||
assert_eq!(padded, BBox::new(0, -2, 10, 14));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_test_padding_width() {
|
||||
let bbox = BBox::new(0, 0, 10, 10);
|
||||
let padded = bbox.padding_width(2);
|
||||
assert_eq!(padded, BBox::new(-2, 0, 14, 10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_test_clamped_padding() {
|
||||
let bbox = BBox::new(0, 0, 10, 10);
|
||||
let padded = bbox.padding(2);
|
||||
let clamp = BBox::new(0, 0, 12, 12);
|
||||
let clamped = padded.clamp_box(clamp);
|
||||
assert_eq!(clamped, clamp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_clamp_failure() {
|
||||
let og = BBox::new(475.0, 79.625, 37.0, 282.15);
|
||||
let padded = BBox {
|
||||
x: 471.3,
|
||||
y: 51.412499999999994,
|
||||
width: 40.69999999999999,
|
||||
height: 338.54999999999995,
|
||||
};
|
||||
let clamp = BBox::new(0.0, 0.0, 512.0, 512.0);
|
||||
let sus = padded.clamp_box(clamp);
|
||||
assert!(clamp.contains_bbox(sus));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod bbox_scale_tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
pub fn bbox_test_scale_int() {
|
||||
let bbox = BBox::new(0, 0, 10, 10);
|
||||
let scaled = bbox.scale(2);
|
||||
assert_eq!(scaled, BBox::new(-5, -5, 20, 20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_test_scale_float() {
|
||||
let bbox = BBox::new(0, 0, 10, 10).cast();
|
||||
let scaled = bbox.scale(1.05); // 5% increase
|
||||
let l = 10.0 * 0.05;
|
||||
assert_eq!(scaled, BBox::new(-l / 2.0, -l / 2.0, 10.0 + l, 10.0 + l));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_test_scale_float_negative() {
|
||||
let bbox = BBox::new(0, 0, 10, 10).cast();
|
||||
let scaled = bbox.scale(0.95); // 5% decrease
|
||||
let l = -10.0 * 0.05;
|
||||
assert_eq!(scaled, BBox::new(-l / 2.0, -l / 2.0, 10.0 + l, 10.0 + l));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bbox_scale_float() {
|
||||
let bbox = BBox::new_xywh(0, 0, 200, 200);
|
||||
let scaled = bbox.cast::<f64>().scale(1.1).cast::<i32>().clamp(0, 1000);
|
||||
let expected = BBox::new(0, 0, 220, 220);
|
||||
assert_eq!(scaled, expected);
|
||||
}
|
||||
#[test]
|
||||
pub fn add_padding_bbox_example() {
|
||||
// let result = add_padding_bbox(
|
||||
// vec![Rect::new(100, 200, 300, 400)],
|
||||
// (0.1, 0.1),
|
||||
// (1000, 1000),
|
||||
// );
|
||||
// assert_eq!(result[0], Rect::new(70, 160, 360, 480));
|
||||
let bbox = BBox::new(100, 200, 300, 400);
|
||||
let scaled = bbox.cast::<f64>().scale(1.2).cast::<i32>().clamp(0, 1000);
|
||||
assert_eq!(bbox, BBox::new(100, 200, 300, 400));
|
||||
assert_eq!(scaled, BBox::new(70, 160, 360, 480));
|
||||
}
|
||||
#[test]
|
||||
pub fn scale_bboxes() {
|
||||
// let result = scale_bboxes(Rect::new(100, 200, 300, 400), (1000, 1000), (500, 500));
|
||||
// assert_eq!(result[0], Rect::new(200, 400, 600, 800));
|
||||
let bbox = BBox::new(100, 200, 300, 400);
|
||||
let scaled = bbox.scale(2);
|
||||
assert_eq!(scaled, BBox::new(200, 400, 600, 800));
|
||||
}
|
||||
}
|
||||
2
bbox/src/traits.rs
Normal file
2
bbox/src/traits.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod max;
|
||||
pub mod min;
|
||||
27
bbox/src/traits/max.rs
Normal file
27
bbox/src/traits/max.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
pub trait Max: Sized + Copy {
|
||||
fn max(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_max {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl Max for $t {
|
||||
fn max(self, other: Self) -> Self {
|
||||
Ord::max(self, other)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(float $($t:ty),*) => {
|
||||
$(
|
||||
impl Max for $t {
|
||||
fn max(self, other: Self) -> Self {
|
||||
Self::max(self, other)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_max!(usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128);
|
||||
impl_max!(float f32, f64);
|
||||
27
bbox/src/traits/min.rs
Normal file
27
bbox/src/traits/min.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
pub trait Min: Sized + Copy {
|
||||
fn min(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_min {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl Min for $t {
|
||||
fn min(self, other: Self) -> Self {
|
||||
Ord::min(self, other)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(float $($t:ty),*) => {
|
||||
$(
|
||||
impl Min for $t {
|
||||
fn min(self, other: Self) -> Self {
|
||||
Self::min(self, other)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_min!(usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128);
|
||||
impl_min!(float f32, f64);
|
||||
Reference in New Issue
Block a user