Arjuna-OCR [Kn&En] — inference package
v1.3.3: adds the row-structure fallback and mislabelled-panel re-typing; these are not re-benchmarked and the accuracy figures below are the v1.2.1 measurements.
What's new in v1.3.0 — layout v15
No recognised word is ever dropped from the page text. Measured on held-out pages of private documents:
every word the recogniser read also appears in doc["text"] — assemble.suppress_text_in is now empty, and
lines keep their in_seal / in_handwriting / in_figure flags so a consumer can filter without data loss.
The layout model gains six classes v1.2 could not express — section_header, list_item, caption,
footnote, formula and barcode. barcode matters most: without it a stamp panel could only be called
figure, and suppressing that figure removed real text. Three reading-order defects in the assembler are
also fixed (column ordering was silently disabled by any centred running header; the column-order routine was
unreachable; and a full-width block could absorb both columns of a two-column page).
Kannada + English document OCR that runs anywhere and configures itself. This repository is the inference / deployment
package (pip install arjuna-ocr):
one supported version (v1.2.1), the three models, the inference code, an HTTP server, a Hugging Face Inference Endpoints
handler and Docker files. Every release, the full evaluation record and the training story live in the archive repository
anandkaman/arjuna-ocr-kn-en.
pip install arjuna-ocr # CPU everywhere
arjuna doctor # what this machine can do, and what the pipeline will use
arjuna ocr page.pdf -o out/ # OCR files, folders, globs or PDFs
arjuna serve --port 8080 # HTTP server
Or without installing anything:
docker run --rm -p 8080:8080 arjuna-ocr:cpu # CPU
docker run --rm --gpus all -p 8080:8080 arjuna-ocr:cuda # NVIDIA
It configures itself
On start it detects the GPU, driver, CUDA and TensorRT, picks the fastest provider it can actually load, and scales batch sizes and thread counts to the machine's VRAM and cores. Only speed settings change — canvases, thresholds, padding and the models are exactly as shipped, so results do not depend on the hardware (beyond fp16 rounding).
TensorRT engines are specific to the GPU and TensorRT version, so they cannot be shipped for every machine. Instead of making you wait for a build, the pipeline starts serving immediately on the CUDA provider and builds the engines in the background; each engine is swapped in only after it reproduces the output of the session it replaces (for the layout model, the reference blocks on the bundled page). If a build fails or produces a wrong engine, the pipeline keeps running on what it had and says so. Cold start on an unseen GPU: ~1.5 s to first request, full speed a minute or two later.
arjuna doctor prints all of it: hardware, driver, usable providers, the plan, every auto-configuration change, and the
reason for any fallback.
What runs where
| provider | hardware | status |
|---|---|---|
| TensorRT (fp16) | NVIDIA + TensorRT | supported — 14.5 pages/s on an RTX 5060 Ti (18.1 with two processes) |
| CUDA | any NVIDIA with a working driver | supported — ~4.6 pages/s, and the path used while engines build |
| CPU | x86-64 / ARM64, no GPU needed | supported — seconds per page, scales with cores |
| DirectML, CoreML, OpenVINO | Windows, Apple Silicon, Intel | planned; the graphs are ordinary CNNs, so these are provider swaps |
| ROCm | AMD | best-effort only (ONNX Runtime support ends after ROCm 7.0) |
Not supported, by design: vLLM, Ollama / llama.cpp, TGI and the Hugging Face serverless Inference API or Inference Providers catalogue. These serve autoregressive language models; Arjuna is three CNN/CTC vision models with their own pre- and post-processing. Use the Docker image, the HTTP server, Inference Endpoints or Triton instead.
NVIDIA
pip install arjuna-ocr installs the CPU runtime, which works everywhere. For GPU speed, replace the runtime with a
build that matches your CUDA — onnxruntime and onnxruntime-gpu share an import name, so only one may be installed:
# CUDA 13 (current default wheels)
pip uninstall -y onnxruntime && pip install "arjuna-ocr[trt]"
# CUDA 12
pip uninstall -y onnxruntime onnxruntime-gpu
pip install onnxruntime-gpu --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
pip install tensorrt-cu12 nvidia-cublas-cu12 nvidia-cudnn-cu12 nvidia-cuda-runtime-cu12
# or skip all of it
docker run --rm --gpus all -p 8080:8080 arjuna-ocr:cuda
arjuna doctor tells you which providers actually load on your machine and why any of them was rejected. If it reports
CPU while you expect GPU, the CUDA libraries the runtime wants are missing or belong to a different CUDA major version.
Performance
14.5 pages/s on one RTX 5060 Ti (150-DPI pages, TensorRT fp16), ~4.6 on the CUDA provider, seconds per page on CPU.
A single process leaves the GPU at 44 % mean utilisation and 3.1 GB, so two processes on one card reach 18.1 pages/s
aggregate. Measurements, the profile of every stage and what does not help: docs/PERFORMANCE.md.
Hosted inference
Hugging Face Inference Endpoints — this repo ships handler.py, so Deploy → Inference Endpoints works directly.
Pick a CPU or GPU instance; engines build in the background on GPU instances.
import base64, requests
img = base64.b64encode(open("page.png", "rb").read()).decode()
r = requests.post(URL, headers={"Authorization": f"Bearer {TOKEN}"},
json={"inputs": img, "parameters": {"format": "json"}})
doc = r.json()["documents"][0]
Anywhere that runs a container — Modal, RunPod, Replicate, fal, Kubernetes, your own server: use the Docker images. Multi-model serving — the three ONNX graphs drop into NVIDIA Triton with the pre/post-processing as a Python backend.
Python
from kanen_infer import KanEnOCR
ocr = KanEnOCR() # auto-detects hardware
ocr.optimize() # optional: build TensorRT engines in the background
doc = ocr.page("page.png") # kanen-ocr/1.0 JSON
md = ocr.render(doc, "md") # json | md | txt | hocr | alto | tsv
docs = ocr.pages(["a.png", "b.pdf"]) # batched
Every line carries text, confidence, min_conf (weakest character), bbox, script and review flags
(low_conf, check, weak_char, script_mix, oversize, in_seal, in_figure, in_id_panel, in_handwriting).
Blocks are text, title, table, figure, seal, id_panel, handwriting, header, footer; tables carry
rows[].cells[]. Nothing is filtered or rewritten — low-confidence lines are flagged, never dropped.
Accuracy (v1.2.1)
| set | measure | result |
|---|---|---|
| private documents, 40 frozen pages | word F1 end-to-end / page CER | 0.967 / 3.11 % |
| private document lines (1,324) | word accuracy / CER | 98.8 % / 0.17 |
| Mozhi test (IIIT-H, public) | word accuracy | 96.3 % |
| MILE (IISc, public) | word accuracy | 93.7 % |
| English PDF text | word accuracy | 97.2 % |
| vs Surya OCR 1, clean pages / unseen MILE pages | word F1 | 0.991 / 0.872 vs 0.929 / 0.751 |
Method, per-benchmark analysis and the full history: see the archive repo's docs/.
Compared with Surya OCR 1 (2026-09-26)
One complete sale deed (12 pages) and ten public pages neither system had seen, on the same GPU:
| sale deed | public pages | VRAM | |
|---|---|---|---|
| Arjuna v1.2.1 | 11.7 pages/s | 7.7 pages/s | 3.1 GB |
| Surya OCR 1 | 0.77 pages/s | 0.35 pages/s | 8.9 GB |
On the deed pages that carry verified labels: word F1 0.994 vs 0.890, page CER 0.1 % vs 3.1 %. Across all 22
pages Arjuna emitted no markup, no empty lines and no characters from scripts that are not on the page, where Surya
emitted 101 tags, 8 empty lines and 154 stray Devanagari/CJK characters. Method, per-page numbers and the side-by-side
text: docs/COMPARISON_SURYA1_2026_09.md. Surya covers 90+ languages; this
comparison is only about printed Kannada/English documents.
What is planned
The layout model is next (RT-DETR trained on a much broader page set), then recognizer int8 for speed, then more
languages — each as a dual-language pair with English, the way Kannada+English works, since the detector and layout
models are script-agnostic and only the recognizer is language-specific (Hindi+English is next). Order, scope and the
gate each item must pass: docs/ROADMAP.md; the multi-language design:
docs/MULTILINGUAL.md.
Known limitations
- Very small or fax-quality English text (≤ 11 px x-height) and thermal/dot-matrix receipts lose accuracy.
- Historical Kannada is strong but below modern print; line splitting on letter-spaced or letterpress faces is much reduced in v1.2.1 but not gone.
- Dense multi-column tables (10+ columns) and unruled form boxes come out as text lines without cell structure.
- Handwriting, stamps and seals produce low-confidence flagged output rather than nothing.
- The layout model was trained on 174 pages of the target document families; other families are typed less reliably.
Licences
Package code, recognizer and text detector: Apache-2.0. The layout model models/layout/kanen_layout_v12.onnx is
AGPL-3.0 — see models/layout/NOTICE.md. Run with layout.mode: never if that does not suit your deployment.
Contact: kamananand98@gmail.com
Known issue — layout may silently drop printed text (open, 2026-09-28)
The shared layout model (kanen_layout_v12) has handwriting precision 0.743 on its own validation set:
about one handwriting box in four is wrong. Where a caller suppresses handwriting text, a false positive
deletes printed text with no trace in the output.
Confirmed on Hindi registration pages: a printed word was detected correctly (score 0.90), classified
handwriting at confidence 0.42, and then withheld. The same mechanism truncates printed labels that a
signature crosses.
This affects both language pairs, because the layout model is shared. Plan and acceptance criteria:
docs/LAYOUT_V13_PLAN.md — retrain on Kannada + Hindi pages together, gate on handwriting precision >= 0.85,
and give the suppression policy a confidence floor so a marginal call keeps the text instead of deleting it.