Skip to content

📦 WP-155 — refine(data): letterbox and instance filter come from upstream

Engineering log · Phase 14 — Augmentation upstreaming to fuse, v0.7.0

🔗 Depends on WP-154b · Needed by WP-155b

⬅️ WP-154b · Roadmap · WP-156 ➡️

WP-155 and WP-155b — the letterbox and the keep mask delegate, and the one pixel metric that moved

The two rows land as one commit. They were split so a delegation swap would never travel with a frozen value (§6.5), and the split cannot be honoured here: the swap moves a value, so WP-155 alone cannot pass make gate and WP-155b alone has nothing to measure. The choice was between a commit that mixes them and a red main between two commits; the principal took the mixed commit. What §6.5 buys — the ability to name the row that moved a value — is preserved by the measurement below rather than by the commit boundary.

What delegates. Letterbox (data/letterbox.py) drops _LetterboxGeom, _resolve_geometry and _resize_pad. The fit comes from letterbox_geometry, the forward affine from letterbox_matrix, and the image from a letterbox-only Compose.from_params(letterbox=..., allow_upscale=..., fill=..., padding_mode="zeros") built once at construction — one grid_sample from the source canvas straight to the letterboxed one instead of an F.interpolate followed by an F.pad. inverse_map and forward_affine keep their signatures and their semantics, so the six evaluation and prediction call sites are untouched. The inverse stays local and closed-form (inv_r, -pad * inv_r) rather than being obtained by inverting the forward matrix numerically, because A10 requires the round trip to be exact and a numerical inversion leaves a residual in it. RandomAffine and MosaicAssembly drop their two byte-identical _keep_mask copies for upstream's instance_keep_mask at all six call sites.

The threshold trap, made loud. Upstream defaults min_size and min_visibility to 0.0 — deliberately, so the package holds no opinion about a training recipe's numbers — against this project's 2.0 and 0.1. Both are passed explicitly at every one of the six call sites, and TestKeepThresholdsReachUpstream in test_affine.py and test_mosaic.py covers each of the three routing sites per class with a size case, a visibility case and a control. The mosaic control states the cost of an omission exactly: at 0.0/0.0 not only the 4%-visible sliver survives but the three instances the placement clips away to nothing survive too, a zero-width box being >= 0.0 on both rules — three empty instances carried down the pipeline with every shape lining up and nothing raising.

The measurement WP-155b owns. Geometry is bit-unchanged: letterbox_pad_fraction holds at 0.4, every coordinate round-trip case passes, goldens/data_checksums.json does not move, and 35 of 36 goldens pass untouched. Exactly one value moved — aug_invariants' letterbox_image_mean, 0.5024 → 0.4994, against a 0.002 tolerance, so 1.5× tolerance and no more. That is the whole eval-path cost of the resampling swap, and it is one number rather than a class of drift because the geometry did not move with it. Upstream's opt-in antialias=True was measured rather than assumed: it recovers nothing here (still 0.4994), its Gaussian mipmap prefilter not engaging at this producer's downscale, so keeping the antialiased filter was not available short of keeping the local implementation. A32 is revised rather than contradicted — non-antialiased resampling is now the whole pipeline's filter rather than the train path's deviation from the eval path, and the two paths differ only in warp count.

The frozen golden, overridden a second time. goldens/frozen/0.6/aug_invariants.json pins that same metric, so the same §7 wall as WP-154b, on the same file, eight commits later. The alternatives were put again — remove the file on the WP-132/WP-154 precedent, or narrow the row to the keep mask and keep the local resample, running two resample implementations and deferring the delegation to 0.7 — and the principal chose the override again: letterbox_image_mean updated in place to 0.4994 in both the live and the frozen copy. docs/ESCALATION.md (2026-09-03, second entry) carries it. That this is the second override of the same file in one session is the reason it is recorded separately rather than folded into the first: two dated exceptions, still not a standing waiver, and the next one is a fresh decision rather than a precedent already set.