pub fn merge_tiled_detections(
dets: Vec<DetectBox>,
cfg: &MergeConfig,
) -> Vec<DetectBox>Expand description
Greedy merge of lifted full-frame detections. Mirrors
metrics/tiled.py::merge_tiled_detections:
- Sort descending by score (ties broken by ascending original index so the
result is deterministic — this differs from NumPy’s unstable
argsortonly on exact ties). - For each unused
basein order, find later unused boxes (same class unlessclass_agnostic) whosemetric.value(base, cand) >= threshold— matched against the original base box — and mark them used. WithMergeMode::KeepBest(default) the base box is emitted unchanged (canonicalized, never grown or rescored); withMergeMode::Unionthe group is replaced by its enclosing union carrying the group’s max score. Either way the output carries the base’s label. - Drop groups below
score_thresholdand truncate tomax_det.
Keep-best is the default because the union measured about −0.05 AP50 on
every frame of the Ocean Cleanup ADIS 4K validation (TOP2-836): whole
frame 0.491 AP50 with plain NMS, 0.437 after the union, 0.490 with
keep-best. Operates in pixel space (the metric’s 1e-9 epsilon is
calibrated to pixel areas).
§Examples
use edgefirst_decoder::tiling::{merge_tiled_detections, MatchMetric, MergeConfig, MergeMode};
use edgefirst_decoder::{BoundingBox, DetectBox};
// A fragment (B) fully inside the full detection (A): IoS=1.0, IoU≈0.17.
let a = DetectBox { bbox: BoundingBox::new(100.0, 100.0, 400.0, 300.0), score: 0.9, label: 0 };
let b = DetectBox { bbox: BoundingBox::new(350.0, 100.0, 400.0, 300.0), score: 0.7, label: 0 };
// IOS matches the fragment; keep-best (the default) suppresses it and
// leaves A's box unchanged…
let ios = merge_tiled_detections(vec![a, b], &MergeConfig::default());
assert_eq!(ios.len(), 1);
assert_eq!(ios[0].bbox, a.bbox);
assert_eq!(ios[0].score, 0.9);
// …while IOU leaves the two separate.
let cfg = MergeConfig { metric: MatchMetric::Iou, ..MergeConfig::default() };
assert_eq!(merge_tiled_detections(vec![a, b], &cfg).len(), 2);
// A box that extends past A at IoS 0.5: keep-best still returns A as is,
// the legacy union mode grows A to the group's enclosing union.
let c = DetectBox { bbox: BoundingBox::new(350.0, 100.0, 450.0, 300.0), score: 0.7, label: 0 };
let keep = merge_tiled_detections(vec![a, c], &MergeConfig::default());
assert_eq!(keep[0].bbox, a.bbox);
let union = MergeConfig { mode: MergeMode::Union, ..MergeConfig::default() };
let grown = merge_tiled_detections(vec![a, c], &union);
assert_eq!(grown.len(), 1);
assert_eq!(grown[0].bbox, BoundingBox::new(100.0, 100.0, 450.0, 300.0));