The Problem

Document ingestion pipelines often rely on GPU‑heavy OCR engines that add latency and cost. A lightweight, CPU‑friendly OCR that still yields accurate text, layout, and reading‑order information is missing in many production stacks.

What This Does

Indra OCR delivers a 1 M‑parameter model that reads ~49 pages/s on a single CPU core.

  • indra_ocr/__main__.py and indra_ocr/cli.py expose the indra‑ocr command line tool.
  • The core pipeline lives in indra_ocr/pipeline.py, which stitches together:
  • indra_ocr/det_post.py – post‑processing of line detections,
  • indra_ocr/rec_preproc.py – image preprocessing for the recognizer,
  • indra_ocr/models.py – ONNX runtime wrappers for the text recognizer,
  • indra_ocr/layout_post.py & indra_ocr/order_post.py – layout block classification and reading‑order inference.
  • indra_ocr/document.py reads PDFs, preserving existing text layers for digital PDFs.
  • indra_ocr/viewer.py powers the optional web viewer (indra‑ocr‑viewer).

How It Is Wired

Execution starts at the console script defined in pyproject.toml, which imports indra_ocr/cli.py. cli.main() dispatches to pipeline.process() for each input file. pipeline.process() calls document.load() to read the PDF or image; if the PDF contains a text layer, document.TextLayer objects are returned unchanged. The image is fed to rec_preproc.preprocess() and then to models.Recognizer.recognize(). Recognized text, along with bounding boxes, is passed to det_post.postprocess() to merge lines into blocks. Finally, layout_post.classify_blocks() assigns semantic labels, and order_post.compute_order() produces a reading‑order list. The resulting JSON, markdown, or plain text is written by cli.output_writer(). The viewer command imports viewer.run() which serves the JSON locally.

Key functions with wide blast radius:

  • pipeline.process() – orchestrates the entire OCR flow.
  • models.Recognizer.recognize() – the ONNX inference engine.
  • det_post.postprocess() – merges lines into logical blocks.

How To Use It

Setup

pip install indra-ocr

Configuration No external config files are required; the model is downloaded automatically from Hugging Face the first run.

Running it

indra-ocr paper.pdf                 # stdout markdown
indra-ocr *.pdf --out results/      # one .md per file, parallelized
indra-ocr scan.png --format json    # JSON with boxes and confidences
indra-ocr-viewer                    # launch web viewer on localhost:8765

These commands use the entry points defined in pyproject.toml.

Real‑World Use

A batch ingestion service for a publishing house can wire indra-ocr into a Celery queue. Each worker pulls a PDF, runs indra-ocr locally, and pushes the resulting JSON to an S3 bucket for downstream indexing.

# worker/task.py
from indra_ocr import pipeline

def process_pdf(path: str, out_dir: str):
    result = pipeline.process(path)
    with open(f"{out_dir}/{Path(path).stem}.json", "w") as fp:
        json.dump(result, fp)

Code Health & Issues

  • Low / Dependency lockfile missing – pyproject.toml lists all dependencies but no poetry.lock or pipfile.lock is present, making builds non‑reproducible. (File: pyproject.toml)
  • No CI for code coverage – .github/workflows/publish.yml only builds; unit tests are present but coverage metrics are absent. (File: .github/workflows/publish.yml)

No other static analysis findings were reported.

The Bottom Line

Indra OCR offers a fast, CPU‑centric OCR solution with full layout awareness and a clean CLI. It is ideal for high‑volume, low‑budget pipelines that still need accurate reading order. The main trade‑off is the lack of a lockfile, which may affect reproducibility in tightly controlled environments.