use std::collections::HashMap;
use crate::coco::COCO;
use crate::params::{IouType, Params};
use crate::primitives::greedy::{GtMasks, ThreshMatrix};
use crate::types::Annotation;
use super::EvalMode;
pub(super) struct PairCell<'a> {
img_id: u64,
cat_id: u64,
max_det: usize,
gt_anns: Vec<&'a Annotation>,
gt_iou_indices: Vec<usize>,
gt_raw_count: usize,
dt_anns: Vec<&'a Annotation>,
dt_iou_indices: Vec<usize>,
dt_ids: Vec<u64>,
dt_scores: Vec<f64>,
iou_matrix: Option<&'a IouMatrix>,
}
struct GtView<'a> {
anns: &'a [&'a Annotation],
order: Vec<usize>,
iou_indices: &'a [usize],
ignore_sorted: Vec<bool>,
in_denominator_sorted: Vec<bool>,
iscrowd_sorted: Vec<bool>,
is_group_of_sorted: Vec<bool>,
num_not_ignored: usize,
}
impl GtView<'_> {
fn len(&self) -> usize {
self.anns.len()
}
fn id_at(&self, sorted_idx: usize) -> u64 {
self.anns[self.order[sorted_idx]].id
}
fn sorted_ids(&self) -> Vec<u64> {
(0..self.len()).map(|gi| self.id_at(gi)).collect()
}
}
struct DtView<'a> {
anns: &'a [&'a Annotation],
iou_indices: &'a [usize],
area_ignore: Vec<bool>,
}
impl DtView<'_> {
fn len(&self) -> usize {
self.anns.len()
}
}
struct MatchOutcome {
dt_matches: ThreshMatrix<u64>,
gt_matches: ThreshMatrix<u64>,
dt_matched: ThreshMatrix<bool>,
gt_matched: ThreshMatrix<bool>,
dt_ignore: ThreshMatrix<bool>,
}
pub(super) fn gather_pair<'a>(
ctx: &EvalImgContext<'a>,
img_id: u64,
cat_id: u64,
max_det: usize,
) -> Option<PairCell<'a>> {
use super::COCOeval;
let gt_ids = COCOeval::get_anns_static(ctx.coco_gt, ctx.params, img_id, cat_id);
let dt_ids = COCOeval::get_anns_static(ctx.coco_dt, ctx.params, img_id, cat_id);
if gt_ids.is_empty() && dt_ids.is_empty() {
return None;
}
let (gt_iou_indices, gt_anns): (Vec<usize>, Vec<&Annotation>) = gt_ids
.iter()
.enumerate()
.filter_map(|(iou_idx, &id)| Some((iou_idx, ctx.coco_gt.get_ann(id)?)))
.unzip();
let mut with_iou_idx: Vec<(usize, &Annotation)> = dt_ids
.iter()
.enumerate()
.filter_map(|(iou_idx, &id)| Some((iou_idx, ctx.coco_dt.get_ann(id)?)))
.collect();
with_iou_idx.sort_by(|a, b| {
b.1.score
.unwrap_or(0.0)
.partial_cmp(&a.1.score.unwrap_or(0.0))
.unwrap_or(std::cmp::Ordering::Equal)
});
with_iou_idx.truncate(max_det);
let (dt_iou_indices, dt_anns): (Vec<usize>, Vec<&Annotation>) =
with_iou_idx.into_iter().unzip();
let dt_ids: Vec<u64> = dt_anns.iter().map(|a| a.id).collect();
let dt_scores: Vec<f64> = dt_anns.iter().map(|a| a.score.unwrap_or(0.0)).collect();
Some(PairCell {
img_id,
cat_id,
max_det,
gt_anns,
gt_iou_indices,
gt_raw_count: gt_ids.len(),
dt_anns,
dt_iou_indices,
dt_ids,
dt_scores,
iou_matrix: ctx.ious.get(&(img_id, cat_id)),
})
}
fn partition_gt<'a>(
pair: &'a PairCell<'a>,
area_rng: [f64; 2],
is_kp: bool,
is_oid: bool,
) -> GtView<'a> {
let anns = pair.gt_anns.as_slice();
let (ignore, in_denominator): (Vec<bool>, Vec<bool>) = anns
.iter()
.map(|ann| {
let a = ann.area.unwrap_or(0.0);
let area_ignore = a < area_rng[0] || a > area_rng[1];
if is_oid {
(
ann.is_group_of.unwrap_or(false) || area_ignore,
!area_ignore,
)
} else {
let mut ignore = ann.iscrowd || area_ignore;
if is_kp {
ignore = ignore || ann.num_visible_keypoints() == 0;
}
(ignore, !ignore)
}
})
.unzip();
let mut order: Vec<usize> = (0..anns.len()).collect();
order.sort_by_key(|&i| ignore[i] as u8);
let ignore_sorted: Vec<bool> = order.iter().map(|&i| ignore[i]).collect();
let in_denominator_sorted: Vec<bool> = order.iter().map(|&i| in_denominator[i]).collect();
let iscrowd_sorted: Vec<bool> = order.iter().map(|&i| anns[i].iscrowd).collect();
let is_group_of_sorted: Vec<bool> = if is_oid {
order
.iter()
.map(|&i| anns[i].is_group_of.unwrap_or(false))
.collect()
} else {
Vec::new()
};
let num_not_ignored = ignore_sorted.iter().filter(|&&x| !x).count();
GtView {
anns,
order,
iou_indices: pair.gt_iou_indices.as_slice(),
ignore_sorted,
in_denominator_sorted,
iscrowd_sorted,
is_group_of_sorted,
num_not_ignored,
}
}
fn area_filter_dt<'a>(pair: &'a PairCell<'a>, area_rng: [f64; 2]) -> DtView<'a> {
let area_ignore: Vec<bool> = pair
.dt_anns
.iter()
.map(|ann| {
let a = ann.area.unwrap_or(0.0);
a < area_rng[0] || a > area_rng[1]
})
.collect();
DtView {
anns: pair.dt_anns.as_slice(),
iou_indices: pair.dt_iou_indices.as_slice(),
area_ignore,
}
}
fn reordered_iou(iou_mat: &IouMatrix, dt: &DtView<'_>, gt: &GtView<'_>) -> Vec<f64> {
let (d, g) = (dt.len(), gt.len());
let mut flat = vec![0.0_f64; d * g];
for di in 0..d {
let Some(row) = iou_mat.get(dt.iou_indices[di]) else {
continue;
};
for (gi_sorted, &gi_orig) in gt.order.iter().enumerate() {
if let Some(&v) = row.get(gt.iou_indices[gi_orig]) {
flat[di * g + gi_sorted] = v;
}
}
}
flat
}
fn match_cell(
ctx: &EvalImgContext<'_>,
gt: &GtView<'_>,
dt: &DtView<'_>,
iou_matrix: Option<&IouMatrix>,
is_oid: bool,
) -> MatchOutcome {
let (d, g) = (dt.len(), gt.len());
let num_iou_thrs = ctx.params.iou_thrs.len();
let mut dt_matches = ThreshMatrix::new(num_iou_thrs, d, 0u64);
let mut gt_matches = ThreshMatrix::new(num_iou_thrs, g, 0u64);
let mut dt_matched = ThreshMatrix::new(num_iou_thrs, d, false);
let mut dt_ignore = ThreshMatrix::repeat_row(num_iou_thrs, &dt.area_ignore);
let Some(iou_mat) = iou_matrix else {
return MatchOutcome {
dt_matches,
gt_matches,
dt_matched,
gt_matched: ThreshMatrix::new(num_iou_thrs, g, false),
dt_ignore,
};
};
let iou_flat = reordered_iou(iou_mat, dt, gt);
let phase2_eligible: Option<Vec<bool>> =
is_oid.then(|| gt.is_group_of_sorted.iter().map(|&x| !x).collect());
let m = crate::primitives::greedy::greedy_match_masked(
&iou_flat,
d,
g,
gt.num_not_ignored,
GtMasks {
rematchable: (!is_oid).then_some(gt.iscrowd_sorted.as_slice()),
phase2_eligible: phase2_eligible.as_deref(),
},
ctx.match_floors,
);
for t_idx in 0..num_iou_thrs {
for (di, dt_ann) in dt.anns.iter().enumerate() {
if let Some(gi) = m.dt_gt[(t_idx, di)] {
dt_matches[(t_idx, di)] = gt.id_at(gi);
gt_matches[(t_idx, gi)] = dt_ann.id;
dt_matched[(t_idx, di)] = true;
dt_ignore[(t_idx, di)] = gt.ignore_sorted[gi];
}
}
}
let mut gt_matched = m.gt_matched;
if is_oid && gt.is_group_of_sorted.iter().any(|&x| x) {
for (t_idx, &iou_thr) in ctx.match_floors.iter().enumerate() {
for di in 0..d {
if dt_matched[(t_idx, di)] {
continue;
}
let row = &iou_flat[di * g..(di + 1) * g];
let Some(gi) = crate::primitives::greedy::best_above_floor(
row,
>.is_group_of_sorted,
iou_thr,
) else {
continue;
};
dt_matches[(t_idx, di)] = gt.id_at(gi);
dt_matched[(t_idx, di)] = true;
if gt_matched[(t_idx, gi)] {
dt_ignore[(t_idx, di)] = true;
} else {
dt_ignore[(t_idx, di)] = false;
gt_matches[(t_idx, gi)] = dt.anns[di].id;
gt_matched[(t_idx, gi)] = true;
}
}
}
}
MatchOutcome {
dt_matches,
gt_matches,
dt_matched,
gt_matched,
dt_ignore,
}
}
pub(super) fn evaluate_cell(
ctx: &EvalImgContext<'_>,
pair: &PairCell<'_>,
area_rng: [f64; 2],
not_exhaustive_cat: bool,
) -> Option<EvalImg> {
let is_kp = ctx.params.iou_type == IouType::Keypoints;
let is_oid = ctx.eval_mode == EvalMode::OpenImages;
let gt = partition_gt(pair, area_rng, is_kp, is_oid);
let dt = area_filter_dt(pair, area_rng);
let mut outcome = match_cell(ctx, >, &dt, pair.iou_matrix, is_oid);
if not_exhaustive_cat {
for t_idx in 0..ctx.params.iou_thrs.len() {
for di in 0..dt.len() {
if !outcome.dt_matched[(t_idx, di)] {
outcome.dt_ignore[(t_idx, di)] = true;
}
}
}
}
let has_content = gt.num_not_ignored > 0 || dt.area_ignore.iter().any(|&ignored| !ignored);
if !has_content && pair.gt_raw_count == 0 {
return None;
}
Some(EvalImg {
image_id: pair.img_id,
category_id: pair.cat_id,
area_rng,
max_det: pair.max_det,
dt_ids: pair.dt_ids.clone(),
gt_ids: gt.sorted_ids(),
dt_matches: outcome.dt_matches,
gt_matches: outcome.gt_matches,
dt_matched: outcome.dt_matched,
gt_matched: outcome.gt_matched,
dt_scores: pair.dt_scores.clone(),
gt_ignore: gt.ignore_sorted,
gt_in_denominator: gt.in_denominator_sorted,
dt_ignore: outcome.dt_ignore,
})
}
pub(in crate::detection) type IouMatrix = Vec<Vec<f64>>;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct EvalImg {
pub image_id: u64,
pub category_id: u64,
pub area_rng: [f64; 2],
pub max_det: usize,
pub dt_ids: Vec<u64>,
pub gt_ids: Vec<u64>,
pub dt_matches: ThreshMatrix<u64>,
pub gt_matches: ThreshMatrix<u64>,
pub dt_matched: ThreshMatrix<bool>,
pub gt_matched: ThreshMatrix<bool>,
pub dt_scores: Vec<f64>,
pub gt_ignore: Vec<bool>,
pub gt_in_denominator: Vec<bool>,
pub dt_ignore: ThreshMatrix<bool>,
}
impl EvalImg {
pub fn num_gt_in_denominator(&self) -> usize {
self.gt_in_denominator.iter().filter(|&&x| x).count()
}
pub fn counts_as_miss(&self, gi: usize) -> bool {
self.gt_in_denominator.get(gi).copied().unwrap_or(false)
}
}
pub(super) struct EvalImgContext<'a> {
pub(super) coco_gt: &'a COCO,
pub(super) coco_dt: &'a COCO,
pub(super) params: &'a Params,
pub(super) ious: &'a HashMap<(u64, u64), IouMatrix>,
pub(super) eval_mode: super::EvalMode,
pub(super) match_floors: &'a [f64],
}