Fix camera unprojection pixel-space mismatch (I1-A) + inference c= typo#49
Open
sibocw wants to merge 1 commit into
Open
Fix camera unprojection pixel-space mismatch (I1-A) + inference c= typo#49sibocw wants to merge 1 commit into
sibocw wants to merge 1 commit into
Conversation
The 3D-keypoints model emits `pred_xy` in the input-image pixel space, which at inference is `inference_image_size` (256x256). `CameraToWorldMapper` derives its intrinsics (focal length, principal point) from the size it is built with, and both scale linearly with that size. The inference and production scripts built the mapper with `camera_rendering_size` (464x464, the simulation render size) while feeding it 256-px predictions. This injects an anisotropic distortion -- in-plane (x, y) scaled by 256/464 ~= 0.55, depth exact -- which corrupts downstream inverse-kinematics joint angles by a median of ~12 deg (CTr) / ~19 deg (FTi). Fixes: - run_keypoints3d_inference.py: build the mapper at `inference_image_size` (the space pred_xy lives in) instead of `camera_rendering_size`, with a comment and a runtime note when the two differ. - run_keypoints3d_inference.py: fix `c=inference_image_size` -> `inference_image_size=inference_image_size` (the script __main__ raised TypeError and was dead). - production/spotlight/keypoints3d.py: apply the identical one-line mapper fix, building at `working_size` (the prediction space) instead of `camera_rendering_size`. Production was touched only for this identical fix. - camera.py: document that `__call__` xy must be in `rendering_size` pixels. - tests/test_camera_unprojection.py: round-trip recovery (~1e-6) and a bug/fix demonstration of the 0.55x in-plane / 1.0x depth anisotropy. Refs #48 (audit finding I1-A and the `c=` crash). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
This work was implemented by Claude Code (Opus 4.8) upon user instruction.
Summary
The 3D-keypoints model emits
pred_xyin the input-image pixel space, which at inference isinference_image_size = (256, 256)(the model is trained on 256-px labels).pose/camera.py::CameraToWorldMapperunprojects(pixel_xy, depth_mm) -> world_mm, and its intrinsics (focal length and principal point) are derived from the size it is built with — both scale linearly with that size.The bug (I1-A): both the inference script and the production pipeline built the mapper with
camera_rendering_size = (464, 464)(the simulation render size) while feeding it 256-px predictions. Because focal length and principal point scale with the build size, this injects an anisotropic distortion: the in-plane (x, y) coordinates are scaled by256/464 ≈ 0.55while depth is left exact. Measured downstream impact: a median inverse-kinematics joint-angle error of ~12° (CTr) / ~19° (FTi).For 256-px predictions the mapper must be built at
rendering_size = 256— this is provably correct (focal & principal point scale linearly with sensor size; depth is unaffected).Separate crash:
run_keypoints3d_inference.pypassedc=inference_image_sizetorun_keypoints3d_inference(...), whose parameter is namedinference_image_size, raisingTypeError. The script's__main__was therefore dead.Fix
pose/keypoints3d/scripts/run_keypoints3d_inference.py: build theCameraToWorldMapperatinference_image_size(the pixel spacepred_xylives in) instead ofcamera_rendering_size. Added a clear comment explaining why, and a runtime note ifcamera_rendering_size != inference_image_size(the simulation render size is intentionally not used for unprojection). Thecamera_rendering_sizeparameter is kept for now.pose/keypoints3d/scripts/run_keypoints3d_inference.py: fixedc=inference_image_size→inference_image_size=inference_image_size(the crash typo at the__main__call site).production/spotlight/keypoints3d.py: applied the identical one-line mapper-resolution fix — build the mapper atworking_size(the prediction space) instead ofcamera_rendering_size. Production was touched only to apply this identical fix; it was otherwise out of audit scope.pose/camera.py: documented inCameraToWorldMapper.__call__thatxymust be expressed inrendering_sizepixels (docstring-only; no behavior change), to guard the pixel-space assumption at the API level.Tests
Added
tests/test_camera_unprojection.py(camera.pyimports only numpy/scipy, so it runs standalone):test_unprojection_inverts_projection[128/256/464]— builds a mapper at size S, forward-projects known world points with the mapper's owncamera_matrix, unprojects via__call__, and asserts recovery toatol=1e-6(actual error ~1e-15).test_focal_scales_with_size_depth_does_not— asserts the in-plane focal scaling ratio is exactly256/464 ≈ 0.55while the depth axis of the focal transform is the identity regardless of size.test_same_input_different_mapper_size_anisotropic_distortion— unprojects the same 256-space(xy, depth)with a 256-mapper vs a 464-mapper, showing the world outputs differ, the camera-frame depth ratio is exactly 1.0, and the in-plane scaling is256/464 ≈ 0.55— confirming 256 is correct for 256-space inputs.Result:
Notes
Addresses I1-A and the
c=crash from #48. Refs #48 (using "Refs", not "Closes").🤖 Generated with Claude Code