Skip to content

Pieusb integration - #747

Draft
davide-b wants to merge 15 commits into
marcinz606:mainfrom
davide-b:pieusb_integration
Draft

Pieusb integration#747
davide-b wants to merge 15 commits into
marcinz606:mainfrom
davide-b:pieusb_integration

Conversation

@davide-b

@davide-b davide-b commented Aug 3, 2026

Copy link
Copy Markdown

Add a second scanner backend: pieusb (direct USB)

Adds PieusbBackend alongside SaneBackend, driving Reflecta / Pacific Image
film scanners over raw USB through the pieusb
library instead of SANE. It is the first non-SANE backend, so a few
transport-neutral seams had to grow to fit it.

Requires pieusb>=0.3.1, added as an optional dependency group (uv sync --group pieusb).

What works

  • Enumeration: pieusb:{bus}:{address} device ids; capabilities derived
    from INQUIRY
  • Scanning: ScanParams maps onto pieusb's attribute-style options
    (mode, color_depth, resolution, auto_exp, and the normalised window
    scaled into device units).
  • Hardware auto-exposure: runs a preview scan and adjusts scan parameter. It is a port of the SANE C backend's
    analyze_preview + set_gain_offset("from preview") + updateGain2.
  • The Scan tab now appears on Windows. It was replaced by
    _ScanUnsupportedPlaceholder there because python-sane has no Windows build;
    pieusb is pyusb + a bundled libusb, so that gate is gone.

Progress reporting gained a phase

The bar used to move only during the read. A pieusb scan has distinguishable
phases (configuring, warming up, calibrating, metering, scanning,
post-processing), and with auto-exposure it runs the whole sequence twice, so a
single fraction was misleading. When the phase name is not provided, it defaults to "Scanning"

ScanWorker.progress / AppController.scan_progress are now
pyqtSignal(float, str) and the bar renders "Calibrating… 42%". The backend
contract in base.py:

Consequences worth knowing when reviewing:

  • The fraction is per-phase, so a multi-phase bar rewinds between phases.
    That is intentional; the label is what carries the meaning.
  • SANE's behaviour is unchanged — it reports one phase and still calls
    progress(fraction). Three of its signatures widened to Callable[..., None]
    so SaneSession still satisfies the ScannerSession protocol while its
    one-argument calls stay valid; no call sites or logic touched.
  • Consumers of progress default the phase (ScanWorker's emit wrapper, its
    batch closure, PerFrameRollSession's discard lambda).

Autofocus is now a capability

Added autofocus support in the scanner capabilities dataclass, as some Reflecta scanners don't support it. It will hide the checkbox and set it to unchecked if this function is not supprorted.

Other things to note

  • PieusbSession is a stubopen_session raises NotImplementedError.
    Nothing in negpy/ calls it: single scans, run_batch frames and
    PerFrameRollSession preview slots all go through backend.scan.
  • No USB permission / driver diagnosis at enumeration (a missing udev rule or a
    vendor-driver-bound device on Windows currently just looks like "no devices").
  • tests/scanners/test_backend_contract.py still runs against SANE only; adding
    pieusb needs a fake pieusb module and a Scanner double.
  • frame and frame_offset_mm are accepted and ignored.

Risks / what to check before merging

  • Hardware coverage is one unit (ProScan 10T)
  • The SANE backend is touched (three annotations, no logic) and base.py's
    protocol changed, so the shared paths deserve a look even though SANE's
    behaviour is unchanged.

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