use alloc::{vec, vec::Vec};
use super::{backend::Backend, DType, Int, Tensor, TensorData};
#[derive(Clone, Copy, Debug)]
pub struct NmsOptions {
pub iou_threshold: f32,
pub score_threshold: f32,
pub max_output_boxes: usize,
}
impl Default for NmsOptions {
fn default() -> Self {
Self { iou_threshold: 0.5, score_threshold: 0.0, max_output_boxes: 0 }
}
}
pub trait Nms<B: Backend> {
fn nms(self, scores: Tensor<B, 1>, options: NmsOptions) -> Tensor<B, 1, Int>;
}
impl<B: Backend> Nms<B> for Tensor<B, 2> {
fn nms(self, scores: Tensor<B, 1>, options: NmsOptions) -> Tensor<B, 1, Int> {
let context = self.autodiff_context().merge(scores.autodiff_context());
let [count, coordinates] = self.dims();
assert_eq!(coordinates, 4, "NMS requires four coordinates per box");
assert_eq!(scores.dims(), [count], "NMS requires one score per box");
let device = self.device();
assert_eq!(scores.device(), device, "NMS inputs must share a device");
assert!(!matches!(self.dtype(), DType::QFloat(_)), "NMS does not support quantized boxes");
if count == 0 { return Tensor::<B, 1, Int>::zeros([0], &device).with_autodiff_context(context); }
let boxes = self.into_data().convert::<f32>().into_vec::<f32>().expect("NMS boxes");
let scores = scores.into_data().convert::<f32>().into_vec::<f32>().expect("NMS scores");
let mut order: Vec<_> = (0..count).filter(|&i| scores[i] >= options.score_threshold).collect();
order.sort_by(|&a, &b| scores[b].total_cmp(&scores[a]));
let mut suppressed = vec![false; count];
let mut keep = Vec::new();
for (position, &index) in order.iter().enumerate() {
if suppressed[index] { continue; }
keep.push(index as i64);
if options.max_output_boxes > 0 && keep.len() >= options.max_output_boxes { break; }
let a = &boxes[index * 4..index * 4 + 4];
let area_a = (a[2] - a[0]) * (a[3] - a[1]);
for &other in &order[position + 1..] {
if suppressed[other] { continue; }
let b = &boxes[other * 4..other * 4 + 4];
let width = (a[2].min(b[2]) - a[0].max(b[0])).max(0.0);
let height = (a[3].min(b[3]) - a[1].max(b[1])).max(0.0);
let intersection = width * height;
let union = area_a + (b[2] - b[0]) * (b[3] - b[1]) - intersection;
suppressed[other] = intersection / union > options.iou_threshold;
}
}
let count = keep.len();
Tensor::<B, 1, Int>::from_data(TensorData::new(keep, [count]), &device).with_autodiff_context(context)
}
}