scanmate-align
Put a scanned or photographed page back onto the original it came from: deskew, rescale and register, trying transform models until one is good enough.
A parallel port of @scanmate/align, not a replacement for it. The
TypeScript remains the reference, and the only useful definition of "correct"
for this package is that it produces the same numbers — bit for bit — as the
TypeScript it parallels.
pip install scanmate-align
from scanmate_align import align_scan
result = align_scan("page1.png", "returned.jpg")
print(result.confidence, result.diagnostics.selected_model, result.transform.rotation_deg)
result.raster sits on the original's canvas, at the original's width and
height, so every coordinate known from the PDF still means what it meant.
Why it exists
Two questions about a returned form are easy to answer once the scan sits exactly on top of the original, and near-impossible before:
- Was anything in the printed text changed? Run OCR on both and diff — which only works if the two are the same page at the same size.
- Was the box at
(x, y)signed? That is a question about a fixed rectangle, and a fixed rectangle only means something once both images agree on where(x, y)is.
How it works
decode → ink → coarse guess → rough warp → features →┬→ RANSAC(similarity) → score →┐
(scale/skew) (ORB) ├→ RANSAC(affine) → score →┼→ warp
└→ RANSAC(homography) → score →┘
The coarse guess exists to make the feature stage possible at all: binary descriptors compare fixed pixel offsets, so they only match between images at comparable scale — and nothing in a JPEG tells you what dpi it was scanned at.
Everything left of the fork happens once, and it is nearly all of the cost. The sweep tries models cheapest first, stops as soon as one is good enough, and a more complex model must earn its extra parameters by a margin: a homography fitted to a flat page bends slightly to follow the page's own noise and would otherwise win every time.
If no model finds a consensus — a nearly blank form has few corners to find —
the coarse estimate is returned on its own and method says so.
One difference from the TypeScript
alignScan is async; align_scan is not. That asynchrony is about libvips on
libuv's threadpool, not about the algorithm — between decode and encode the
TypeScript is as CPU-bound and single-threaded as this. Python's codec is Pillow
and synchronous, so an async def here would be a coroutine that never yields.
The departure is in the calling convention only. Every number is the TypeScript's, held there by goldens generated from the real build — including a byte-identical warped raster on every case.
Requirements
Python 3.11 or newer, and scanmate-ink. Fully typed (PEP 561), mypy --strict
clean.
Release files for scanmate-align 0.24.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| scanmate_align-0.24.2.tar.gz | 66.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scanmate_align-0.24.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 108.8 kB
Release files / scanmate_align-0.24.2.tar.gz
| Download URL | scanmate_align-0.24.2.tar.gz |
|---|---|
| Size | 66.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
521a4696ceeb18b36cb244c1ec8d17cefd9f282485e9b419af18a095dea4689b
|
|
BLAKE2b-256 checksum How to use checksums |
dce91b893b95c9d6a8a303b9f64d4c8a1c96f49c4d640c61a1f2950262a1d90a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / scanmate_align-0.24.2-py3-none-any.whl
| Download URL | scanmate_align-0.24.2-py3-none-any.whl |
|---|---|
| Size | 42.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf3422a403dcf2a68fba801399a2857be737f9466e07f8041b7313fd8175d387
|
|
BLAKE2b-256 checksum How to use checksums |
384f1f8c29f49e935fef5c50452790893f4fe45eaa0ceea5b38adaff2f7e1706
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|