Expand description
SAHI-style tiled-inference postprocessing: lift per-tile detections to full-frame coordinates and merge them across tiles.
A high-resolution frame is covered by a uniform overlapping grid of tiles
(geometry lives in the edgefirst-image crate). Each tile is run through
the small tile-input model and decoded independently to normalized [0,1]
detections over the model input. This module lifts those to full-frame
pixels (lift_tile_boxes) and merges duplicates at tile seams
(merge_tiled_detections) using GREEDYNMM with the IOS
(intersection-over-smaller) match metric. TiledFrameAccumulator is a
streaming collector so a pipelined runtime can push each tile’s detections
as inference completes and finalize once the frame’s last tile arrives.
The merge reproduces ModelPack’s reference runtime
(metrics/tiled.py::merge_tiled_detections) numerically. IOS matters
because an object split across a tile overlap appears as two partial boxes
whose IoU is low but whose IoS is high, so IoS merges them where IoU leaves
duplicates.
§Per-tile decode guidance (affects mAP)
Run the per-tile crate::Decoder with a low score threshold (e.g.
0.05) and class-aware NMS, and a modest per-tile max_det. The merge’s
own MergeConfig::score_threshold defaults to 0.0 precisely because
per-tile decode is the real flood control — a high per-tile threshold
discards true-positive fragments before the merge can join them, collapsing
the recall the IOS design buys. Final score gating belongs in
MergeConfig::score_threshold.
§Known limitations
- Objects larger than one tile cannot be reconstructed: every tile sees only a fragment, and with no whole-object box to anchor the union the fragments may not mutually pass the IOS threshold. Choose a tile size that exceeds the largest expected object, or add the optional full-frame downscaled pass (push it as one extra tile into the accumulator).
§Reference implementations
- Grid spacing (EvenDist): the canonical authority is HAL’s own
edgefirst_image::tile_grid(ported from the adis-uav-modelsahi()function).overlap_ratiois a minimum; realized overlap is never rounded below it. ModelPack’s validator is slated to adopt this same grid. - Merge: ModelPack
metrics/tiled.py::merge_tiled_detections— mirrored here numerically. The only deliberate difference is tie-breaking on exactly equal scores (ascending original index here vs NumPy’s unstableargsort), which makes the streaming accumulator order-independent; results are identical on non-degenerate inputs.
Structs§
- Merge
Config - Configuration for the tiled-detection merge.
- Tile
Placement - How one tile was cut from the full frame and fed to the model. Produced by
the input side (the
edgefirst-imagetiling API), consumed bylift_tile_boxes. All fields are native full-frame pixels exceptletterbox. - Tiled
Frame Accumulator - Streaming collector for one frame’s tiled detections. A pipelined runtime pushes each tile’s per-tile-decoded boxes as inference completes (any order), then finalizes once every tile has arrived — the “collect after the final tile” fan-in. Not internally synchronized; keep one accumulator per in-flight frame.
Enums§
- Match
Metric - Overlap metric used by the tiled-detection merge to decide whether two boxes belong to the same object.
Functions§
- lift_
tile_ boxes - Lift tile-local normalized
[0,1]xyxy detections (over the model input) to full-frame pixel xyxy. Mirrorsmetrics/tiled.py::lift_tile_boxes: optionally invert the letterbox, thenfull = origin + norm * crop_size. Consumes and rewritesboxesin place. - merge_
tiled_ detections - Greedy Non-Max Merge of lifted full-frame detections. Mirrors
metrics/tiled.py::merge_tiled_detections: - unletter_
norm - Invert a letterbox: map a
BoundingBoxnormalized over the model input back to normalized-over-the-crop, given the content bounds[lx0, ly0, lx1, ly1]. The box is canonicalised first, a degenerate (zero-span) letterbox axis maps with unit scale (no divide-by-zero), and the result is clamped to[0, 1].