RGB separation mode (3 separate exposures): design sketch + sample data #288
rohanpandula
started this conversation in
Ideas
Replies: 1 comment 1 reply
-
|
It's up next in my plan, I just need some files to work with as I don't have a scanning setup like that 🙂 if you can provide more samples that would be great |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Hi @marcinz606 (and anyone else poking at this),
I saw "RGB (3 separate exposures) mode" on the roadmap and I think I can help, because I've spent the last few months building the capture side of exactly this.
My rig is a Sony a7CR on a copy stand over a film holder, with a narrowband RGB LED backlight driven from a small controller. For each frame it fires the red LED and takes a shot, then green, then blue: three separate ARW exposures of the same negative, with the camera and film held dead still the whole time. So the three files line up to the pixel by construction, and each one only ever saw one narrow band of light. The channels come apart cleanly, and I can set each LED's brightness independently so the dense blue layer gets enough light without blowing out red.
That capture rig works today. What I don't have is the processing side, which is where NegPy comes in. I'd rather build this with you than drop a giant PR on your head, so here's how I'd fit it into the code you already have, plus the calls I think are yours to make.
The short version
It's your flat-field feature with three reference files instead of one.
I read through how #255 wired up flat-field correction, and it already solved the hard part: attaching extra files to one image without breaking the file-hash = one-recipe model. RGB mode is the same shape.
Where it would live
RgbSetConfig(enabled: bool, r_path: str, g_path: str, b_path: str), mirroringFlatFieldConfig(enabled, reference_path)innegpy/features/flatfield/models.py. One line to add it toWorkspaceConfiginnegpy/domain/models.py, same as theflatfieldfield._load_source_f32(negpy/services/rendering/image_processor.py), right whereapply_flatfieldalready runs on the buffer (and the matching spot in the preview loader). Whenrgb_set.enabled, instead of the normal rawpy decode I load the three files, take one channel from each, and stack them into the(H, W, 3)float32 buffer the rest of the pipeline expects.reference_pathdoes today. Yourflatfield_tokenmtime trick maps straight over for busting the cache when one of the three files changes.flatfield_profilesnamed-profile table is a ready-made pattern for "this whole roll was shot as RGB sets."For decode I'd pull each file with
output_color=raw,user_wb=[1,1,1,1],gamma=(1,1),output_bps=16, so I'm stacking honest sensor values and letting your normalization do the balancing.Calls I think are yours
These are spots where I have an opinion from the capture side, but it's your codebase, so I'd rather ask than assume.
Channel extraction. The a7CR is a Bayer sensor, not mono, so "the red exposure" still comes off rawpy as full RGB. The simple move is to take the matching channel from each (R from the red-lit shot, and so on). The green channel under a green LED actually resolves best, since there are twice as many green photosites, so there's an argument for leaning on that. I can also shoot these as Pixel Shift composites, which gives true colour per pixel and sidesteps the question. That feels like a v2 rather than a v1 dependency.
Exposure balancing. I balance the three channels at capture by tuning each LED's brightness rather than the shutter. In the sample set below all three are 1/25s, and the raw channel means already land within about 1.6x of each other (roughly 2190/2930/3580 for R/G/B), so the stack is close to balanced before you ever see it. If you'd rather not lean on that, the merge could carry explicit per-channel gains, or normalize from EXIF for sets that do vary the shutter per channel. Your call on how much to bake in.
Registration. My rig holds everything still, so the frames are aligned and I'd assume "already registered" for v1. If you ever want to accept sets shot on a looser setup, optional alignment can come later.
UI. Two options that both reuse what's there: a three-file picker dialog like the flat-field "Add" flow, or a "combine selected as RGB set" action on the file browser, which already does multi-select. I lean toward the picker for v1 because it's explicit about which file maps to which channel.
Separation. This one mostly takes care of itself. Narrowband capture leaves almost no inter-channel crosstalk to correct, so the Separation control sitting at its 1.0 no-op default is already close to right for these scans. The small residual you'll see in the sample (more below) is the only thing it would touch.
A small first slice
If you're up for it, I'd keep the first PR narrow:
RgbSetConfig, theapply_rgb_mergelogic, a minimal way to set the three paths, and a unit test in the shape oftests/test_flatfield_logic.py(synthetic arrays, no file I/O). No profiles, no fancy UI yet. Get the core merge landing real pixels, then iterate on the parts above once you've seen it run.Sample set to poke at
Nobody can build or test this without real three-exposure sets, so here's a first one: https://github.com/rohanpandula/TriRGB/releases/tag/negpy-rgb-sample-1
One 35mm frame, three 61 MP a7CR ARWs, one per narrowband LED:
DSC00448.ARWis the red LED (raw mean ~84% in R)DSC00449.ARWis green (~74% G)DSC00450.ARWis blue (~68% B)All ISO 100, 1/25s, camera and film static between frames so they line up to the pixel. You can see the CFA still leaks a little between channels (the 84/74/68 instead of a clean 100), which is the small residual your Separation control is there to mop up. If it's useful I'll add more across different stocks plus a flat, and shoot a white-light version of the same frame so we can measure real dye crosstalk for the gallery.
Does this line up with how you were picturing it? Happy to adjust the shape. And if you'd rather I just sit on the code and you take the sample set for a spin first, that works too.
Beta Was this translation helpful? Give feedback.
All reactions