Render a half-frame scan as a diptych once the split is turned off - #865
Merged
Conversation
Half-frame mode gives each exposure its own asset (`<hash>#1` / `#2`) and its own edit. Turning the mode off never lost those edits - nothing deletes the rows - but it made them unreachable: the whole-frame asset keys on the unsuffixed hash and came back with defaults. A whole-frame scan that carries half edits is now a diptych. Half 1 renders under its own config, half 2 under its own, and the two are joined back into the original geometry. Each half is sliced before the pipeline, so its normalization measures the pixels it was edited on, and each gets its own pipeline identity or the stage caches collide and the second render comes back as the first. The gap keeps the cut band's width but is filled black rather than copied from the scan: the source gutter is scene-linear negative data, so pasting it in gives a bright bar, and running the pipeline on a thin dark strip renormalizes it into noise. A half with no saved edit takes its sibling's. A diptych is read-only - two configs drive the canvas, so the controls panel has nothing to drive - and is flagged at discovery in one query for the roll. It carries the both-sides-filled split badge and exports as one `<name>-DIPTYCH` file.
marcinz606
marked this pull request as ready for review
August 16, 2026 15:41
One conflict, in controller.py: both sides added an import next to the other. Kept both — diptych_configs from the half-frame helpers and render_export_filename from the Linear Output destination work (#862).
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.
What
Half-frame mode gives each exposure its own asset (
<hash>#1/#2) and its own edit. Turning the mode off never lost those edits — nothing deletes the rows, and both theft guards (load_or_promoteskipping the path fallback for a half,load_file_settings_by_pathexcluding%#%) keep them intact. It only made them unreachable: the whole-frame asset keys on the unsuffixed hash and came back with defaults.A whole-frame scan that carries half edits is now a diptych: half 1 renders under its own config, half 2 under its own, and the two are joined back into the original geometry — one image, one export.
How
services/assets/half_frame.py—diptych_configs(a missing half copies its sibling),join_halves,gap_px, andslice_half(half=0)for crop-without-split.image_processor.py—process_exportsplit at the encode seam into_render_export_buffer+_encode_export;diptych=renders both halves and joins them, then encodes once, so no double compression.workers/render.py—RenderWorker._render_diptych: slice, tworun_pipelineruns, join.controller.py—_mark_diptychsflags the whole roll in one query at discovery;_diptych_taskstamps the saved split profile onto the asset; wired into all three export builders and the preview/render path.composite_kind→"diptych", both-sides-filled split badge, controls panel disabled with a status hint. Export settings stay editable (they live outsideControlsPanel.pages).<name>-DIPTYCH.<ext>, reusing the existing-HDRcomposite hook.Two details that are load-bearing rather than incidental:
<source_hash>#1/#2), or the stage caches collide and the second render comes back as the first.log_boundswriteback is suppressed for a diptych — those bounds were measured on one half, under that half's edit.Decisions worth a second opinion
_GAP_FILLis a module constant if the finish border colour turns out to read better.#1/#2rows exist and half-frame mode is off. No new UI, but also no way to opt one scan out.Not in scope
Filmstrip thumbnails and contact-sheet tiles stay whole-frame: a diptych tile costs two pipeline runs per proxy. A diptych's thumbnail therefore will not match its export. Both caveats are in
docs/USER_GUIDE.md.Testing
make lint,make typeclean;uv run pytest tests→ 4183 passed, 11 skipped.New coverage in
tests/test_half_frame.py:join_halvesgeometry and centre-padding,gap_pxscale invariance,slice_half(half=0),diptych_configs(both halves / one / neither / a half / a#stitchcomposite), the render worker using two configs on two slices with distinct hashes, metrics coming from half 1 and marked,_mark_diptychs, the stamped split geometry,composite_kind, and the export filename.One pre-existing test needed a stub:
test_export_flushdrivesrequest_exporton a bareMagicMock, which cannot unpack the new_diptych_taskreturn.docs/CHANGELOG.mduntouched, per repo convention.