Repository navigation
Scan: exposure lock for nkscan roll scanning - #1146
Merged
Merged
Conversation
marcinz606
force-pushed
the
feat/nkscan-exposure-lock
branch
from
September 23, 2026 09:33
69fb8fc to
648df0a
Compare
nkscan meters every frame by itself. For colour negative it scales each channel so the frame rectangle's 99.9th percentile reaches 97% of full scale. Mid-strip that is the orange film base, so the mask comes off before the ADC; at a strip end it is bare light past the cut, so the mask stays in. Metering every frame of a loaded LS strip on the scanner gave frames 2-6 within 3% of each other and frame 1 at -0.24/-0.61/-0.76 log10 (R/G/B), the same G/B offset the strip-end frames showed in Roll Analysis. - Scan panel: Exposure row (Meter Frame... / Unlock) for backends with ScannerCapabilities.exposure_lock. The lock lives in ScannerSettings, so it holds across the six batches of a roll and across restarts; a lock metered on another device is ignored. - ScanParams.exposures reaches nkscan's scan_frame, which then skips metering; ScanResult.exposures reports what a scan ran at. - NkscanBackend.meter meters the whole detected frame with its offset, never the scan window, infrared included. It uses Session.meter_frame where nkscan has it and falls back to the smallest scan_frame. - MeterRequest / ScanWorker.run_meter, modelled on prescan. The batch's per-frame offset formula is one function shared with metering.
nkscan 0.12.0 ships Session.meter_frame, so metering no longer falls back to the smallest scan_frame on older releases. 0.12 also reports whether a pass arrived whole. A short frame pass raises TransientScanError, so ScannerService retries it rather than writing planes that hold data only for the blocks that arrived. A short thumbnail pass raises before its frames are cached, since its fit reads the missing blocks as film ending early. The user guide says release builds include nkscan and sends source installs to CONTRIBUTING.md, which gains a Coolscan entry.
marcinz606
force-pushed
the
feat/nkscan-exposure-lock
branch
from
September 25, 2026 07:26
648df0a to
c925779
Compare
marcinz606
marked this pull request as ready for review
September 25, 2026 07:26
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.
Problem
nkscan meters every frame on its own. For color negative it scales each channel so the 99.9th percentile of the frame rectangle lands at 97% of full scale. In the middle of a strip, the brightest area is the orange film base, so the mask comes off before the ADC. At a strip end it is the bare light past the cut, so the mask stays in. Those frames come out 0.4–0.6 D denser in G/B, which is what broke Roll Analysis on the marbella rolls (#1145).
Measured on the scanner, one 6-frame strip:
A roll is six strips, so six batches, and the lock has to last across them.
Change
ScannerCapabilities.exposure_lock(nkscan only). The lock is saved inScannerSettings, so it survives batches and restarts. A lock metered on another device is ignored.ScanParams.exposuresreaches nkscan'sscan_frame, which then skips metering.ScanResult.exposuresreports the exposures a scan ran at.NkscanBackend.meter: meters the whole detected frame with its offset, never the scan window, because the film base in the gap is what the meter should land on. Infrared is included, so the lock also holds for IR and ICE scans.MeterRequest/ScanWorker.run_meter, modelled on prescan. The batch's per-frame offset formula is one function, shared with metering.scan_meter_frame(no default key), and a USER_GUIDE bullet.Test plan
make all: lint, ty, full pytest (6892 passed)meter_framepath, fallback, LS-50 superfine), service, worker, settings round-trip, scan sidebara_frame_scans_at_the_exposures_metered_for_iton the scanner