Half Frame: plain auto-detecting toggle, more accurate detection - #1112
Draft
PabloRuizCuevas wants to merge 7 commits into
Draft
PabloRuizCuevas wants to merge 7 commits into
PabloRuizCuevas wants to merge 7 commits into
Conversation
… workflow Rolls (negpy/services/assets/rolls.py) replace folders as the library's primary listing: importing a folder recognizes it as a roll; Save as Roll… turns any loaded batch into a reopenable virtual roll, not necessarily from one folder. The Library tree lists every roll flat, color-coded (folder amber, virtual coral, no left-side twisty gutter), with Rename…, bulk Delete…, and an opt-in "rename the folder on disk" checkbox that rehomes every loaded path when the renamed roll is the active one. Roll Settings dialog tags gear, capture, place, process and scanning metadata for the current frame, a selection, or the whole roll in one dialog, with a Load-preset flow to fill and tick fields. New Roll moves off the toolbar onto the Film Strip section's own header menu (CollapsibleSection.set_actions_menu), next to Reset Roll to Defaults — a roll-wide reset (reused _asset_defaults, one frame's worth of logic applied to every visible one) with its own confirm dialog. The top-level toolbar is gone: actions live in the row of the section they act on, Library's own corner row or the Film Strip row next to the loaded frames. restore_session now recognizes the active roll on restart too, the same "only when unambiguous" rule an already-open multi-folder session uses. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Turning Half Frame on used to pop the rectangle editor before splitting anything -- now it just runs the same batch auto-detect Auto-detect All Splits does, directly, no dialog. The editor stays reachable from the tune-icon menu (Adjust Split...) for the odd frame the batch gets wrong. The toggle was one global flag shared by every roll. Now each roll remembers its own (half_frame_mode_by_roll, keyed by the real roll id from rolls.py); an ad hoc session with no recognized roll keeps the single sticky flag it always had. AppController.half_frame_mode_for_roll() is the one place that resolves which applies, read both by request_asset_discovery (so a roll always splits into what it was left as) and by the toggle's own displayed state. A new roll saved from an ad hoc session (create_roll_from_session) seeds its own entry from that session's current value, so saving a roll never silently resets the toggle for it. half_frame_mode_changed (mirroring the existing rgb_scan_mode_changed) fires wherever the active roll changes -- open_roll, open_library_folders, a library search landing on ad hoc results -- so the button follows without re-toggling itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Auto-detect All Splits (and the toggle, which runs it on turning on) only ever found split_x -- the crop rect it saved was whatever was already there, defaulting to the full uncropped scan. The scanner bed / holder around the film was never trimmed by auto-detect at all, only the gutter between the two frames, which is why the edges stayed loose after detecting. detect_split_and_crop_for_file (half_frame.py) decodes the file once and runs two independent detectors on the same buffer: the existing 1-D gutter heuristic for split_x, and detect_film_crop -- a thin wrapper around get_autocrop_coords(mode=FILM, ratio=Free), the same single-frame film-bounds detector the Geometry tab's own Auto Crop already uses. FILM mode keeps the rebate/sprockets a two-up diptych's outer crop must not cut into; a forced aspect ratio would be wrong for a two-up frame. process_auto_detect_all_splits (render.py) and _on_splits_detected (controller.py) now carry (split_x, crop_rect | None) pairs instead of a bare float; a file whose crop detection fails keeps whatever crop it already had, only the split moves for it. The per-frame rectangle editor's own Auto-detect is unchanged (split-only) -- it already has a crop the user drags by hand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
detect_split_x took the midpoint of the whole band that deviated from a local background -- correct for a clean gutter, but a smooth in-scene gradient blending into one edge (an overexposed sky) widens that band on one side only, dragging the midpoint toward it. The gutter's own edges are now found directly, as the steepest slope on each side of the rough peak, refined to sub-pixel precision by a parabola through each edge's neighbors, and the true center is the midpoint between those two edges instead. Renamed to detect_gutter and returns (split_x, gutter_thickness): the edges it now finds also measure the band's own width, previously never detected at all -- Auto-detect only ever moved the split line, leaving Cut thickness wherever it last was. detect_split_x stays as a thin wrapper for callers that only want the position. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat/half-frame-auto-detect-accuracy gave the per-frame rectangle editor's own Auto-detect button sub-pixel split_x and gutter thickness (detect_gutter), but left the batch path (Auto-detect All Splits, and the toggle that runs it) on the old split-only detect_split_x_for_file -- merged in here, so this branch had it, without the batch benefiting. detect_split_and_crop_for_file now calls detect_gutter instead of detect_split_x, returning (split_x, gutter_thickness, crop_rect) instead of just (split_x, crop_rect); _on_splits_detected applies all three to each file's override. Thickness comes free out of the same computation that already finds split_x, so this costs nothing extra to detect -- only the plumbing to save it was missing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Its own Auto-detect button (HalfFrameDialog) only ever ran detect_gutter -- the rectangle stayed whatever it already was, same gap the batch path had before the previous commit. A user pressing it expected the same result the toggle gives, since both call themselves "Auto-detect". _on_auto now also runs detect_film_crop and sets the rectangle from it. Fixed alongside: split_x is relative to the cropped width (slice_half's own convention), so the gutter search has to run inside the newly detected crop, not the full uncropped scan -- detect_split_and_crop_for_file had the same bug (detecting the gutter and the crop independently, on the same uncropped buffer), fixed there too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
detect_film_crop no longer delegates to the scanner-bed autocrop detector: it finds nothing on a tightly-framed camera scan (no bed to separate from film) and can crop a whole exposure off a frame it misreads, especially in low light. Each of the four sides is now searched independently, inward from the scan's own boundary, for the same uniform-and-extremal band detect_gutter finds in the middle -- unexposed rebate reads as a narrow run near the frame's own darkest or brightest tone. A side with no rebate, or a margin too wide or not extremal enough against the frame's own tonal range to trust, is left uncropped rather than guessed at. Verified by eye against real half-frame camera-scan rolls: trims genuine narrow rebate margins, leaves tightly-framed sides alone, and does not crop into a blank or dark exposure or into locally-uniform scene content (calm water, sky) that only coincidentally looks like film base. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PabloRuizCuevas
force-pushed
the
feat/pr-half-frame-detection
branch
from
September 17, 2026 10:27
50ffebb to
b388c93
Compare
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.
Summary
Half Frame becomes a plain, auto-detecting toggle instead of an editor you step through first, and its detection gets more accurate.
detect_gutternow returns both the split position and the gutter's own thickness, refined to sub-pixel accuracy, rejecting a smooth in-scene gradient (an overexposed sky) that could otherwise pull the detected center off target.detect_film_cropfinds the rebate/bed margin around a diptych so the batch auto-detect can also trim the outer edges, not only find the split.detect_film_cropno longer delegates to the scanner-bed autocrop detector, which finds nothing on a tightly-framed camera scan and can crop a whole exposure off a frame it misreads in low light. Each of the four sides is now searched independently, inward from the scan's own boundary, for the same uniform-and-extremal banddetect_gutterfinds in the middle.Note: this PR is based on
mainbut its branch (feat/pr-half-frame-detection) is built on top of the current #1109 (feat/pr-rolls-and-roll-settings), since the per-roll Half Frame state needs the Roll concept #1109 introduces -- please review/merge #1109 first. Once it lands, this diff will automatically shrink to just the half-frame changes.Testing
make all(ruff check/format, full pytest) passes with only one known unrelated failure (test_load_tooltip_follows_a_rebinding, a macOS key-symbol rendering difference, pre-existing onmain).🤖 Generated with Claude Code