pub mod assignment;
pub mod bins;
pub mod confusion;
pub mod cross_class;
pub mod histogram;
pub mod params;
pub mod report;
pub mod rewrite;
pub use assignment::{assign_bins, BinAssignment, DtBin, DtBinLabel};
pub use bins::TideErrorBin;
pub use confusion::{compute_confusion_matrix, ConfusionMatrixCounts};
pub use cross_class::compute_cross_class_ious;
pub use histogram::{
compute_fp_iou_histogram_bbox, compute_fp_iou_histogram_boundary,
compute_fp_iou_histogram_segm, compute_fp_iou_histogram_with, FpIouHistogram,
};
pub use params::TideParams;
pub use report::{KernelMarker, TideConfig, TideReport};
pub use rewrite::{apply_fix, FixKind};
use std::collections::HashMap;
use crate::accumulate::{accumulate, sort_max_dets, AccumulateParams};
use crate::dataset::{CocoDataset, CocoDetections};
use crate::error::EvalError;
use crate::evaluate::{evaluate_with_retention, EvalKernel, EvaluateParams};
use crate::parity::ParityMode;
use crate::similarity::{BboxIou, BoundaryIou, SegmIou};
pub fn error_decomposition_with<K: EvalKernel>(
gt: &CocoDataset,
dt: &CocoDetections,
kernel: &K,
kernel_marker: KernelMarker,
params: TideParams<'_>,
parity_mode: ParityMode,
) -> Result<TideReport, EvalError> {
let eval_params = EvaluateParams {
iou_thresholds: params.iou_thresholds,
area_ranges: params.area_ranges,
max_dets_per_image: params.max_dets_per_image,
use_cats: params.use_cats,
retain_iou: false,
};
let (grid, cross_class) = evaluate_with_retention(gt, dt, eval_params, parity_mode, kernel)?;
let baseline_map = compute_map(&grid, params.iou_thresholds, params.max_dets_per_image)?;
let assignment = assign_bins(gt, dt, &cross_class, ¶ms)?;
let mut delta_per_bin: HashMap<TideErrorBin, f64> = HashMap::new();
for (bin, fix) in [
(TideErrorBin::Cls, FixKind::Cls),
(TideErrorBin::Loc, FixKind::Loc),
(TideErrorBin::Both, FixKind::Both),
(TideErrorBin::Dupe, FixKind::Dupe),
(TideErrorBin::Bkg, FixKind::Bkg),
(TideErrorBin::Missed, FixKind::Missed),
] {
if !bin_has_work(&assignment, bin) {
continue;
}
let delta = run_fix_pass(
gt,
dt,
kernel,
&assignment,
fix,
¶ms,
parity_mode,
baseline_map,
)?;
delta_per_bin.insert(bin, delta);
}
let delta_all_fp = run_fix_pass(
gt,
dt,
kernel,
&assignment,
FixKind::AllFp,
¶ms,
parity_mode,
baseline_map,
)?;
let config = TideConfig {
t_f: params.t_f,
t_b: params.t_b,
kernel: kernel_marker,
cross_class_topk: None,
};
Ok(TideReport {
baseline_map,
delta_per_bin,
delta_all_fp,
config,
})
}
pub fn error_decomposition_bbox(
gt: &CocoDataset,
dt: &CocoDetections,
params: TideParams<'_>,
parity_mode: ParityMode,
) -> Result<TideReport, EvalError> {
error_decomposition_with(gt, dt, &BboxIou, KernelMarker::Bbox, params, parity_mode)
}
pub fn error_decomposition_segm(
gt: &CocoDataset,
dt: &CocoDetections,
params: TideParams<'_>,
parity_mode: ParityMode,
) -> Result<TideReport, EvalError> {
error_decomposition_with(gt, dt, &SegmIou, KernelMarker::Segm, params, parity_mode)
}
pub fn error_decomposition_boundary(
gt: &CocoDataset,
dt: &CocoDetections,
params: TideParams<'_>,
parity_mode: ParityMode,
dilation_ratio: f64,
) -> Result<TideReport, EvalError> {
let kernel = BoundaryIou { dilation_ratio };
error_decomposition_with(gt, dt, &kernel, KernelMarker::Boundary, params, parity_mode)
}
fn bin_has_work(assignment: &BinAssignment, bin: TideErrorBin) -> bool {
match bin {
TideErrorBin::Missed => !assignment.missed_gts.is_empty(),
TideErrorBin::Cls => assignment.dt_labels.values().any(|l| l.bin == DtBin::Cls),
TideErrorBin::Loc => assignment.dt_labels.values().any(|l| l.bin == DtBin::Loc),
TideErrorBin::Both => assignment.dt_labels.values().any(|l| l.bin == DtBin::Both),
TideErrorBin::Dupe => assignment.dt_labels.values().any(|l| l.bin == DtBin::Dupe),
TideErrorBin::Bkg => assignment.dt_labels.values().any(|l| l.bin == DtBin::Bkg),
}
}
#[allow(clippy::too_many_arguments)]
fn run_fix_pass<K: EvalKernel>(
gt: &CocoDataset,
dt: &CocoDetections,
kernel: &K,
assignment: &BinAssignment,
fix: FixKind,
params: &TideParams<'_>,
parity_mode: ParityMode,
baseline_map: f64,
) -> Result<f64, EvalError> {
let (corrected_gt, corrected_dt) = apply_fix(gt, dt, assignment, fix)?;
let eval_params = EvaluateParams {
iou_thresholds: params.iou_thresholds,
area_ranges: params.area_ranges,
max_dets_per_image: params.max_dets_per_image,
use_cats: params.use_cats,
retain_iou: false,
};
let grid = crate::evaluate::evaluate_with(
&corrected_gt,
&corrected_dt,
eval_params,
parity_mode,
kernel,
)?;
let corrected_map = compute_map(&grid, params.iou_thresholds, params.max_dets_per_image)?;
Ok(corrected_map - baseline_map)
}
fn compute_map(
grid: &crate::evaluate::EvalGrid,
iou_thresholds: &[f64],
max_dets_per_image: usize,
) -> Result<f64, EvalError> {
let mut max_dets = vec![max_dets_per_image];
sort_max_dets(&mut max_dets);
let acc = accumulate(
&grid.eval_imgs,
AccumulateParams {
iou_thresholds,
recall_thresholds: crate::parity::recall_thresholds(),
max_dets: &max_dets,
n_categories: grid.n_categories,
n_area_ranges: grid.n_area_ranges,
n_images: grid.n_images,
},
ParityMode::Strict,
)?;
let n_t = acc.precision.shape()[0];
let n_r = acc.precision.shape()[1];
let n_k = acc.precision.shape()[2];
let area_idx = 0usize; let m_idx = max_dets.len() - 1;
let mut ap_values: Vec<f64> = Vec::with_capacity(n_t * n_k);
for t in 0..n_t {
for k in 0..n_k {
let recall_v = acc.recall[(t, k, area_idx, m_idx)];
if recall_v < 0.0 {
continue; }
let mut any_nonneg = false;
let mut s = 0.0_f64;
for r in 0..n_r {
let v = acc.precision[(t, r, k, area_idx, m_idx)];
if v >= 0.0 {
any_nonneg = true;
s += v;
}
}
if any_nonneg {
ap_values.push(s / (n_r as f64));
} else {
ap_values.push(0.0);
}
}
}
if ap_values.is_empty() {
return Ok(0.0);
}
let total: f64 = ap_values.iter().sum();
Ok(total / (ap_values.len() as f64))
}