feat(profile): build and apply camera profiles for trichrome captures - #125
Open
toonoumi wants to merge 1 commit into
Open
feat(profile): build and apply camera profiles for trichrome captures#125toonoumi wants to merge 1 commit into
toonoumi wants to merge 1 commit into
Conversation
A trichrome capture is not the same device space as a normal Bayer shot: the merge takes the red plane shot under red light, the green under green and the blue under blue, so each channel's sensitivity is the sensor response times its own light, and the channel balance comes from three independent exposures. Two halves of that were missing. Profiling. The IT8 wizard decoded exactly one file, so the space the user actually scans in could not be profiled at all. Page 1 gains a "Trichrome: merge 3 RAWs" checkbox: Browse becomes a multi-select requiring exactly three RAWs, sorted by basename exactly as an import sorts them so the wizard and a 3-way import of the same files agree on which frame is red, green and blue. "Use current image" adopts the triplet directly when the loaded image is already a merge. decode_target_merged sets is_merged/merge_sources on the bare CCRImage that decode_target already builds, and read_image dispatches on that before it looks at the path - so the merged frame arrives in the same bare device space (apply_input_icc=False: no field correction, no camera profile) and everything downstream, sampling through ICC/DCP synthesis, treats it as one raw. The merge mode is shown in the target label rather than silently baked, since it decides the device space the fit happens in. Applying. _read_merged applied field correction and the slice chain and then returned - the DCP/ICC apply the RAW branch performs had no counterpart, so even a correct profile did nothing on the frames it was built for. It now applies in the same pipeline position (field correction -> slice ops -> downsize -> profile). as_shot_wb is None on purpose: a merge has three source RAWs with three different camera_whitebalance values and none describes the merged balance. Since spec/camera-profile-calibration-wb.md a generated profile carries its own calibration neutral and ignores the as-shot value, which is exactly right here. Guard. Swapping the two kinds produces badly wrong colour with no symptom beyond "the colour looks off", so profiles record which space they describe - DCP private tag 52525, ICC private tag CCRk, both absent on every existing profile, so "no tag" means normal. A mismatch logs once per profile+kind pair rather than refusing: colour management is the user's call. Verified end to end on the real trichrome triplet (merge, decode, fit, build, apply, tag round-trip). Fit QUALITY is unverified - there is no trichrome shot of an IT8 chart to test against, only negatives. Spec: spec/trichrome-camera-profile.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Why
A trichrome capture is not the same device space as a normal Bayer shot. The merge takes the red plane shot under red light, the green under green, the blue under blue — so each channel's sensitivity is the sensor response × its own light, and the channel balance comes from three independent exposures. Two halves of that were missing:
_read_mergeddid field correction and the slice chain, then returned — the DCP/ICC apply the RAW branch performs (ccr_image.py:748-750) had no counterpart. So even a correct profile did nothing on the frames it was built for.Profiling from a triplet
Page 1 of the wizard gains "Trichrome: merge 3 RAWs (red, green, blue)":
ccr_merge.sort_for_merge), so the wizard and a 3-way import of the same files agree on which frame is R, G and B.main_windownow hands the dialog the merge descriptor.decode_target_mergedsetsis_merged/merge_sourceson the same bareCCRImagethatdecode_targetalready builds.read_imagedispatches onis_mergedbefore it looks at the path or Positive mode, so the merged frame arrives in the identical bare device space (apply_input_icc=False→ no field correction, no camera profile) and everything downstream — sampling, fit, quality report, ICC/DCP synthesis — is untouched. That's the "treat it as one raw" requirement, implemented as a decode-source change rather than new profiling maths.Applying to merged images
_read_mergednow applies the active profile in the same pipeline position as the RAW branch: field correction → slice ops → downsize → profile.as_shot_wbisNoneon purpose. A merge has three source RAWs with three differentcamera_whitebalancevalues and none of them describes the merged channel balance — that's a property of the three lights and their exposures, so any single frame's metadata would be arbitrary. Since #123 a generated profile carries its own calibration neutral and ignores the as-shot value, which is exactly right here: a trichrome profile balances merged frames on the lighting rig it was calibrated on. A profile without a baked neutral takes the documented unbalanced path.Device-space guard
Swapping the two kinds produces badly wrong colour with no symptom beyond "the colour looks off" — the exact failure this feature exists to remove, and now easy to hit with both kinds in one picker. So profiles record which space they describe: DCP private tag 52525 (LONG), ICC private tag
CCRk(XYZType payload, mirroringCCRn). Both are absent on every existing profile, so "no tag" means normal and nothing already on disk changes meaning. A mismatch logs once per profile+kind pair rather than refusing — colour management is the user's call, and a hard block would be worse than a preview they can see is wrong. The wizard also defaults the name to "Camera trichrome …" so the two are distinguishable.This guard is the one part of the PR the user didn't ask for. It's self-contained (the tag +
is_trichrome+_warn_kind_mismatch) and can be dropped if you'd rather not carry it.Verification
Full suite: 1112 passed, 9 skipped. Two order-dependent failures appear in one chunk (
test_positive_mode,test_sprocket_mask) — I confirmed they reproduce identically on cleanmainatcca21b2, so they're pre-existing pollution, not this change. Both pass in isolation.14 new tests in
tests/test_trichrome_profile.py, following the merge tests' convention of monkeypatching the one rawpy-touching function: bare-device decode contract, triplet ordering, DCP and ICC apply on merged reads,apply_input_icc=Falseand the disable toggle, pipeline position asserted as an explicit order list, the baked-neutral claim, kind round-trip in all three containers (matrix ICC, cLUT ICC, DCP), tag well-formedness (ascending TIFF tags, LONG×1, neutral still round-trips beside it), and warn-once behaviour.End-to-end on the real trichrome triplet in
H:/TempPhoto/it8/3way— merge → decode → sample → fit → build both containers → apply on a merged read →is_trichromeround-trip all work, and the profiled read differs from the bare one as expected.What is NOT verified: fit quality. That folder holds trichrome negatives, not a chart, and there's no trichrome shot of an IT8 anywhere on disk — so the ΔE from that run is meaningless and I'm not reporting it as a result. Shooting the IT8 under the three lights is the remaining validation step, and it's the one that will say whether the profile is actually good.
🤖 Generated with Claude Code