Skip to content

fix(crop): keep the whole picture on high-key frames - #1613

Draft
kurtwindisch wants to merge 2 commits into
marcinz606:mainfrom
kurtwindisch:fix/autocrop-high-key
Draft

kurtwindisch wants to merge 2 commits into
marcinz606:mainfrom
kurtwindisch:fix/autocrop-high-key

Conversation

@kurtwindisch

@kurtwindisch kurtwindisch commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Auto Crop kept only a fragment of the picture on some film scanner frames. All the affected frames here are Coolscan B&W scans. Thin areas of a negative are as bright as the clear rebate, and two stages read that brightness as the end of the film or of the picture. Each failure has its own fix.

  • A film box must have evidence for where it ends. A box with fewer than three supported sides is a strip that runs off the scan, so its unsupported sides reach the frame edge. A side more than 5% inside the frame is where a threshold mask ran out on picture content (MAX_UNSUPPORTED_INSET, select_consensus). The surround check did not catch this, because a bright part of the picture passes as bed.
  • An occupancy run must end at clear rebate. A high-key picture dips under the 0.55 run threshold mid-frame, and a run that keeps half a side passed TIER_MIN_KEEP. Each end of the run inside the box now needs a clear line within 3% of the side (run_ends_at_rebate). Otherwise the tiers abstain, and the edge walk reads these frames correctly. The check reads the clearest line, not the mean, so a tilted edge or a narrow frame gap still passes. A picture area as bright as the rebate reads as rebate, so this check does not catch that case.

Crop All Frames also benefits. It placed the film-box failure wrongly before this change.

Results

Checked on 678 real frames from one library (Coolscan TIFFs and camera scans in CR3, DNG, RAF and ARW). 17 crops changed.

Frames Result
12 with a hand-drawn crop Moved to it: the worst edge error fell from 61% of the frame to under 1%, and all 12 ended within 4.5%
1 with a hand-drawn crop Moved 0.1%, stays about 11% off
4 with a saved auto crop 2 viewed: the saved crop was a fragment. 1 viewed: the new crop is wider and correct. 1 not viewed: it moved 35% of the frame, and the new crop sits within 1% of the saved one

Every frame the tier stage resolved in this set was one of its failures, so the tier stage now resolves none of them. The rule for two-sided boxes has a clear gap in the data: unsupported sides sat within about 2% of the frame edge or deeper than 6.7%.

Square film in a 3:2 scan is not affected: no crop or film box changed on the 139 frames set to 1:1, whose wide margins are supported sides.

Saved frames

  • BOUNDS_VERSION and THUMBNAIL_RENDER_VERSION are not bumped, as a deliberate choice. Saved crops and meter rects are frozen, and neither bump resets them, so a bump would re-meter or re-render every frame with no gain. New detections, a frame's meter rect included, use the new code.
  • A saved bad crop stays until Auto Crop runs on that frame again. Turn Auto Crop off and on, or run Crop All Frames.
  • The film box also feeds the film base reader and Meter live, so their reads can change on affected frames.

Open questions

Test plan

  • uv run pytest tests (10671 passed), then pytest tests --cov=negpy (same count) on the first commit, before the rebase. On the current branch, the crop, meter and skew test files pass
  • make rust, make lint, make type pass
  • New tests fail with their check turned off: test_consensus_refuses_a_two_sided_box_that_stops_short_of_the_frame, test_tier_refinement_refuses_a_run_that_ends_inside_the_picture
  • Drove the app offscreen with a sandboxed user directory: Auto Crop keeps the full picture on the failed frames
  • The reporter checked a full 36-frame roll in the running app
  • New tests for a 2 degree tilt and a narrow frame gap fail on the mean-based check and pass on the clearest-line check
  • All 678 frames give the same crops after that change
  • Six square-in-3:2 layouts with real picture content, 96 runs: old and new code give the same crop

🤖 Generated with Claude Code

Auto Crop kept a fragment of the picture on some film scanner frames.
Thin areas of a negative are as bright as the clear rebate, and two
stages read that brightness as the end of the film or of the picture.

A film box with two supported sides now reaches within 5% of the frame
at its other sides. Film that runs off the scan does. A box that stops
further in ended where a threshold mask ran out on picture content.

A tier occupancy run now counts only when clear rebate lies past each
end. A high-key picture dips under the run threshold mid-frame, and the
edge walk reads those frames correctly.

On 678 frames from a real library, 17 crops changed. Of 13 frames with
a hand-drawn crop, 12 moved to it: the worst edge error fell from 61% of
the frame to under 1%, and all 12 ended within 4.5%. One moved 0.1% and
stays about 11% off. The other 4 hold a saved auto crop.
No crop changed on the 139 1:1 frames.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kurtwindisch
kurtwindisch force-pushed the fix/autocrop-high-key branch from 7894a20 to 8e209c5 Compare November 5, 2026 19:27
The rebate check averaged occupancy past a run, so two good results
were refused: a tilted edge spreads across lines, and a narrow frame
gap leads into the next picture. A 2 degree tilt kept rebate in the
crop, and a 10 px gap moved a 3:2 crop toward the next frame.

The check now takes the clearest line within 3% of the side. On two
real high-key frames, every line past a false end held at least 0.43
occupancy, and the true edge reached 0.005, so the 0.25 limit stays.
The crops of all 678 frames in the regression set match the check
this replaces.

The film box rule now counts supported sides directly. Docs name the
rule as fewer than three sides, and say that a picture area as bright
as the rebate still reads as rebate.

BOUNDS_VERSION and THUMBNAIL_RENDER_VERSION stay as they are. Saved
crops and meter rects are frozen, and neither bump resets them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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