Skip to content

📦 WP-107 — feat(eval): whole-image merge for overlapping tiles

Research log · Phase 9 — Inference and generalization

🔗 Depends on WP-064 · Needed by WP-108, WP-111

⬅️ WP-093 · Roadmap · WP-108 ➡️

WP-107 — whole-image merge

Recorded before the work, because the reason this is a package rather than a step inside another one is itself the finding: two overlapping 1024 px tiles that both detect one object produce two detections at full confidence, and an NMS-free path has no suppression stage to remove the duplicate. The merge is a policy that has to be decided and argued, which is precisely why it kept failing to happen as a side effect — WP-063 deferred it into WP-088, whose scope never took it up, and WP-064 shipped 0.3.0 without it.

What the work established is that the duplicate rule does not have to be a suppression rule. Core ownership (A59) keeps or drops a detection by where it is: the cores are built from the recorded windows before the model runs, nothing is compared against another detection or ranked by confidence, and a detection is dropped whether or not the neighbouring tile found anything. Run the model twice with different weights and the same detection is owned by the same tile. A confidence-ranked dedup by rotated IoU is the defensible alternative and would score higher, because it takes the union of the tiles' recalls rather than the owner's — it was rejected because the figure is meant to measure a suppression-free pipeline, and reintroducing suppression at the seam makes it measure something else. That rejection is the package's actual content; the code is the cheap part.

The cost is real and one-directional: whole-image recall is the owner's recall, so this merge can only lower a per-tile number. It is pinned by a test rather than left as prose, so that a later reader who finds the drop surprising cannot quietly fix it into NMS.

One residual runs the other way and was nearly missed. The first draft of the module claimed the difficult straggler "cannot inflate the score". That is wrong in exactly the direction this package exists to police: a detection landing on a surviving clipped copy is discarded under A48, where against the true whole-image annotation set it would have been a false positive, and discarding false positives raises precision. It is bounded to objects wider than the overlap and it is R18's own devkit behaviour rather than an invention here — but it is not zero, and it is now measured at 0.165 of map_50 on the unit fixture rather than argued away. A claim of the form "cannot" about one's own instrument deserves a measurement before it is written down.

The DoD's equality clause needed one guard the row did not ask for. A single-tile image must score identically through both paths, and an equality between two saturated 1.0s would prove nothing at all — so the fixture is built to land every one of the four metric keys strictly inside (0, 1), asserted separately, and the equality is then asserted with no tolerance.

Two smaller things the merge inherits rather than solves. --limit can end a tile prefix mid-source-image, so an incomplete trailing image is dropped rather than scored partially — a whole-image figure under --limit N therefore covers at most N tiles' worth of complete images. And A52's --drop-empty-tiles moves the core midpoints when windows are missing from the layout: still a partition, but the v/2 circumradius guarantee weakens, which is a property of that off-protocol layout rather than of this rule.