Skip to main content

ocrsieve

A sieve for document pages. For every page it answers which extraction path the page needs: what comes out for free, what needs OCR, and what needs a strong model.

Class Meaning Extraction
digital_text native digital text in the PDF layer direct read, zero cost, no OCR
blank no content skip
typed_clean printed or typewritten, no pen strokes at all cheap OCR
typed_signed printed plus signatures, initials or stamps only cheap OCR
typed_annotated printed plus handwriting that carries information OCR that reads handwriting
handwritten the body of the text is handwritten strong model

The five model classes sit on one axis - how much handwritten information the page carries - and the routing decision is a single number read off it:

score = p(typed_annotated) + p(handwritten)

Compare it with a threshold and you have the decision. Uncertainty that does not change the decision (clean versus signed) is marginalised away, and uncertainty that does change it pushes the score up, which is the safe side.

typed_clean and typed_signed route to the same place on purpose. Keeping them apart splits one contradictory training signal - "a pen stroke is irrelevant here, decisive next door" - into two learnable tasks: detecting that a stroke exists, and judging whether it carries content.

digital_text is never a prediction: it is read from the file.

Install

pip install ocrsieve[cpu]      # inference on CPU (onnxruntime)
pip install ocrsieve[gpu]      # inference on CUDA (onnxruntime-gpu)

The base package pulls only pymupdf, pillow and numpy, and the extra picks the ONNX Runtime build - the two runtimes conflict, so exactly one of them has to be chosen. Without either, the deterministic stage still works and the visual model raises a clear ImportError.

The model ships inside the wheel, at ocrsieve/artifacts/model.onnx (16 MB). There is nothing to download at first run.

CLI

ocrsieve examples/1.jpg
ocrsieve examples/1.pdf
ocrsieve ./folder -o manifest.json       # walks the folder, writes JSON
ocrsieve a.pdf b.png ./folder --json     # JSON to stdout
ocrsieve ./folder --no-model             # deterministic stage only
ocrsieve ./folder --inspect              # per-page PDF signals, no model
ocrsieve ./folder --max-image-cover 0    # any image goes to the model
ocrsieve ./folder --threads 8
$ ocrsieve examples/1.jpg
examples/1.jpg
     1  typed_clean      0.55  model     model: typed_clean (score 0.0429)

1 pages in 1 files (0 mixed)
  typed_clean                   1 100.0%
  decided by: {'model': 1}

Python

classify takes anything: a path to a PDF or an image, a directory entry, raw bytes, an open file, a PIL.Image or a pymupdf.Document. Multi-page TIFFs and PDFs come back as several pages.

from ocrsieve import PageClassifier

sieve = PageClassifier()

result = sieve.classify("document.pdf")
result = sieve.classify("scan.jpg")
result = sieve.classify(pdf_bytes, name="upload.pdf")
result = sieve.classify(Image.open("page.png"))
result = sieve.classify(open("document.pdf", "rb"))

for page in result.pages:
    print(page.page, page.label, page.needs_ocr, page.decided_by, page.confidence)

result.mixed                      # pages of different classes in one file
result.pages_needing_ocr()
result.pages_needing_strong_ocr()
result.to_dict()                  # JSON-serializable

confidence, score and probabilities are filled in whenever the model decided, and decided_by says who decided: stage1, model or fallback.

One-shot helpers, for when the classifier does not need to be kept around:

from ocrsieve import classify, inspect

classify("document.pdf")
inspect("document.pdf")           # deterministic stage only, no model loaded

Deterministic stage only - per-page characteristics, no ONNX call:

from ocrsieve import inspect

for view in inspect("document.pdf"):
    print(view.page, view.stage1, view.chars, view.max_image_cover,
          view.has_native_text, view.has_invisible_text_layer, view.reason)

Model only - classify images directly, bypassing the PDF logic:

from ocrsieve import VisualClassifier

model = VisualClassifier()
model.predict([image])
# [{'label': 'typed_clean', 'confidence': 0.91, 'score': 0.04,
#   'needs_strong_ocr': False, 'probabilities': {...}}]

Options:

PageClassifier(
    model_path=None,        # a different .onnx
    use_model=True,         # False = deterministic stage only
    max_image_cover=0.03,   # 0 sends any page containing an image to the model
    render_width=500,       # must match how the model was trained
    threads=None,           # ONNX Runtime cores
    providers=None,         # ONNX Runtime providers, CUDA first when available
)

ocrsieve.available_providers() reports what the installed runtime offers, so a [gpu] install can be checked in one line.

Tests

pip install -e ".[dev]"
pytest

Metadata

Release files for ocrsieve 1.0.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 ocrsieve 1.0.1
File Size Uploaded
ocrsieve-1.0.1.tar.gz 14.9 MB Details

Built distribution (wheel)

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

Total release size: 29.8 MB

Release files / ocrsieve-1.0.1.tar.gz

Download URL ocrsieve-1.0.1.tar.gz
Size 14.9 MB
Tags Source
SHA-256 checksum
How to use checksums
3db186b8173b3bdffb9314121d5a06a1570055f3e9b1e60e9676c4f59465eec3
BLAKE2b-256 checksum
How to use checksums
1b2ed666d2f6501878e669745edab629371b788aa21e3f3a36d93f56abfba9d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.9

Release files / ocrsieve-1.0.1-py3-none-any.whl

Download URL ocrsieve-1.0.1-py3-none-any.whl
Size 14.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
a13ac08c8fe74b104a6e185aa45f4663e7bc82462f08034f601e117a367d010a
BLAKE2b-256 checksum
How to use checksums
87c6a7bb2c1a2189bf2155c672d45ba57c1fabfa382b10aa527991d60b20a434
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.9

Release history Release notifications | RSS feed

This release

1.0.1 This release

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