Skip to content

feat(plustek): upgrade to pyopticfilm 1.1.2 - #958

Merged
marcinz606 merged 2 commits into
marcinz606:mainfrom
jboneng:feat/pyOpticfilm_Update
Aug 22, 2026
Merged

marcinz606 merged 2 commits into
marcinz606:mainfrom
jboneng:feat/pyOpticfilm_Update

Conversation

@jboneng

@jboneng jboneng commented Aug 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Bumps the Plustek backend to pyopticfilm 1.1.2 and adapts NegPy to the upstream API and orientation fix. Scope is the pyOpticfilm path only — no SANE, roll/feeder, or other backend changes.

pyopticfilm 1.1.2 (library)

Relevant upstream changes this PR depends on:

Added

  • Multi-exposure (ME) scanning for OpticFilm 8200i SE: short and long colour passes merged into the deliverable ScanImage.rgb.
  • Adaptive quiet USB drain on GL128 (DEFAULT_IMAGE_USB_PACE_S, line-aligned throttle during bulk acquire).
  • Integrator notes in docs/negpy-integration.md (orientation, ScanImage contract, migration).

Fixed

  • Scan orientation on mirror_x models (8200i SE): left–right correction in ImagePipeline.assemble(); prescan crop no longer applies a compensating X flip.
  • ME shadow colour fringes from short/long misregistration: improved pass alignment (phase correlation, ROI refinement) and safer merge at misaligned edges.
  • High-PPI memory use in the image pipeline (chunked host processing).
  • 8200i SE IR scans: correct colour DVDSET handling; aligned IR plane on ScanImage.ir.

Changed

  • ScanImage slimmed to NegPy-facing fields only (rgb, dpi, device_model, optional ir). ME bracket data moved to Scanner.last_me_debug (lab/debug use).
  • ME short/long planes stay linear on the bracket; film-base makeup runs once on the merged deliverable.

Integrator notes

  • Forward-only orientation fix: files scanned before 1.1.2 may still appear mirrored; rescan if orientation matters.
  • Breaking: removed ScanImage ME fields (rgb_short, rgb_long, exposure_*, merge_*, align_shift_*). NegPy does not consume those.

NegPy changes

Dependency

  • Require pyopticfilm>=1.1.2 in the plustek optional dependency and lockfile.

Orientation migration

  • Remove prescan_mirror_x and crop_to_scan_window() mirror compensation.
  • Prescan crop uses clamp_scan_area() directly (1:1 with pyopticfilm TA coords).
  • Trust ScanImage.rgb / ScanImage.ir from Scanner.scan() — no NegPy-side horizontal flip.

Plustek backend

  • Quiet USB drain always re-applied after Scanner.open() (_ensure_quiet_usb_drain); not exposed in the UI.
  • Single-pass colour + IR: one scanner.scan(..., infrared=capture_ir, multi_exposure=...) call replaces the old two-pass colour/IR flow and manual align_ir_to_rgb.
  • multi_exposure passed through from ScanParams.

Scan UI

  • New Multi-exposure checkbox beside IR (8200i SE only, off by default), gated on caps.multi_exposure.
  • Persisted in ScannerSettings.

Docs

  • docs/USER_GUIDE.md: multi-exposure control, single-pass IR, orientation/rescan note.
  • docs/PLUSTEK_WINDOWS.md: minimum pyopticfilm 1.1.2.

Unchanged

  • 8200i SE scan-ready lockout, colour ASIC shading cache, SE bringup/crop geometry.
  • SANE backend and non-Plustek scan UI.

Fixes #928

Test plan

  • uv sync --group plustek && make test (or uv run pytest tests/scanners/ tests/test_scan_sidebar.py)
  • Prescan crop matches scan ROI (no double mirror)
  • IR scan: one pass, dust removal aligned
  • Multi-exposure off by default; enabled scan completes on 8200i SE
  • Compare orientation of a new scan vs SilverFast
  • Rescan any pre-1.1.2 files where left/right matters

@jboneng
jboneng marked this pull request as ready for review August 22, 2026 17:21
@marcinz606
marcinz606 merged commit ae65197 into marcinz606:main Aug 22, 2026
1 check passed
marcinz606 pushed a commit that referenced this pull request Aug 26, 2026
…978)

PR #958 ("feat(plustek): upgrade to pyopticfilm 1.1.2") accidentally flattened
the Prescan crop↔window conversion to a plain clamp, dropping the x-reflection
that had been correct since the Plustek backend first shipped (#783).

pyopticfilm flips the image left–right in assemble() (mirror_x, 8200i SE /
8100 V2), so the Prescan the user frames on is the mirror-corrected image while
the hardware crops by sensor x. Passing the display crop straight into TA space
produced correct dimensions but an x-displaced ROI — "correct crop size, offset
one way".

Restore the self-inverse mirror-aware conversion:

- params.py: crop_to_scan_window(crop, *, mirror_x) — reflect x to (1-x2, y1,
  1-x1, y2) when mirrored, passthrough otherwise.
- base.py / plustek_backend.py: re-add prescan_mirror_x capability, wired from
  model.mirror_x.
- prescan_dialog.py: apply the conversion at all three crop↔window points.

Tests: x-reflection, passthrough, self-inverse for crop_to_scan_window, and
prescan_mirror_x surfaced on 8100 V2 caps. Verified on hardware (OpticFilm
8100 V2): a Prescan crop now lands exactly where drawn.
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.

pyOpticfilm (Plustek USB): scans and Prescan preview are mirrored (OpticFilm 8200i SE)

2 participants