anaksor-ocr

Documentation

Install anaksor-ocr, read photos, scans and PDFs, and export what it reads.

On this page
  1. Install
  2. Read a page
  3. What comes back
  4. PDFs and exports
  5. Options
  6. Command line
  7. Accuracy and limitations
  8. Weights
  9. Your own models
  10. Security
  11. Working on anaksor

Install

# images, on ONNX Runtime (no PyTorch)
pip install anaksor-ocr
# adds the PyTorch backend: CUDA, Apple MPS, the SMTR head
pip install "anaksor-ocr[torch]"
# PyTorch, PDF input and Word export
pip install "anaksor-ocr[all]"

Needs Python 3.11 or newer. The model weights, about 95 MB, download to ~/.cache/anaksor the first time you read a page.

ONNX Runtime (default)PyTorch ([torch])
InstallNo PyTorchPyTorch (several GB with CUDA)
DevicesCPU; CUDA with pip install onnxruntime-gpuCPU, CUDA, Apple MPS
Reading headsCTC (the default), CPPDCTC, CPPD, and SMTR (decoder="gtc" or "auto")

Both read pages identically. anaksor uses PyTorch when it is installed and ONNX Runtime otherwise; choose with backend="torch" or "onnx", or --backend. On a CPU, PyTorch is a little faster.

Read a page

Python
import anaksor

# uses a GPU when there is one, otherwise the CPU
ocr = anaksor.OCR()
result = ocr("letter.jpg")

# the whole page, in reading order
print(result["text"])
for line in result["lines"]:
    print(round(line["rec_score"], 2), line["text"])
Command line
# text to the terminal
anaksor letter.jpg
# Word, with the layout rebuilt
anaksor letter.jpg -o letter.docx
# searchable PDF
anaksor contract.pdf -o contract-searchable.pdf

ocr(...) takes a file path, bytes, a PIL image or an RGB NumPy array. Images are read as JPEG, PNG, WEBP, BMP or TIFF, and phone photos are turned upright from their EXIF orientation.

What comes back

A result is plain JSON-friendly data: the page text, its paragraphs (blocks), and every line with its position and confidence. This example is shortened.

{
  "text": "ព្រះរាជាណាចក្រកម្ពុជា\nជាតិ សាសនា ព្រះមហាក្សត្រ\n\n...",
  "blocks": [{"text": ..., "lines": [0, 1], "box": [[412, 96], ...], "rec_score": 0.981}],
  "lines": [
    {"text": "ជាតិ សាសនា ព្រះមហាក្សត្រ",
     "parts": [...], "box": [...], "quads": [...],
     "rec_score": 0.987, "det_score": 0.93, "block": 0}
  ],
  "width": 1654, "height": 2339
}
text
Lines joined with line breaks, paragraphs with a blank line.
blocks
Paragraphs in reading order, with their box and the indices of their lines.
lines
Each line: text, its box and the detected quads it was built from, the text of each quad in parts, and the recognition and detection confidence.
width, height
The image size the coordinates refer to (after EXIF rotation).

