Skip to content

fix(exposure): reach past the color refs by one fraction of each span - #1373

Merged
marcinz606 merged 1 commit into
mainfrom
fix/bounds-reach-span-ratio
Oct 12, 2026
Merged

marcinz606 merged 1 commit into
mainfrom
fix/bounds-reach-span-ratio

Conversation

@marcinz606

@marcinz606 marcinz606 commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

The bounds analysis widens each channel's color refs out to the luma window. On main that reach is the same in log units for every channel. On a negative whose channels differ in density range (a faded negative, for example), it is then a different fraction of each channel's span, so a gray scale normalizes to a different value per channel. A negative Luma Range Clip also tints the frame as it widens the window.

The reach is now one shared fraction of each channel's own span, computed as a ratio of sums: h = Σ(l_c − r_c) / Σ s_c.

This replaces #1366, which averaged the per-channel ratios. That version lets a near-flat channel drive the reach for all three: a negative Luma Range Clip margin becomes m · s_c · mean(1/s). The ratio of sums keeps the old mean log reach and widens every channel by the same fraction. The tests and docs are adapted from #1366 (co-authored by Sean Harding).

Measurements

264 color frames from 14 rolls, measured with the Roll Analysis decode path (saved crop, flat field and unmix). An A/B/C/D toggle in the app was used for visual checks.

main #1366 (mean of ratios) this PR (ratio of sums)
Gray tilt vs the frame's own gray axis, R/B (log) 0.110 / 0.138 0.079 / 0.116 0.079 / 0.116
Error on a synthetically faded copy (normalized) 0.074 0.048 0.049
Channel spread of window growth at Luma Range Clip −0.3 (P90) 1.19 1.03 1.03
Mean change vs main (normalized) 0 0.027 0.026

On these frames this PR and #1366 match closely. They differ only when one channel's span is near flat, which none of these frames have. The new headroom test covers that case.

Known limit: a strongly green-dominant scene (lawn, forest) can read as unequal channel ranges and pick up a slight magenta cast in the shadows. Within one frame, scene content and real fading cannot be told apart.

Changes

  • normalization.py: ratio-of-sums reach. Spans of mixed sign keep the single log reach.
  • THUMBNAIL_RENDER_VERSION 3 → 4. The recorded golden also picked up the missing autocrop_square / autocrop_squared defaults.
  • docs/PIPELINE.md: bounds paragraph.
  • Tests: the gray scale at unequal channel ranges stays neutral, and a negative Luma Range Clip widens every channel alike, even with one channel near flat. Both new tests fail on main.

Saved edits keep their stored bounds. A frame changes only when it is measured again.

Test plan

  • make all (lint, ty, full pytest)
  • New tests fail on main's recombination
  • Implementation matches the measured "Ratio" variant bit-exactly on all 264 frames at clip 0, −0.1 and −0.3
  • Visual check on a faded negative

The bounds analysis widened every channel's color refs to the luma window
by the same amount in log units. On a negative whose channels differ in
density range, that reach is a different fraction of each channel's span,
so a gray scale normalizes to a different value per channel, and a
negative Luma Range Clip tints the frame as it widens the window.

The reach is now one shared fraction of each channel's own span: a ratio
of sums, sum(l_c - r_c) / sum(s_c). A mean of per-channel ratios (#1366)
lets a near-flat channel inflate the reach for all three; the ratio of
sums keeps the old mean log reach and widens every channel alike.

Measured on 264 color frames from 14 rolls (preview decode, saved crop
and unmix): gray tilt against each frame's own gray axis drops from
0.110/0.138 to 0.079/0.116 (R/B, log), the error on a synthetically
faded copy from 0.074 to 0.049, and the channel spread of the window
growth at Luma Range Clip -0.3 from 1.19 to 1.03. Saved edits keep
their stored bounds.

Bumps THUMBNAIL_RENDER_VERSION; recording its golden also picked up the
autocrop_square and autocrop_squared defaults it was missing.

Co-authored-by: Sean Harding <sharding@dogcow.org>
@marcinz606
marcinz606 merged commit 11788db into main Oct 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant