Skip to content

feat(assets): half-frame mode — two frames per scan - #554

Merged
marcinz606 merged 1 commit into
mainfrom
feat/half-frame
Jul 18, 2026
Merged

feat(assets): half-frame mode — two frames per scan#554
marcinz606 merged 1 commit into
mainfrom
feat/half-frame

Conversation

@marcinz606

@marcinz606 marcinz606 commented Jul 18, 2026

Copy link
Copy Markdown
Owner

What

Half-frame cameras (Pentax 17, Olympus Pen…) expose two photos per 35mm frame; scans arrive as one file with two images separated by an unexposed gutter. This adds a Half Frame toggle (Files toolbar, mirrors RGB-scan end to end): each scan appears as two frames on the contact sheet, each with independent edits, bounds metering, thumbnails, sidecars, and exports.

How

  • Identity: discovery expands each file into two assets with hash <base>#1 / #2 (negpy/services/assets/half_frame.py). Every hash-keyed store — file_settings, edit_history, file_marks, disk thumbnails, EXIF, render memo — becomes per-half with no schema changes.
  • Independent bounds: the decoded buffer is sliced at the gutter before the pipeline, so full-frame metering is per-half metering. No shader changes; GPU caches keyed by the suffixed source_hash.
  • Shared decode: decode/preview caches key on the unsuffixed hash (base_hash()), so both halves reuse one decode. Slices are copied — cached buffers stay read-only.
  • Gutter auto-detect: 1-D column-luma heuristic with three gates — local-median deviation (immune to frame-brightness differences), extremal-vs-both-sides (rejects in-scene step edges), full-height uniformity (rejects textured features). Center-split fallback; the crop tool trims residue.
  • Collision guards: per-half sidecars name.N.negpy; export stems name_1/name_2; add_files same-path guard matches per half; EXIF path-fallback rehoming excludes # hashes so it can't steal a half's edit.
  • Toggling off re-collapses to whole scans; per-half edits stay in the DB and return on re-enable.

Validation

  • 19 new tests (tests/test_half_frame.py): detector polarities/fallbacks/rejections, slice partition, identities, sidecar/export suffixes, discovery expansion, dedup guard, per-half bounds independence.
  • Detector validated on all 7 samples/half_frame Pentax 17 scans: 6 split dead-center in the gutter; IMG428 (invisible gutter, night frames) correctly falls back to center.
  • Headless app run (Xvfb): 7 files → 14 filmstrip entries, both halves of one scan rendered independently at half-width with distinct stats.
  • make all green — 2022 passed.

Out of scope (v1)

Divider-drag UI, per-file split (mode is global like RGB-scan), RGB-triplet + half-frame combination (triplets stay whole).

closes #553

Half-frame cameras put two photos on one scan. A global toggle
(mirrors RGB-scan) expands each file at discovery into two assets
with hash <base>#1/#2 — every hash-keyed store (edits, history,
marks, thumbnails) becomes per-half automatically. The decoded
buffer is sliced at the auto-detected gutter before the pipeline,
so bounds metering is independent per half; decode caches key on
the unsuffixed hash so halves share one decode. Per-half sidecars
(name.N.negpy), export stems name_1/name_2, batch workers, session
restore, tutorial step.
@github-actions

Copy link
Copy Markdown

Preview load metrics (github-linux-x64)

format cold load throughput
CR2 0.760 s 29.51 MPx/s
NEF 0.596 s 41.27 MPx/s
ARW 0.212 s 94.98 MPx/s
RAF 0.623 s 26.11 MPx/s
DNG 0.631 s 37.91 MPx/s

fixtures: rawsamples.ch · sha: 2f6e2b2 · full JSON

@marcinz606
marcinz606 merged commit 81bea3c into main Jul 18, 2026
2 checks passed
@marcinz606
marcinz606 deleted the feat/half-frame branch July 20, 2026 11:27
@ycjhjiangxiao-commits

Copy link
Copy Markdown

Can you extend this function to split film strip scan into multiple frames?

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.

half-frame mode

2 participants