Skip to main content

pyopticfilm

Python driver for Plustek OpticFilm USB film scanners, built on PyUSB and reverse-engineered from USB captures and the SANE genesys backend.

The library talks directly to the scanner’s Genesys ASIC (GL842, GL843, GL845, or GL128) over USB—no vendor Windows driver is required once the device is bound for libusb access.

Supported hardware

Only the OpticFilm 8200i SE is validated for scanning in this release.

Model USB ID ASIC Scan
OpticFilm 8200i SE 07b3:1825 GL128 Yes
OpticFilm 8200i 07b3:130d GL845 Probe only
OpticFilm 8100 07b3:130c GL845 Probe only
OpticFilm 7600i (v1 / v2) 07b3:0c3b GL845 Probe only
OpticFilm 7500i 07b3:0c13 GL845 Probe only
OpticFilm 7400 (v1 / v2) 07b3:0c3a GL845 Probe only
OpticFilm 7300 07b3:0c12 GL845 Probe only
OpticFilm 7200i / 7200 07b3:0c04, 07b3:0807, 07b3:0c07 GL843 / GL842 Probe only

Other OpticFilm models enumerate and open: you can read status, turn the lamp on/off (where implemented), and dump registers for bring-up. scan(), calibrate(), home(), and park() are intentionally gated on probe-only models until the protocol is verified on real hardware—calling them raises AsicError with a clear message rather than risking carriage or lamp damage on untested tables.

Scanner.open() prefers a scan-ready 8200i SE when several Plustek film scanners are connected.

Features (8200i SE)

  • Color and infrared transparency scans at 150–7200 dpi (ASIC programs at ≥600 dpi; lower PPI shares the 600 dpi register set and is downsampled on the host)
  • Optional crop via normalized area (x1, y1, x2, y2 in 0–1)
  • Dark/white shading calibration with on-disk cache (~/.cache/pyopticfilm/calib_v2.json)
  • GL128 ASIC shading path (AFE codes + shading blob) aligned with SilverFast capture order
  • 16-bit RGB numpy output; optional TIFF export via tifffile
  • Progress and cancel hooks for long scans

Not implemented or out of scope here: iSRD infrared dust removal, SilverFast-style UI, or shipping a desktop app—applications own post-processing.

Requirements

  • Python ≥ 3.11
  • numpy, pyusb
  • libusb 1.0 backend for PyUSB
    • Windows: libusb-package is installed automatically with pip install pyopticfilm and provides a bundled libusb-1.0.dll
    • Linux: system libusb-1.0 (e.g. libusb-1.0-0 on Debian/Ubuntu) and permission to access the device (udev rule or run as root—not recommended)
    • macOS: libusb via Homebrew or libusb-package

Optional: tifffile for ScanImage.save_tiff().

Installation

pip install pyopticfilm

From source:

git clone https://github.com/jboneng/pyopticfilm.git
cd pyopticfilm
uv sync --all-groups

USB access

The Plustek vendor driver must not own the device when using this library.

Windows

Use Zadig to replace the vendor driver with WinUSB (or libusbK) for the scanner’s USB interface. Step-by-step instructions, troubleshooting, and how to revert to the Plustek driver are in docs/windows-setup.md.

Linux

Install libusb and add a udev rule granting your user access to Plustek film scanners, for example:

# /etc/udev/rules.d/99-plustek-opticfilm.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="07b3", MODE="0666"

Reload udev rules and replug the scanner.

Quick start

from pyopticfilm import Scanner

with Scanner.open() as scanner:
    print(scanner.model.model, scanner.device_id)
    scanner.warmup()  # init, home, lamp on
    image = scanner.scan(resolution=1800, mode="color")
    print(image.rgb.shape, image.rgb.dtype)  # H×W×3 uint16

    image.save_tiff("frame.tif")  # requires tifffile

Infrared scan (8200i SE):

with Scanner.open() as scanner:
    scanner.warmup()
    ir = scanner.scan(resolution=1800, mode="infrared")

Crop (normalized coordinates on the transparency window):

image = scanner.scan(
    resolution=2400,
    mode="color",
    area=(0.1, 0.1, 0.9, 0.9),  # x1, y1, x2, y2
)

