test: Add failing Harness test — square Frame region maps to a 3.16:1 strip in View space on iOS - #4113
Merged
mrousavy merged 1 commit intoJul 31, 2026
Conversation
The existing end-to-end coordinate test projects the frame center only, and the center is a fixed point of an axis-scaling error: it maps to camera (0.5, 0.5) under both a correct and an anisotropic matrix, so it lands on the view center either way. This test projects a region instead. A square in Frame space must stay square in View space, since the sensor scale, the orientation counter-rotation and the preview's aspect-preserving crop all preserve aspect. Camera space is allowed to be anisotropic in between (iOS normalizes per-axis to [0, 1]); converting out of it has to undo the same per-axis scaling. Expected to fail on iOS and pass on Android — see the PR description for the measured numbers and the suspected cause in FrameCoordinateSystemConverter.getFrameToCameraMatrix.
|
Someone is attempting to deploy a commit to the Margelo Team on Vercel. A member of the Team first needs to authorize it. |
4 tasks
Owner
|
Danke! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds one failing Harness test reproducing an iOS-only anisotropy when composing the two public coordinate conversions. Per the harness README, this PR is the reproduction — the issue will reference it.
What's wrong
Composing the library's own two transforms produces a distorted mapping on iOS:
A square region — the natural thing to map when you have a scan guide or a detection box — comes back as a wide, short strip. Each sample smears across its neighbours.
Measured
iPhone 15 Pro Max, iOS 26.5.2, frame 3840×2160,
orientation: "left". A square guide cell, identical ±19.7 dp in both view axes:383.6 × 121.4 px — ratio 3.159, where a correct mapping gives 1.000. Pixel 8 (1280×720) composes correctly at 1.000, so this is iOS-only.
convertViewPointToCameraPointlooks right on its own: for the square patch it returns Δx/Δy = 0.5625 = exactly 9/16, consistent with per-axis normalisation over a 16:9 image.Suspected cause
ios/Utils/FrameCoordinateSystemConverter.getFrameToCameraMatrixrotates within[0, 1]²in step 1, then normalises in step 3:scaledBypost-concatenates, so a point is normalised by the buffer's own width/height before the rotation swaps the axes — and the extents never swap with it. The measured numbers match that reading exactly:0.0562 × 2160 = 121.4and0.0999 × 3840 = 383.6, i.e. each output axis is scaled by its own extent after the swap, where the swap should have transposed them. Predicted anisotropy for a 16:9 frame atorientation: leftis (16/9)² = 3.1605, against the measured 3.159.One candidate shape for the fix, though the test is the arbiter:
Corner and round-trip behaviour stay correct under the bug, which is why the existing tests don't catch it:
round-trips Frame -> Camera -> Framepasses because the error is self-inverse —getCameraToFrameMatrixis literallygetFrameToCameraMatrix().inverted().round-trips Frame center -> Camera -> View center end-to-endpasses because the frame center maps to camera(0.5, 0.5)under both a correct and an anisotropic matrix, so it lands on the view center either way. The center is a fixed point of the error.Only an off-center region exposes it.
The test
maps a square Frame region onto a square View regioninvisioncamera.coordinates.harness.tsx, next to the existing end-to-end test. It runs the square the other way round — Frame → Camera → View — so the frame callback is installed beforestart()exactly like the neighbouring tests, and no new setup shape is introduced.min(width, height) / 4, kept small so it stays inside the preview'sresizeMode='cover'crop on any view aspect ratio.PreviewView.leftEdge / topEdgeistoBeCloseTo(1, 1)— a ratio rather than absolute lengths, since frame resolution and view size differ per device, but a square is square everywhere.No platform guard, per README §8: the behaviour should hold on both platforms, so the shared test should show the discrepancy rather than hide it. Expect red on iOS, green on Android.
What I could not verify myself
I have not executed this test. I don't have a rig for this monorepo (no
bun, and the failure is iOS-only), so the CI run on this PR is the first real execution. The test is written against the conventions in__tests__/README.mdandbiome checkis clean, but treat the exact tolerance as a starting point — iftoBeCloseTo(1, 1)proves too tight for real device geometry on a correct implementation, it should be loosened rather than the invariant weakened.Also worth flagging: fork PRs may not be able to assume the AWS Device Farm OIDC role, so the iOS leg might not run here without a maintainer-triggered run.
Context
Found while mapping a Rubik's cube scan guide onto sampled frames in a shipping app. Our workaround validates the composed mapping with an isotropy check and falls back to deriving the geometry from the frame dimensions when it fails — no platform branch, it's measured at runtime. Android accepts the library transform; iOS rejects it at 3.159 and falls back. Happy to adjust anything here, and glad to re-run against a fix.