0xFl4g/qira-ocr

Hybrid multi-engine OCR for Arabic and English documents

Python

0

15 commits

updated Oct 16, 2026

See the code

README

qira-ocr

A hybrid multi-engine OCR toolkit for Arabic and English documents. Routes between PaddleOCR (fast, strong on printed text and tables), QARI-OCR (state-of-the-art Arabic with diacritics), and Surya (Arabic and handwriting fallback) behind a single unified interface. Supports PDFs, images, and batch processing with output as plain text, JSON, Markdown, or HTML.

Built as a university project at the University of Sheffield.

Project Structure

src/qira_ocr/
  __init__.py          # Public OCR class — single entry point
  loader.py            # PDF text extraction, rasterization, image loading
  router.py            # Automatic engine selection via Arabic script detection
  result.py            # Structured result model (Page > Block > Line > Word)
  structure.py         # Table/layout analysis using PPStructure
  cli.py               # Click CLI (qira-ocr scan ...)
  api.py               # FastAPI REST API (/ocr, /health)
  engines/
    base.py            # OCREngine protocol
    paddle.py          # PaddleOCR wrapper
    qari.py            # QARI-OCR wrapper (Arabic VLM)
    surya.py           # Surya OCR wrapper
tests/                 # 73 tests covering all modules

Installation

Requires Python 3.12+. Install with uv:

uv sync              # core OCR library
uv sync --extra cli  # + command-line interface
uv sync --extra api  # + REST API server
uv sync --extra qari # + QARI-OCR engine (Arabic VLM, needs GPU)
uv sync --extra all  # everything

Usage

Python

from qira_ocr import OCR

ocr = OCR()

# Basic — auto-detects engine, extracts text from PDF text layers directly
result = ocr.read("invoice.pdf")
print(result.to_text())

# Force a specific engine
result = ocr.read("arabic_note.jpg", engine="qari")  # best Arabic quality
result = ocr.read("arabic_note.jpg", engine="surya")  # lighter Arabic fallback

# Export as structured data
for page in result.pages:
    for block in page.blocks:
        print(block.text, block.bbox, block.confidence)

# Other export formats
result.to_dict()  # nested dict with bounding boxes + confidence scores
result.to_markdown()  # markdown with table formatting
result.to_html()  # html with table formatting

CLI

qira-ocr scan document.pdf
qira-ocr scan photo.jpg --engine surya --format markdown
qira-ocr scan ./documents/ --output ./results/ --format json

REST API

uvicorn qira_ocr.api:app
POST /ocr    file upload, optional engine/format params
GET  /health engine availability status

Engine Routing

When engine="auto" (default), qira-ocr inspects the document to pick the best engine:

ConditionEngineWhy
PDF with text layerDirect extractionNo OCR needed
Low Paddle confidence (non-Latin)QARI-OCRState-of-the-art Arabic, 0.061 CER
Low confidence, QARI not installedSuryaLighter Arabic/handwriting fallback
Everything elsePaddleOCRFastest, strong on printed English + tables

Override with engine="paddle", engine="surya", or engine="qari".

Running the surya engine

Surya (0.20+) runs OCR on a vision-language model served by an external inference server. On first use it starts the server itself and downloads the model (about 1.47 GB GGUF from datalab-to/surya-ocr-2-gguf). You need one of these:

  • CPU / Apple Silicon: the llama-server binary from llama.cpp (brew install llama.cpp, or a release build with LLAMA_CPP_BINARY=/path/to/llama-server)
  • NVIDIA GPU: vllm (Docker plus the NVIDIA Container Toolkit)

To use a server that is already running, set SURYA_INFERENCE_URL=http://host:port/v1. Without a server, the surya engine raises an error with install hints. The auto route falls back to surya when Paddle confidence is low and QARI isn't installed.

If surya fails on every block it attempted on a page (the backend is down or unreachable), recognize raises RuntimeError instead of returning an empty page. This is deliberate and aborts a multi-page document; partial failures only log a warning and the failed blocks' text is missing.

Surya bounding boxes are per layout block (a paragraph, table, header, and so on), not per text line: every line in a surya block shares that block's bbox and confidence.

Tests

uv sync --extra cli --extra api
uv run pytest -m "not requires_ocr_server"  # what CI runs: no model downloads, no server
uv run pytest                               # full suite: downloads Paddle/surya models, needs llama-server or vllm

License

MIT

arabic
document-processing
ocr
paddleocr
pdf
python
surya