List devices without opening:

from pyopticfilm.usb.device import find_devices

for info in find_devices():
    print(info.device_id, info.product_id, info.asic_hint, info.is_supported)

API overview

Entry Purpose
Scanner.open(device_id=None) Open preferred or specified OpticFilm
scanner.warmup(home=True, lamp=True) Boot ASIC, optional home + lamp
scanner.scan(...) Run a full scan → ScanImage
scanner.calibrate(...) Run shading; updates cache
scanner.status() Read scanner status flags
scanner.home() / scanner.park() Motor positioning
scanner.lamp_on() / scanner.lamp_off() Lamp control (allowed on probe-only models)
scanner.advanced Low-level register read/write (bring-up)
scanner.calibrator Direct access to calibration cache

ScanImage fields: rgb (uint16 H×W×3), dpi, device_model, optional ir plane (not populated by default).

Scan modes: "color", "infrared". "gray" is not implemented.

Enable debug logging:

from pyopticfilm.logging import enable_debug_logging

enable_debug_logging()

Calibration

Shading runs automatically before scan when a matching cache entry exists. Force a new calibration with scanner.calibrate(force=True) or scanner.scan(..., apply_calib=False) to skip applying cached data.

The cache key includes resolution, crop geometry, and scan method (transparency vs infrared). GL128 colour shading uses ASIC-internal measurements at home; IR uses a white-only path suitable for stationary shading.

Probe-only models

Code for additional OpticFilm variants is included so enumeration, model selection, and SANE-derived geometry tables can be exercised without hardware. These paths are deliberately locked for motor moves and image acquisition:

  • model.scan_ready is False for every model except the 8200i SE
  • Scanner._ensure_scan_ready() blocks scan, calibrate, home, and park
  • GL128 motor moves stay disabled unless the model is scan-ready

If you have a non-SE OpticFilm and want to help validate scanning, open an issue with your exact USB IDs (bcdDevice matters for some models) and we can work through capture-based bring-up.

Development

uv sync --all-groups
uv run ruff check .
uv run pytest -q

CI runs on Python 3.11–3.13 (lint + tests; no hardware in CI).

Project layout:

  • src/pyopticfilm/usb/ — enumeration, claim, Genesys USB protocol
  • src/pyopticfilm/asic/ — per-ASIC drivers (GL128, GL845, …)
  • src/pyopticfilm/device/ — per-model register/geometry tables
  • src/pyopticfilm/scan/ — geometry, calibration, scan session pipeline

License

GPL-3.0-or-later — see LICENSE.

Acknowledgements

Register and motor tables for GL845-family models are derived from the SANE genesys backend. The 8200i SE (GL128) protocol was reconstructed from USB traffic captures of the Windows driver and SilverFast; it is not present in SANE.

Release files for pyopticfilm 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyopticfilm 1.1.1
File Size Uploaded
pyopticfilm-1.1.1.tar.gz 114.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyopticfilm 1.1.1
File Interpreter ABI Platform
pyopticfilm-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 235.4 kB

Release files / pyopticfilm-1.1.1.tar.gz

Download URL pyopticfilm-1.1.1.tar.gz
Size 114.1 kB
Tags Source
SHA-256 checksum
How to use checksums
30f9e243f101173636e7adec0fe604a7fcd165f2992b412f0868f76904b551c8
BLAKE2b-256 checksum
How to use checksums
47c22de6d124d7b716e6d2e83b118239f1e3e9c76cd5f2c686d7963feb814b73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 12, 2026.

Transparency log

Release files / pyopticfilm-1.1.1-py3-none-any.whl

Download URL pyopticfilm-1.1.1-py3-none-any.whl
Size 121.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c04562cec868b7bb8d1086cacd59711c68ac90a3140d3da4c52ce23369a8a718
BLAKE2b-256 checksum
How to use checksums
e97adf1ee2d844a237710733b087e8c38f12af9fa8a4826853806bad0dd8fdd6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 12, 2026.

Transparency log

Release history Release notifications | RSS feed

1.3.4

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.2

2 release files

This release

1.1.1 This release

2 release files

1.1

2 release files

1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page