use core::cmp::Ordering;
use crate::{CoreError, Detection, ios, iou};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OverlapMetric {
IoU,
IoS,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NmsMode {
ClassAware,
ClassAgnostic,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NmsConfig {
pub threshold: f32,
pub mode: NmsMode,
pub metric: OverlapMetric,
}
pub fn nms_in_place(
detections: &mut [Detection],
order_scratch: &mut [usize],
suppressed_scratch: &mut [bool],
config: NmsConfig,
) -> Result<usize, CoreError> {
if !config.threshold.is_finite() || !(0.0..=1.0).contains(&config.threshold) {
return Err(CoreError::InvalidThreshold);
}
let len = detections.len();
if order_scratch.len() < len || suppressed_scratch.len() < len {
return Err(CoreError::InsufficientScratch);
}
let order = &mut order_scratch[..len];
let suppressed = &mut suppressed_scratch[..len];
for (index, slot) in order.iter_mut().enumerate() {
*slot = index;
}
for value in suppressed.iter_mut() {
*value = false;
}
order.sort_unstable_by(|left, right| {
detections[*right]
.score()
.partial_cmp(&detections[*left].score())
.unwrap_or(Ordering::Equal)
.then_with(|| left.cmp(right))
});
for candidate_position in 0..len {
let candidate_index = order[candidate_position];
if suppressed[candidate_index] {
continue;
}
for contender_index in order.iter().skip(candidate_position + 1).copied() {
if suppressed[contender_index]
|| (config.mode == NmsMode::ClassAware
&& detections[candidate_index].class_id()
!= detections[contender_index].class_id())
{
continue;
}
let overlap = match config.metric {
OverlapMetric::IoU => iou(
detections[candidate_index].bbox(),
detections[contender_index].bbox(),
),
OverlapMetric::IoS => ios(
detections[candidate_index].bbox(),
detections[contender_index].bbox(),
),
};
if overlap > config.threshold {
suppressed[contender_index] = true;
}
}
}
let mut kept = 0;
for index in 0..len {
if !suppressed[index] {
let detection = detections[index];
detections[kept] = detection;
kept += 1;
}
}
Ok(kept)
}