Know what is on a scanned page before you run OCR on it.
pagetriage measures tilt, blur, bleed-through and folds, tells a real text layer from a scan, and flags legacy Hindi fonts. It uses no model, so the same page gives the same answer every time. Try it below: it runs in this tab and nothing is uploaded.
The original scan
Drop a PDF here
It stays in this tab. Files up to 256 MB.
Readout
What it reads today
| Question | How | Status | Details |
|---|---|---|---|
| Loading the table. | |||
The same page gives the same answer, byte for byte
One Rust core builds for the command line, for Python and for the browser. Every golden page in the repository runs through all three, on Linux and on macOS, in CI, and the report fingerprints have to match before anything merges.
That is harder than it sounds. The platform maths library (exp, sin, cos) differs in the last digit between glibc, Apple's libm and WebAssembly, and unordered maps iterate in a different order on every run. Both are banned from the core; sums over pixels happen in a fixed order, and the report writer rounds and sorts before anything is printed.
The proof is in the demo above: the fingerprint this tab computes is compared with the one the native build produced for the same sample. The guarantee covers the analyser given identical pixels. Image decoders are outside it, which is why a JPEG opened by two different decoders can differ.
How well it works
Every number comes from this repository's own benchmark scripts. Where the evidence is weaker than the number looks, the last column says so.
| Check | Result | How far to trust it |
|---|---|---|
| Text layer, 300 synthetic PDF pages | 300 of 300 right | Weak: we wrote the corpus. Two settings were tuned on half of it; the other half scored 159 of 159. |
| Legacy Hindi fonts, same corpus | 17 of 17 found, none among the other 283 pages | Weak: the font list was written from memory and no real deed was tested. |
| Tilt, 984 rotations of 82 DIBCO pages | median error 0.05 degrees | Solid for the median; the tail is poor (90th percentile 0.75 degrees, worst 26.5 on papyri). |
| Howe black-and-white method, DIBCO 2011 (held out) | F-measure 89.6, the paper reports 91.9 | Short of the target we set; three dark-background pages cause the gap. |
| Howe, DIBCO 2009 and 2010 (tuned here) | F-measure 94.0, the paper reports 93.8 | Settings were chosen on these pages. |
| Speed | about 68 ms per A4 page at 150 dpi, one thread | Measured on an Apple-silicon laptop, native; 142 pages per second on 12 threads. |
Run it yourself
It is not yet on crates.io, PyPI or npm, so you build from source. rust-toolchain.toml selects Rust 1.98.
Command line
git clone https://github.com/jtushar53/pagetriage
cd pagetriage
cargo build --release -p pagetriage-cli
target/release/pagetriage analyse page.png
target/release/pagetriage structure file.pdf
Add --pretty to indent the JSON; the compact default is the byte-reproducible form.
Python
python3 -m venv .venv
source .venv/bin/activate
pip install maturin pillow
maturin develop --release \
-m crates/pagetriage-py/Cargo.toml
import json, pagetriage_py as pt
from PIL import Image
im = Image.open("page.png").convert("L")
gray, (width, height) = im.tobytes(), im.size
report = json.loads(pt.analyse_page(gray, width, height, 150))
report["quality"]["skew_deg"]
Browser
scripts/install-wasm-tools.sh
PAGETRIAGE_WASM_FEATURES=pdf \
scripts/build-wasm.sh
import init, { analyse_page, rgb_to_gray }
from "./pkg/pagetriage.js";
await init();
const gray = rgb_to_gray(rgbBytes);
const report = JSON.parse(analyse_page(
gray, undefined, width, height, 150,
undefined, "{}"));
Built on
- N. R. Howe, "Document binarization with automatic parameter tuning", IJDAR 2013. doi:10.1007/s10032-012-0192-x; author's manuscript.
- N. R. Howe, "A Laplacian energy for document binarization", ICDAR 2011.
- N. Otsu, "A threshold selection method from gray-level histograms", 1979.
- J. Sauvola and M. Pietikainen, "Adaptive document image binarization", 2000.
- J. Canny, "A computational approach to edge detection", 1986.
- Y. Boykov and V. Kolmogorov, "An experimental comparison of min-cut/max-flow algorithms for energy minimization in vision", 2004.
- B. B. Chaudhuri and U. Pal, "Skew angle detection of digitized Indian script documents", 1997.
- ISO 32000, the PDF specification.
The full list, with what each paper is used for, is in docs/REFERENCES.md. A few entries there are marked unverified: a citation or formula recalled from memory and not yet checked against the paper.