pagescan

Document scanner for Python

Phone photo in,
deskewed print-ready
PDF out.

Deskew, orient, enhance, PDF — locally. Nothing leaves the machine: no upload, no API key, no third party holding a copy of the document you just photographed.

pip install git+https://github.com/7RPlus-GmbH/pagescan
$ pagescan photo.jpg output.pdf
INFO: Processing: photo.jpg
INFO: Detection: legacy
Saved: output.pdf
Quality: 0.87

Why it exists

Built for the documents you cannot upload.

01

Runs on your machine

Detection, segmentation and enhancement all happen locally. The weights sit on disk; the photo never leaves the process.

02

Headless and scriptable

One command per file, or a loop over ten thousand. No GUI, no desktop session, nothing to click — it belongs in a cron job.

03

Made for regulated work

MIT licensed and open weight, so a compliance review can read every line and every model that touched the page.

How pagescan reads a page

Seven stages, two escape hatches.

Stages 01–04 are model-driven and can fail. Both fallbacks rejoin the main flow, so 05–07 always run on the same contract.

Input photo source

01 · Locate the page

01

YOLO11 detector

Finds the page.

bbox
DefaultSA24 + LCNet — on the current weights this chain beats the cascade on the held-out benchmark (44/50 vs 35/50 at IoU ≥ 0.90) and runs ~75× faster, so it is what ScanConfig() uses. The cascade is opt-in via use_cascade=True.
02

HQ-SAM ViT-B segmenter

Refines the edge.

mask
03

Quad fit

Hull → polyDP.

4 corners
04

Validate & repair

Quad sanity checks.

valid

02 · Rectify

05

Perspective transform

Warp to a flat rectangle.

flat page
AltContour fallback — no usable quad, so the page is cropped to its bounding box instead.
06

Orientation correction

Upright the page.

upright

03 · Finish

07

Enhancement

Shadow removal, white balance, sharpen.

300 dpi
PDF output output

The dashed boxes are fallbacks, not steps: a photo that detects and fits cleanly never touches them. Everything downstream of 04 receives the same shape either way, which is why a rejected quad degrades the result instead of failing the run.

Open weights

Every model that touches the page, published.

Detection, segmentation and orientation weights on Hugging Face under Apache-2.0 — audit them, fine-tune them, redistribute them. The library itself is MIT.

YOLO11n

Detector. Finds the page in the photo and hands on a bounding box.

HQ-SAM ViT-B

Segmenter. Turns the box into an edge accurate enough to fit a quad to. From SysCV’s sam-hq, Apache-2.0.

MobileNetV3 small

Orientation. Decides which way is up, so the rectified page comes out upright. From the OnnxTR project, Apache-2.0.

FastViT-SA24 + LCNet100

The corner-regression chain that runs by default — faster than the cascade and, on the current weights, more accurate on held-out photos. From DocsaidLab’s DocAligner, Apache-2.0.

Install & use

One command per page.

Command line

# not on PyPI yet — install from source
$ pip install git+https://github.com/7RPlus-GmbH/pagescan

$ pagescan photo.jpg output.pdf

# a directory of photos, one PDF each
$ pagescan --batch --input-dir photos/ \
           --output-dir scans/ --workers 4

# crop and perspective only, no enhancement
$ pagescan photo.jpg --raw

Python

import pagescan

pagescan.scan("photo.jpg", "output.pdf")
pagescan.scan_batch("input/", "output/")

from pagescan import ScanConfig

cfg = ScanConfig(jpeg_quality=60, deskew=True)
pagescan.scan("photo.jpg", "out.pdf", config=cfg)