Reading order handles text columns, receipts (a price stays on its item's line), skewed phone photos and tightly spaced Khmer lines whose stacked vowels and subscripts make neighbouring boxes overlap. Lines longer than the recogniser's input are read in overlapping windows and joined at whole Khmer clusters.

PDFs and exports

# one result per page; images are kept for the PDF export
pages = ocr.read("report.pdf", pages=[1, 2, 3], keep_images=True)

from anaksor import export
open("report-searchable.pdf", "wb").write(export.to_pdf(pages))
open("report.docx", "wb").write(export.to_docx(pages, highlight_low=True))
  • Searchable PDF keeps each page exactly as scanned and lays the recognised text over it invisibly, so it can be searched, selected and copied.
  • Word rebuilds the layout from the geometry: headings, centred and right-aligned blocks, indents, prose reflowed into paragraphs, and receipt prices aligned on a right tab stop. highlight_low marks lines read with low confidence.

PDF pages are always rendered and read as images, never taken from the PDF's own text layer. Khmer PDFs often store text in legacy font encodings or in glyph order, so their text layer is unreliable even when it is there.

Options

When creating anaksor.OCR(...)

OptionDefaultWhat it does
backend"auto""torch" or "onnx"; auto means PyTorch if it is installed
device"auto""cuda", "cuda:1", "mps" (PyTorch only) or "cpu"
pad_y, pad_x0.16, 0.10Margin added around each line before it is read, as a fraction of the line height. Too little cuts off subscripts.
min_rec_score0.5Lines read with less confidence are left out
try_vertical, try_180True, FalseAlso read tall boxes turned 90 degrees; also read every box upside down

For one page: ocr(image, ...)

OptionDefaultWhat it does
unclip_ratio1.5How far detected boxes are grown; larger values join more text into one box
box_thresh, thresh0.5, 0.3Detection confidence: per box, and per pixel
scales(1.0,)Extra detection passes, such as (1.0, 1.6) for small print; slower

The two models also work on their own: anaksor.Detector().detect(image) returns line boxes, and anaksor.Recognizer().recognize([crop, ...]) reads cropped lines.

Command line

anaksor scan.pdf --pages 1-3,7 -o scan.docx
# one JSON file per image
anaksor photos/*.jpg -o out/ -f json
# download the weights ahead of time
anaksor --fetch-models
OptionWhat it does
-o, --outputOutput file, or a folder for several inputs. Without it, text and JSON go to the terminal.
-f, --formattext, json, pdf or docx; by default from the output file's extension
--pages, --dpiPDF pages to read, such as 1-3,7; the resolution they are rendered at (200)
--backend, --deviceauto, torch, onnx; auto, cpu, cuda, mps
--pad-y, --pad-x, --min-scoreThe options of the same name above
--det-weights, --rec-weightsUse your own weight files
--highlight-lowWord output: highlight low-confidence lines

Accuracy and limitations

PartMeasured onResult
Detection v3KhmerST validation photosprecision 0.75, recall 0.79
Detection v3HierText validation: English on photosprecision 0.76, recall 0.69
Detection v3synthetic pagesprecision 0.998, recall 0.998
Recognition v43,878 real validation lines: Khmer, Latin and mixed86.7% exactly right, 1.38% character error
Recognition v4the 2,497 Khmer-only lines among them90.1% exactly right, 0.73% character error

KhmerST labels only some of the text in each photo, so for detection, unlabelled text found by another detector counts as "don't care"; that flatters precision a little.

  • Printed Khmer, documents, receipts and signs read best.
  • English and other Latin text on photos is weaker: the detector can miss it on signs, and the recogniser has seen little real English.
  • Handwriting and decorative lettering are weak. Check each line's rec_score, or use --highlight-low for Word output.

Weights

Each model comes as an ONNX graph (.onnx, for ONNX Runtime) and as PyTorch weights (.safetensors); only the files your backend needs are downloaded, from the first of:

  1. the path you passed (det_weights=, rec_weights=, or --det-weights);
  2. a file in the folder named by ANAKSOR_WEIGHTS_DIR;
  3. the cache in ~/.cache/anaksor (or ANAKSOR_HOME), downloaded once from Hugging Face and checked against its SHA-256.

Neither format can run code when it is loaded. On a machine without internet access, run anaksor --fetch-models elsewhere and copy the two files for your backend into ANAKSOR_WEIGHTS_DIR.

Your own models

Models trained with OpenOCR-style configs (the format these models were trained with) run with a spec made from the training config. The tools are in the repository, not the installed package, so run them from a clone:

git clone https://github.com/SokhengDin/anaksor-ocr && cd anaksor-ocr
python tools/spec_from_config.py path/to/svtrv2_cppd_khmer.yml --out specs/
# for the onnx backend (needs [export])
python tools/export_onnx.py --rec best.pth --rec-spec specs/rec-svtrv2-cppd-khmer.yml --out onnx/
Python
rec = anaksor.Recognizer("specs/rec-svtrv2-cppd-khmer.yml", weights="best.pth")
ocr = anaksor.OCR(recognizer=rec)

Supported: detection RepViT + DBNet; recognition SVTRv2 (any size) with RCTC + SMTR heads, RCTC alone, or CPPD. .pth checkpoints load as they are, without running code.

Security

  • Weights never run code. ONNX graphs are run by ONNX Runtime; PyTorch weights are read with safetensors, or with torch.load(weights_only=True), and anything that needs more is refused.
  • Untrusted images reach few decoders. Only JPEG, PNG, WEBP, BMP and TIFF are opened, and Pillow's limit on oversized images stays on.
  • Dependencies are audited. Even the oldest versions the package allows have no known vulnerabilities.

Report a vulnerability privately through GitHub security advisories.

Working on anaksor

git clone https://github.com/SokhengDin/anaksor-ocr && cd anaksor-ocr
uv venv && uv pip install torch --index-url https://download.pytorch.org/whl/cpu
uv pip install -e ".[all]" pytest ruff pip-audit onnx onnxscript
# no weights or network needed
pytest

See the changelog and the website's source.