Skip to content

Add manual ground/top-plane tilt correction - #429

Open
RufanMelfor wants to merge 1 commit into
reco-project:mainfrom
RufanMelfor:feat/ground-top-tilt
Open

Add manual ground/top-plane tilt correction#429
RufanMelfor wants to merge 1 commit into
reco-project:mainfrom
RufanMelfor:feat/ground-top-tilt

Conversation

@RufanMelfor

@RufanMelfor RufanMelfor commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reimplemented from scratch against the current architecture, like the
seam-positioning and color-matching PRs before it — neither ground_tilt
nor top_tilt exist anywhere in the current codebase.

Adds a small additional tilt near the bottom (ground) or top of frame,
where the flat two-plane approximation's parallax error is largest:
tan(theta) of extra tilt, applied only within an adjustable band near
the respective edge (identity everywhere else), via the tangent-addition
identity warp(t, c, k) = (k*c + t) / (1 - c*t/k) where t is plane-space
y and k = fy / (2*width) converts between "plane width = 1.0" units and
the tangent-addition math's own scale.

New Topology fields: ground_tilt_x/z, top_tilt_x/z (the tilt
scalars — manual-only, no automatic fitting path exists for either), and
ground_tilt_band_width/top_tilt_band_width (the ramp's adjustable
full-strength threshold; the ramp always starts at a fixed
GROUND_TILT_BAND_START = 0.08). All six default to a no-op (0.0 tilt /
0.16 band width), so existing calibrations render unchanged.

Unlike color-matching, this got a full CPU mirror rather than being
made GPU-only/opt-in: the warp is pure closed-form arithmetic with no
state, so it was tractable to add to stitch::geometry::PlaneMap:: sample_uv (operating on the same raw, pre-extended-UV-remap uv.y as the
GPU shader, in the same order, so the two backends agree by construction).
Verified with a new end-to-end agreement test that actually ran on real
GPU hardware with non-zero tilt on both planes
(cpu_and_gpu_backends_agree_with_ground_top_tilt), plus focused unit
tests on the warp math itself (identity below the band threshold, a real
shift beyond it, and the top-tilt's mirrored one-sidedness).

GPU-side wiring turned out simpler than the seam-positioning/
color-matching precedent needed: encode_stitch_pass already receives the
full Calibration, so the two new vec4<f32> uniform fields
(ground_tilt, top_tilt) are populated directly from
calibration.topology inside it — no new params threaded through
render_to_target/render_to_view or StitchPipeline's public render
methods. Only a genuine live-editable-without-scene-rebuild setter chain
was needed: StitchPipeline::set_ground_tilt_x/z/set_top_tilt_x/z/
set_*_band_width (mirrors set_blend_width exactly) → Executor
StitchCore → reco-gui's preview bridge.

reco-gui: 6 new LabeledSliders in the Calibration panel (tilt values
-0.3..0.3 at 5 decimals, band widths 0.09..0.4 at 2 decimals),
mirroring the existing Intersect/x_ty sliders' wiring exactly, including
all 3 calibration-sync sites (reset, file-load, and post-auto-calibrate
reload).

reco-calibrate's optimizer and reco-autocam's panner test fixtures
were updated only for the new required Topology fields (defaulted to
no-op) — the automatic-fitting path for these tilt values doesn't exist
and is out of scope here.

Verified: fmt --check clean; cargo test -p reco-core (156 passed,
including the new GPU agreement test and 4 new geometry unit tests; only
the 2 pre-existing CUDA hardware-gap failures, unrelated), -p reco-gui
(65/65), -p reco-calibrate (48/48, including the optimizer fitting tests
— confirms the required-field addition didn't disturb existing fitting
behavior), -p reco-autocam (6/6); full workspace build clean.

Calibration

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

All contributors have signed the CLA. Thank you!
Posted by the CLA Assistant Lite bot.

…op-plane tilt correction

Reimplemented from scratch against the current architecture (post
"stitch unification" / "executor spine" refactors), like the
seam-positioning and color-matching ports before it - neither
ground_tilt nor top_tilt exist anywhere in the current codebase.

Adds a small additional tilt near the bottom (ground) or top of frame,
where the flat two-plane approximation's parallax error is largest:
`tan(theta)` of extra tilt, applied only within a band near the
respective edge (identity elsewhere), via the tangent-addition
identity `warp(t, c, k) = (k*c + t) / (1 - c*t/k)` where `t` is
plane-space y and `k = fy / (2*width)` converts between "plane
width = 1.0" units and the tangent-addition math's own scale.

New Topology fields: ground_tilt_x/z, top_tilt_x/z (the tilt scalars,
manual-only - no automatic fitting path exists), ground_tilt_band_width/
top_tilt_band_width (the ramp's adjustable full-strength threshold; the
ramp always starts at a fixed GROUND_TILT_BAND_START = 0.08). All six
default to a no-op (0.0 tilt / 0.16 band width), so existing
calibrations render unchanged.

Unlike color-matching, this got a full CPU mirror rather than being
made GPU-only/opt-in: the warp is pure closed-form arithmetic with no
state, so it was tractable to add to stitch::geometry::PlaneMap::
sample_uv (operating on the same raw pre-extended-UV-remap uv.y as the
GPU shader, in the same order, so the two backends agree by
construction rather than needing an excluded-from-testing escape
hatch). Verified with a new end-to-end agreement test that actually
ran on real GPU hardware with non-zero tilt on both planes
(cpu_and_gpu_backends_agree_with_ground_top_tilt), plus focused unit
tests on the warp math itself (identity below the band threshold,
real shift beyond it, top-tilt's mirrored one-sidedness).

GPU-side wiring turned out simpler than the seam-positioning/
color-matching precedent needed: encode_stitch_pass already receives
the full Calibration, so the two new vec4<f32> uniform fields
(ground_tilt, top_tilt) are populated directly from
calibration.topology inside it - no new params threaded through
render_to_target/render_to_view or StitchPipeline's public render
methods. Only a genuine live-editable-without-scene-rebuild setter
chain was needed: StitchPipeline::set_ground_tilt_x/z/set_top_tilt_x/z/
set_*_band_width (mirrors set_blend_width exactly) -> Executor ->
StitchCore -> reco-gui's preview bridge.

reco-gui: 6 new LabeledSliders in the Calibration panel (tilt values
-0.3..0.3 at 5 decimals, band widths 0.09..0.4 at 2 decimals),
mirroring the existing Intersect/x_ty sliders' wiring exactly,
including all 3 calibration-sync sites (reset, file-load, and
post-auto-calibrate reload).

reco-calibrate's optimizer.rs and reco-autocam's panner test fixtures
were updated only for the new required Topology fields (defaulted to
no-op) - the automatic-fitting path for these tilt values doesn't
exist and is out of scope here, matching the fork's own "manual-only"
design.

Verified: cargo fmt --check clean; cargo test -p reco-core (156
passed, including the new GPU agreement test and 4 new geometry unit
tests; only the 2 pre-existing CUDA hardware-gap failures, unrelated),
-p reco-gui (6/6), -p reco-calibrate (48/48, including the optimizer
fitting tests - confirms the required-field addition didn't disturb
existing fitting behavior), -p reco-autocam (65/65); full workspace
check clean (reco-obs excluded, pre-existing unrelated OBS-SDK build
gap); clippy clean for this change's own code (verified by overlaying
the separate fix/d3d11-stage-frame-unsafe branch's fix to isolate from
upstream's 4 pre-existing, unrelated clippy failures).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@RufanMelfor

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants