Skip to content

📦 WP-157 — refine(data): rotated boxes and flip pairs delegate upstream

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

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

⬅️ WP-156 · Roadmap · WP-158 ➡️

WP-157 — a convention passed in, and the one test that can see it

Four functions in rotated_aug.py become adapters — mirror_rboxes, shift_rboxes, warp_rboxes and rbox_envelopes over upstream's mirror_rboxes, shift_rboxes, transform_rboxes and rbox_envelopes — and two bodies stay: clip_rboxes_to_canvas with its per-instance helper, because clipping a rotated box yields a polygon and upstream declines the operation by design, and check_rotated_pairing, which validates a local Targets invariant and was never geometry. rotated_geom.py is untouched. rbox_envelopes was not on the scope's list of three; it delegates bit-identically and the row's principle is "shrink to what upstream declines to own", so it went with them.

The callback is the whole row, and only one test can tell whether it was passed. Upstream imposes no angle convention and its canonicalize argument defaults to None, returning the box exactly as the arithmetic left it. Its mirror returns pi - theta where WP-058 returned -theta — the same rectangle, since the two differ by the half turn a rectangle is invariant under. Passing this project's canonicalizer collapses them, and the residual is the rounding of the wrap alone: measured max-abs 2.98e-07 on the angle column across 512 swept boxes with centre and extents bit-identical, against the 1e-4 test_aug_frozen.py's GEOM compares geometric expectations at. Omitting the callback is the dangerous case and it is invisible to almost everything: on the same sweep the un-canonicalized angles run to 3.9195, well past 3*pi/4, while the corner sets the two forms produce are bit-identical. No corner, coordinate, envelope or IoU assertion can distinguish them — TestCanonicalOutput's range assertions are the entire safety net for this row, which is worth stating because the natural instinct at a delegation is to trust the geometry tests.

The fit moved from float32 to float64, and the evidence that this is harmless is not a coordinate tolerance. The corner round trip now runs in the matrix's dtype, float64 throughout this package, where it previously narrowed to the box dtype first: max-abs 1.91e-06 under a similarity and 9.54e-07 under shear. The golden that bounds it directly, rotated_rbox_area at 96.1934, has a tolerance of 0.5 — wide enough to have hidden a flipped keep decision. What actually rules that out is the pair of zero-tolerance counts, affine_kept_instances 2.0 and mosaic_kept_instances 8.0, matching exactly. The narrowing back to the box dtype happens inside the callback rather than after it, because a canonical float64 angle sitting a hair inside the half-open [-pi/4, 3*pi/4) can round out of that range on the way to float32, and the range is a postcondition the assigner and the OBB head read in the dtype Targets actually carries.

The keypoint table is a shape conversion this project owns. A dataset publishes the pairs it wants swapped (A64 keeps that the public surface, so K stays generic); permute_keypoint_pairs takes a full-length (K,) index in which slot i takes its value from slot index[i], identity slots included. Applying the pairs in list order to range(K) reproduces the sequential per-pair swap this transform used to perform, overlapping pairs included — [(0, 1), (1, 2)] composes to the same three-cycle either way. The bounds check and its error string stay on this side, since a bad pair is a caller error whichever side permutes. Upstream moves coordinates and says so, so the matching keypoint_vis permutation stays local; WP-153c's §8.12 measurement already named the trap, and the case that pins it — test_mirrored_visibility_flags_follow_own_points — asserts the pairing of each mirrored point with its own flag rather than the two columns separately, with an unpaired third slot to pin that an identity slot neither moves nor loses its flag. Without it a mirrored sample carries a visible point marked occluded and its partner marked visible, at identical shapes with every coordinate assertion passing.

One device hazard, closed because no test here can catch it. The flip index is built on the CPU from a Python list. Upstream moves it to the keypoint device itself before gathering; the local visibility gather now does the same explicitly, since the loop it replaced was device-agnostic for free and a CPU index reaching index_select on an MPS tensor raises. The gate runs on the CPU, so nothing in it would have failed.

No golden moved: 36/36 with git status goldens/ empty. Deleted with the bodies they served: _POINT_DIM, _QUAD_CORNERS, and the apply_affine_to_points import.