use crate::TensorDyn;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[repr(C)]
pub struct BoundingBox {
pub xmin: f32,
pub ymin: f32,
pub xmax: f32,
pub ymax: f32,
}
impl BoundingBox {
pub fn new(xmin: f32, ymin: f32, xmax: f32, ymax: f32) -> Self {
Self {
xmin,
ymin,
xmax,
ymax,
}
}
pub fn to_canonical(&self) -> Self {
let xmin = self.xmin.min(self.xmax);
let xmax = self.xmin.max(self.xmax);
let ymin = self.ymin.min(self.ymax);
let ymax = self.ymin.max(self.ymax);
BoundingBox {
xmin,
ymin,
xmax,
ymax,
}
}
}
impl From<BoundingBox> for [f32; 4] {
fn from(b: BoundingBox) -> Self {
[b.xmin, b.ymin, b.xmax, b.ymax]
}
}
impl From<[f32; 4]> for BoundingBox {
fn from(arr: [f32; 4]) -> Self {
BoundingBox {
xmin: arr[0],
ymin: arr[1],
xmax: arr[2],
ymax: arr[3],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct DetectBox {
pub bbox: BoundingBox,
pub score: f32,
pub label: usize,
}
#[derive(Debug)]
pub struct Segmentation {
pub xmin: f32,
pub ymin: f32,
pub xmax: f32,
pub ymax: f32,
pub segmentation: TensorDyn,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProtoLayout {
Nhwc,
Nchw,
}
#[derive(Debug)]
pub struct ProtoData {
pub mask_coefficients: TensorDyn,
pub protos: TensorDyn,
pub layout: ProtoLayout,
}
#[must_use]
#[inline]
pub fn unletter_norm(b: BoundingBox, lb: [f32; 4]) -> BoundingBox {
let b = b.to_canonical();
let [lx0, ly0, lx1, ly1] = lb;
let inv_w = if lx1 > lx0 { 1.0 / (lx1 - lx0) } else { 1.0 };
let inv_h = if ly1 > ly0 { 1.0 / (ly1 - ly0) } else { 1.0 };
BoundingBox {
xmin: ((b.xmin - lx0) * inv_w).clamp(0.0, 1.0),
ymin: ((b.ymin - ly0) * inv_h).clamp(0.0, 1.0),
xmax: ((b.xmax - lx0) * inv_w).clamp(0.0, 1.0),
ymax: ((b.ymax - ly0) * inv_h).clamp(0.0, 1.0),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounding_box_is_c_compatible() {
assert_eq!(std::mem::size_of::<BoundingBox>(), 16);
assert_eq!(std::mem::align_of::<BoundingBox>(), 4);
}
#[test]
fn bounding_box_round_trips_through_an_array() {
let bb = BoundingBox::new(0.1, 0.2, 0.3, 0.4);
let arr: [f32; 4] = bb.into();
assert_eq!(BoundingBox::from(arr), bb);
}
#[test]
fn unletter_norm_is_identity_for_a_full_frame_letterbox() {
let bb = BoundingBox::new(0.25, 0.25, 0.75, 0.75);
assert_eq!(unletter_norm(bb, [0.0, 0.0, 1.0, 1.0]), bb);
}
#[test]
fn unletter_norm_canonicalises_and_clamps() {
let got = unletter_norm(
BoundingBox::new(0.9, 0.9, 0.1, 0.1),
[0.0, 0.125, 1.0, 0.875],
);
assert!(got.xmin <= got.xmax && got.ymin <= got.ymax);
for v in [got.xmin, got.ymin, got.xmax, got.ymax] {
assert!((0.0..=1.0).contains(&v), "{v} escaped [0,1]");
}
}
#[test]
fn unletter_norm_survives_a_degenerate_letterbox_axis() {
let got = unletter_norm(BoundingBox::new(0.2, 0.2, 0.8, 0.8), [0.5, 0.0, 0.5, 1.0]);
for v in [got.xmin, got.ymin, got.xmax, got.ymax] {
assert!(v.is_finite(), "degenerate axis produced {v}");
}
}
}