Skip to content

feat(plustek): add Multi-Pass scanning, refine Multi-Exposure controls - #1064

Draft
TobbyTravel wants to merge 4 commits into
marcinz606:mainfrom
TobbyTravel:feat/multi-pass-stacking
Draft

feat(plustek): add Multi-Pass scanning, refine Multi-Exposure controls#1064
TobbyTravel wants to merge 4 commits into
marcinz606:mainfrom
TobbyTravel:feat/multi-pass-stacking

Conversation

@TobbyTravel

@TobbyTravel TobbyTravel commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Multi-Pass scanning for Plustek GL128 devices (repeat an exposure and stack the results for a noise reduction), and refines the existing Multi-Exposure control alongside it. The two are independent axes that compose, giving four scan modes total:

  • Single-Pass — one exposure per scan. Fastest, standard quality.
  • Multi-Pass — repeats the same exposure and stacks the results to cut noise.
  • Adaptive Multi-Exposure — automatically captures a short and long exposure and fuses them for extended dynamic range. No stacking.
  • Adaptive Multi-Pass — Multi-Exposure and Multi-Pass together, for the most dynamic range and the least noise this scanner can produce.

GUI refinement

  • Scan mode collapsed into the one combo above (four named options) instead of a Multi-exposure checkbox with hidden state.
  • New Passes control (1-9) next to it, shown only for a stacking mode.
  • IR and stacking are mutually exclusive (each repeat is its own motor cycle; IR stacking isn't validated yet) — the UI now resolves this automatically wherever it's set, including a saved settings file, so it can't reach Scan in a contradictory state.
  • Progress reporting understands stacking: one progress boundary at the short→long exposure change, regardless of how many passes are stacked on each side of it.

Benchmark (8100 V2, 7200dpi, same negative frame)

Benchmark, same negative frame, 8100 V2 @ 7200dpi (per-channel noise std-dev on matched flat crops; R/G/B, and average % noise reduction vs. Single-Pass baseline):

Mode R G B vs Single-Pass
Single-Pass 3.78 5.68 4.58 — (baseline)
Multi-Pass ×4 2.92 3.96 2.40 −33%
Adaptive ME (42k-capped, pre-fix) 2.81 2.71 2.60 −41%
Adaptive ME (64k-capped, post-fix) 2.37 1.85 1.90 −55%
Adaptive Multi-Pass ×4 (42k-capped) 2.42 1.84 1.43 −58%

Test plan

  • pytest tests/scanners/ tests/test_scan_sidebar.py -q — 555 passed, 4 skipped
  • pytest tests/ -q — full suite green (5490 passed, 22 skipped)
  • Manual: 8100 V2 @ 7200dpi, all four modes — no ASIC comm-loss or motor issues; IR correctly disables at Passes>1

Draft — awaiting a pyopticfilm release containing PR #65 before this is ready to merge.

pyopticfilm's Multi-Pass feature (PR marcinz606#65) repeats a single exposure
n_passes times and stacks the aligned repeats for an SNR gain, as an
axis orthogonal to the existing ME (short+long) fusion rather than an
alternative to it. Replaces the earlier N-brackets design this branch
was originally built against (never shipped upstream) with a plain
"Passes" control (1-9) independent of the Multi-exposure mode combo
(Off/Adaptive/Fixed), matching pyopticfilm's actual API:
Scanner.scan(multi_exposure, me_exposure_mode, n_passes, align_passes).

IR and Multi-Pass cannot combine yet (pyopticfilm rejects it), so the
sidebar disables/resets whichever control was set second, and the
backend fails fast with a RuntimeError before touching hardware if
both reach it anyway.

Claude-Session: https://claude.ai/code/session_016bn5DFZyR6BS8ApqhPXUoY
Simplifies the two independent ME-mode/Passes controls into one
"Scan mode" combo (Single-Pass, Multi-Pass, Adaptive Multi-Exposure,
Adaptive Multi-Pass) with a 2-9 slider that only appears for the two
stacking modes. Drops "Fixed" long-exposure mode from NegPy's surface
entirely — it's a pyopticfilm lab/debug-only concept now (see Scan
Lab, which keeps full unrestricted access to it and the manual
exposure overrides).

- MultiExposureMode loses FIXED; ScannerSettings' existing
  unknown-value-degrades-to-off migration path already handles a
  persisted "fixed" blob safely, no new migration code needed.
- New ScanCaptureMode (UI-only, never persisted) with translation
  helpers to/from the real (multi_exposure_mode, n_passes) fields,
  keeping the domain model as pyopticfilm's actual orthogonal axes.
- Checking IR while a stacking mode is selected drops the mode to its
  non-stacking equivalent (Multi-Pass -> Single-Pass, Adaptive
  Multi-Pass -> Adaptive Multi-Exposure) rather than silently
  resetting a hidden value; selecting a stacking mode while IR is
  checked unchecks IR.
- Per-item capability gating on the mode combo, with graceful
  fallback (drop just the unavailable axis) if the current selection
  becomes invalid on a capability change.

Claude-Session: https://claude.ai/code/session_016bn5DFZyR6BS8ApqhPXUoY
…ing conflict

pyopticfilm dropped its "fixed" ME exposure mode (never exposed in this
app's UI) and the me_exposure_mode parameter along with it — remove the
now-nonexistent kwarg from the Scanner.scan() call.

Also self-heal an IR + Multi-Pass stacking conflict at settings-load time
the same way the live toggle handlers already do, instead of only
catching it when the user touches a control (a settings blob saved with
both set would otherwise reach Scan and fail there). Replace the locally
mirrored MAX_PASSES_UI constant with an import of pyopticfilm's own
MAX_N_PASSES, and add a test for the n_passes=9 upper boundary.

Claude-Session: https://claude.ai/code/session_01KThZJVYa894isy2k8aZXtK
@TobbyTravel TobbyTravel changed the title feat(plustek): add Multi-Pass same-exposure repeat stacking feat(plustek): add Multi-Pass scanning, refine Multi-Exposure controls Sep 8, 2026
self._caps_max_n_passes was written in three places but never read; every
live site already passes caps.max_n_passes straight through. First-time
Multi-Pass selection was landing users on the slider floor (2) instead of
the intended default (3).

Claude-Session: https://claude.ai/code/session_01GTVvLh5UfczCAeTKFHzny6
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