0xFl4g/qira-ocr

Hybrid multi-engine OCR for Arabic and English documents

Python

0

15 commits

updated Oct 16, 2026

See the code

README

qira-ocr

A hybrid multi-engine OCR toolkit for Arabic and English documents. Routes between PaddleOCR (fast, strong on printed text and tables), QARI-OCR (state-of-the-art Arabic with diacritics), and Surya (Arabic and handwriting fallback) behind a single unified interface. Supports PDFs, images, and batch processing with output as plain text, JSON, Markdown, or HTML.

Built as a university project at the University of Sheffield.

Project Structure

src/qira_ocr/
  __init__.py          # Public OCR class — single entry point
  loader.py            # PDF text extraction, rasterization, image loading
  router.py            # Automatic engine selection via Arabic script detection
  result.py            # Structured result model (Page > Block > Line > Word)
  structure.py         # Table/layout analysis using PPStructure
  cli.py               # Click CLI (qira-ocr scan ...)
  api.py               # FastAPI REST API (/ocr, /health)
  engines/
    base.py            # OCREngine protocol
    paddle.py          # PaddleOCR wrapper
    qari.py            # QARI-OCR wrapper (Arabic VLM)
    surya.py           # Surya OCR wrapper
tests/                 # 73 tests covering all modules

Installation

Requires Python 3.12+. Install with uv:

uv sync              # core OCR library
uv sync --extra cli  # + command-line interface
uv sync --extra api  # + REST API server
uv sync --extra qari # + QARI-OCR engine (Arabic VLM, needs GPU)
uv sync --extra all  # everything

Usage

Python

from qira_ocr import OCR

ocr = OCR()

# Basic — auto-detects engine, extracts text from PDF text layers directly
result = ocr.read("invoice.pdf")
print(result.to_text())

# Force a specific engine
result = ocr.read("arabic_note.jpg", engine="qari")  # best Arabic quality
result = ocr.read("arabic_note.jpg", engine="surya")  # lighter Arabic fallback

# Export as structured data
for page in result.pages:
    for block in page.blocks:
        print(block.text, block.bbox, block.confidence)

# Other export formats
result.to_dict()  # nested dict with bounding boxes + confidence scores
result.to_markdown()  # markdown with table formatting
result.to_html()  # html with table formatting

CLI

qira-ocr scan document.pdf
qira-ocr scan photo.jpg --engine surya --format markdown
qira-ocr scan ./documents/ --output ./results/ --format json

REST API

uvicorn qira_ocr.api:app
POST /ocr    file upload, optional engine/format params
GET  /health engine availability status

Engine Routing

When engine="auto" (default), qira-ocr inspects the document to pick the best engine:

ConditionEngineWhy
PDF with text layerDirect extractionNo OCR needed
Low Paddle confidence (non-Latin)QARI-OCRState-of-the-art Arabic, 0.061 CER
Low confidence, QARI not installedSuryaLighter Arabic/handwriting fallback
Everything elsePaddleOCRFastest, strong on printed English + tables

Override with engine="paddle", engine="surya", or engine="qari".

Running the surya engine

Surya (0.20+) runs OCR on a vision-language model served by an external inference server. On first use it starts the server itself and downloads the model (about 1.47 GB GGUF from datalab-to/surya-ocr-2-gguf). You need one of these:

  • CPU / Apple Silicon: the llama-server binary from llama.cpp (brew install llama.cpp, or a release build with LLAMA_CPP_BINARY=/path/to/llama-server)
  • NVIDIA GPU: vllm (Docker plus the NVIDIA Container Toolkit)

To use a server that is already running, set SURYA_INFERENCE_URL=http://host:port/v1. Without a server, the surya engine raises an error with install hints. The auto route falls back to surya when Paddle confidence is low and QARI isn't installed.

If surya fails on every block it attempted on a page (the backend is down or unreachable), recognize raises RuntimeError instead of returning an empty page. This is deliberate and aborts a multi-page document; partial failures only log a warning and the failed blocks' text is missing.

Surya bounding boxes are per layout block (a paragraph, table, header, and so on), not per text line: every line in a surya block shares that block's bbox and confidence.

Tests

uv sync --extra cli --extra api
uv run pytest -m "not requires_ocr_server"  # what CI runs: no model downloads, no server
uv run pytest                               # full suite: downloads Paddle/surya models, needs llama-server or vllm

License

MIT

arabic
document-processing
ocr
paddleocr
pdf
python
surya