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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ocrsieve-1.0.1.tar.gz (14.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ocrsieve-1.0.1-py3-none-any.whl (14.9 MB view details)

Uploaded Python 3

File details

Details for the file ocrsieve-1.0.1.tar.gz.

File metadata

  • Download URL: ocrsieve-1.0.1.tar.gz
  • Upload date:
  • Size: 14.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.9

File hashes

Hashes for ocrsieve-1.0.1.tar.gz
Algorithm Hash digest
SHA256 3db186b8173b3bdffb9314121d5a06a1570055f3e9b1e60e9676c4f59465eec3
MD5 bd3fa3ae2298f548122cdea0f39339be
BLAKE2b-256 1b2ed666d2f6501878e669745edab629371b788aa21e3f3a36d93f56abfba9d0

See more details on using hashes here.

File details

Details for the file ocrsieve-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: ocrsieve-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 14.9 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.9

File hashes

Hashes for ocrsieve-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a13ac08c8fe74b104a6e185aa45f4663e7bc82462f08034f601e117a367d010a
MD5 186141c4c904353b5e914bef453b1abd
BLAKE2b-256 87c6a7bb2c1a2189bf2155c672d45ba57c1fabfa382b10aa527991d60b20a434

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page