On this page
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]) | |
|---|---|---|
| Install | No PyTorch | PyTorch (several GB with CUDA) |
| Devices | CPU; CUDA with pip install onnxruntime-gpu | CPU, CUDA, Apple MPS |
| Reading heads | CTC (the default), CPPD | CTC, 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
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"])# 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.pdfocr(...) 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, itsboxand the detectedquadsit was built from, the text of each quad inparts, 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_lowmarks 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(...)
| Option | Default | What 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_x | 0.16, 0.10 | Margin added around each line before it is read, as a fraction of the line height. Too little cuts off subscripts. |
min_rec_score | 0.5 | Lines read with less confidence are left out |
try_vertical, try_180 | True, False | Also read tall boxes turned 90 degrees; also read every box upside down |
For one page: ocr(image, ...)
| Option | Default | What it does |
|---|---|---|
unclip_ratio | 1.5 | How far detected boxes are grown; larger values join more text into one box |
box_thresh, thresh | 0.5, 0.3 | Detection 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| Option | What it does |
|---|---|
-o, --output | Output file, or a folder for several inputs. Without it, text and JSON go to the terminal. |
-f, --format | text, json, pdf or docx; by default from the output file's extension |
--pages, --dpi | PDF pages to read, such as 1-3,7; the resolution they are rendered at (200) |
--backend, --device | auto, torch, onnx; auto, cpu, cuda, mps |
--pad-y, --pad-x, --min-score | The options of the same name above |
--det-weights, --rec-weights | Use your own weight files |
--highlight-low | Word output: highlight low-confidence lines |
Accuracy and limitations
| Part | Measured on | Result |
|---|---|---|
| Detection v3 | KhmerST validation photos | precision 0.75, recall 0.79 |
| Detection v3 | HierText validation: English on photos | precision 0.76, recall 0.69 |
| Detection v3 | synthetic pages | precision 0.998, recall 0.998 |
| Recognition v4 | 3,878 real validation lines: Khmer, Latin and mixed | 86.7% exactly right, 1.38% character error |
| Recognition v4 | the 2,497 Khmer-only lines among them | 90.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-lowfor 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:
- the path you passed (
det_weights=,rec_weights=, or--det-weights); - a file in the folder named by
ANAKSOR_WEIGHTS_DIR; - the cache in
~/.cache/anaksor(orANAKSOR_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/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
pytestSee the changelog and the website's source.