Skip to content

Use native per-axis scanner resolution, resampling y to reach beyond it - #1010

Open
dtrtuser wants to merge 1 commit into
marcinz606:mainfrom
dtrtuser:feat/native-per-axis-resolution
Open

Use native per-axis scanner resolution, resampling y to reach beyond it#1010
dtrtuser wants to merge 1 commit into
marcinz606:mainfrom
dtrtuser:feat/native-per-axis-resolution

Conversation

@dtrtuser

Copy link
Copy Markdown
Contributor

resolution alone is sometimes an artificially narrow convenience subset of what a scanner can actually do — confirmed on this project's reference Epson V500 (epkowa/interpreter backend): resolution tops out at 1600dpi, while the device's real native per-axis capability (x_resolution/y_resolution) reaches 6400 on x and, depending on source, up to 9600 on y.

The two axes aren't just different, they're asymmetric in a way that changes with source: under this device's Transparency Unit, y's native ladder drops 3200 and 6400 entirely (replaced by a 9600 that appears nowhere under Flatbed), while x keeps its full native ladder either way. A plain "use whichever values both axes have in common" approach caps out at 1600 for exactly this reason.

_resolve_resampled_resolutions fixes this by treating x (the sensor-pitch axis, native at every target worth offering on the reference device) as authoritative, and for each of its native values finds the largest native y value at or below it. Any gap between the two is resampled up in software after the read (_resample_rows_to_dpi, bilinear via cv2.resize) — always an upsample, never a downsample, so this only ever recovers real captured detail, never invents it. This is a strict superset of the previous exact-intersection behavior (_resolve_square_resolutions, kept as its own function since "no resampling needed" is occasionally the more precise thing to ask for) and supersedes it in _detect_dpi and at scan time.

IR capture is explicitly rejected together with a DPI that needs resampling: _align_ir_to_rgb documents that even interpolating the IR channel risks softening a thin dust defect's minimum below the detection pipeline's noise floor (a downsample case measured there, 0.22 -> 0.31, "shattered"). Untested for the upsample case this resample is, and not worth guessing at on a precision-sensitive measurement — fails loudly with the native-only alternatives instead of silently degrading dust detection.

Verified end-to-end on an Epson Perfection V500 (epkowa/interpreter, Transparency Unit) on Fedora 44: a 3200dpi scan of a 6x4.5cm medium format negative — unreachable at all under the previous exact-match logic, which capped at 1600 on this device — now scans y natively at 2400 and resamples up, producing visibly more real detail than 1600dpi did, with correct proportions (no stretching on either axis).

Adds regression coverage: pure-function tests for
_resolve_resampled_resolutions and _resample_rows_to_dpi (test_capabilities.py), and scan-level tests in
test_transparency_source_switch.py covering the axis-pair write, the actual row-count upsample on the returned array, the fail-loud path for an unreachable DPI, and the IR-capture rejection. Each new behavior was confirmed to actually fail against the prior (exact-match or no-resample) code before being confirmed against the fix, not just checked for shape.

`resolution` alone is sometimes an artificially narrow convenience
subset of what a scanner can actually do — confirmed on this project's
reference Epson V500 (epkowa/interpreter backend): `resolution` tops
out at 1600dpi, while the device's real native per-axis capability
(`x_resolution`/`y_resolution`) reaches 6400 on x and, depending on
source, up to 9600 on y.

The two axes aren't just different, they're asymmetric in a way that
changes with `source`: under this device's Transparency Unit, y's
native ladder drops 3200 and 6400 entirely (replaced by a 9600 that
appears nowhere under Flatbed), while x keeps its full native ladder
either way. A plain "use whichever values both axes have in common"
approach caps out at 1600 for exactly this reason.

_resolve_resampled_resolutions fixes this by treating x (the
sensor-pitch axis, native at every target worth offering on the
reference device) as authoritative, and for each of its native values
finds the largest native y value at or below it. Any gap between the
two is resampled up in software after the read (_resample_rows_to_dpi,
bilinear via cv2.resize) — always an upsample, never a downsample, so
this only ever recovers real captured detail, never invents it. This
is a strict superset of the previous exact-intersection behavior
(_resolve_square_resolutions, kept as its own function since "no
resampling needed" is occasionally the more precise thing to ask for)
and supersedes it in _detect_dpi and at scan time.

IR capture is explicitly rejected together with a DPI that needs
resampling: _align_ir_to_rgb documents that even interpolating the IR
channel risks softening a thin dust defect's minimum below the
detection pipeline's noise floor (a downsample case measured there,
0.22 -> 0.31, "shattered"). Untested for the upsample case this
resample is, and not worth guessing at on a precision-sensitive
measurement — fails loudly with the native-only alternatives instead
of silently degrading dust detection.

Verified end-to-end on an Epson Perfection V500 (epkowa/interpreter,
Transparency Unit) on Fedora 44: a 3200dpi scan of a 6x4.5cm medium
format negative — unreachable at all under the previous exact-match
logic, which capped at 1600 on this device — now scans y natively at
2400 and resamples up, producing visibly more real detail than 1600dpi
did, with correct proportions (no stretching on either axis).

Adds regression coverage: pure-function tests for
_resolve_resampled_resolutions and _resample_rows_to_dpi
(test_capabilities.py), and scan-level tests in
test_transparency_source_switch.py covering the axis-pair write, the
actual row-count upsample on the returned array, the fail-loud path
for an unreachable DPI, and the IR-capture rejection. Each new
behavior was confirmed to actually fail against the prior (exact-match
or no-resample) code before being confirmed against the fix, not just
checked for shape.
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.

1 participant