---
license: cc-by-nc-sa-4.0
language:
- uz
- ru
- en
pipeline_tag: image-to-text
library_name: onnx
tags:
- ocr
- document-ai
- uzbek
- cyrillic
- ctc
- onnx
---

# Xattot v1
**Xattot** turns images of printed documents (book scans, articles, theses, phone photos of pages) into Markdown text.
It reads **Uzbek (Latin and Cyrillic script), Russian and English**, runs on a **CPU** with `onnxruntime` only, and is
the first model released by Steppe Labs.
At its core is **Satr**, a small text-line recognizer (3.25M parameters) that Steppe Labs designed and trained from
scratch. Satr is aimed at Uzbek Cyrillic text, where the Cyrillic letters **ҳ қ ғ ў** are often misread by
general OCR systems and vision-language models.
*Xattot* (Uzbek: "calligrapher, scribe"), the person who copied books by hand. *Satr* means "line".

**In our independent benchmark (details below):**
- On **Uzbek Cyrillic** book scans Xattot has the lowest error of the six systems tested: CER **0.61%** against
1.75% for the next best (Qianfan-OCR). The difference is statistically significant.
- It reads the letters **ҳ қ ғ ў** correctly **98.6%** of the time on book scans and **99.7%** on printed pages. The
vision-language models tested reach 49–73%, and Indra 33–34%.
- On **printed pages (scanner + phone camera)** Xattot has the lowest error overall: **0.62%** against 1.35% for
PaddleOCR-VL-1.6. The difference is significant.
- On **Uzbek Latin, Russian and English** book scans the difference to the best system is **not significant**. On
Russian and English, PaddleOCR-VL-1.6 is numerically ahead (0.59% vs 1.16% and 0.16% vs 0.49%).
- It runs on a CPU: about 1 s per text block and 7 s per page with 2 threads, while the vision-language models take
58–165 s per page on an NVIDIA T4 GPU.
## Quick start
```bash
git clone https://huggingface.co/steppelabs/xattot && cd xattot
pip install -r requirements.txt
python xattot.py assets/sample_page.png --out result --draw # result/sample_page.md, result/sample_page_boxes.jpg
```
```python
from xattot import load_xattot, read_page
x = load_xattot() # first call downloads the Apache-2.0 layout/line models (~150 MB)
print(read_page(x, "page.jpg")) # Markdown
```
Images are EXIF-rotated and scaled so that the longest side is at most 2,500 px, the same preparation as in the
benchmark. `--gpu` uses `onnxruntime-gpu`.
## How it works
| step | model | source |
|---|---|---|
| page orientation (0/90/180/270°) | PP-LCNet_x1_0_doc_ori | PaddlePaddle, Apache-2.0 |
| layout blocks and reading order | PP-DocLayoutV3 | PaddlePaddle, Apache-2.0 |
| text lines in each block | PP-OCRv5 mobile det | PaddlePaddle, Apache-2.0 |
| **line image → text** | **Satr v1** | **Steppe Labs, this repository** |
| table structure → Markdown table (cells read by Satr) | SLANet_plus | PaddlePaddle, Apache-2.0 |
| formula → LaTeX (optional, `--formula`, experimental) | PP-FormulaNet_plus-L | PaddlePaddle, Apache-2.0 |
PaddlePaddle is Baidu's open-source deep-learning platform. Xattot uses its released models in ONNX form, so the
Paddle framework itself is not needed. These models are not included here: `xattot.py` downloads them from the
official PaddlePaddle repositories at pinned revisions (see [NOTICE](https://huggingface.co/steppelabs/xattot/blob/main/NOTICE)). The pinned files are byte-identical (SHA-256) to the copies Xattot was developed and benchmarked with.
**Satr v1:** a CNN (line height 48 px → 1, width / 4) followed by 2 Transformer blocks (d=192, 4 heads) and a CTC head.
It has 263 output characters: Latin, Cyrillic including ҳ қ ғ ў, Greek, digits, and sub/superscript and technical
symbols. Two formats are included:
- `satr/satr.onnx` for inference;
- `satr/satr.safetensors` with `satr_model.py` for fine-tuning in PyTorch. Both give the same output.
## Examples
Real Xattot output on pages from the benchmark. The boxes are drawn from Xattot's own output with no edits. The CER
under each image is that of the output shown.



The examples also show mistakes. On the first page the two-line title is split into three blocks, so the word "ва"
lands after the second title line. On the second page a speck in the right margin is read as `%`.
## Evaluation
The full protocol was written down **before** any system was run, including the decision rule below. All systems got
the same images.
**Systems:**
| system | version | hardware |
|---|---|---|
| Xattot v1 | this repository | CPU, 2 threads |
| PaddleOCR-VL-1.6 | `PaddlePaddle/PaddleOCR-VL-1.6` @ c5630ab, prompt `OCR:` per layout block, fp16 | T4 GPU |
| Qianfan-OCR | `baidu/Qianfan-OCR` @ 623bf5d, `Parse this document to Markdown.`, fp16 | T4 GPU |
| Unlimited-OCR | `baidu/Unlimited-OCR` (v1) @ 8249d20, its own document prompt, bf16 | T4 GPU |
| Tesseract | 5.3.4, given the correct language (`uzb`, `uzb_cyrl`, `rus`, `eng`) | CPU |
| Indra | 1.1.2, default settings | CPU, 2 threads |
No paid APIs were tested.
**Test sets:**
- **Book scans: 99 text blocks.** 104 blocks of 3–12 lines (586 lines in total) were drawn at random, with a fixed
seed, one from each of 104 documents that were not used in training. They come from four groups: Uzbek Latin,
Uzbek Cyrillic, Russian and English.
- **Reference text:** a Steppe Labs annotator corrected each block against the image. The starting draft for each
block came from one of the six systems, assigned at random and hidden from the annotator.
- **Scoring:** each system is scored only on blocks it did not draft. Pairwise differences use blocks drafted by
neither system.
- On about 8 blocks an AI assistant pointed out possible errors; the annotator decided the final text from the image.
- **5 blocks were excluded**, leaving 99: 3 contained formulas, and in 2 a line was cut in half by the block border.
- **Printed pages: 10 pages** that we typeset in fonts not used to train Satr: PT Serif/Sans/Mono, Linux Libertine,
IBM Plex, Carlito and Computer Modern.
- Layouts: one and two columns, typewriter style, and a 9 pt page.
- Languages: Uzbek Latin 3, Uzbek Cyrillic 4, Russian 2, English 1.
- Each page was printed, scanned at 300 dpi and photographed with a phone, giving **20 images**.
- The reference is the typeset text.
**Metric and normalization:**
- Metric: character error rate (CER), micro-averaged, with 95% confidence intervals from a cluster bootstrap
(10,000 resamples, clustered by block or by page).
- The same normalization is applied to every system before scoring:
- Unicode NFC;
- one apostrophe, dash and quote form;
- markup, tags and coordinates removed;
- hyphens between letters dropped;
- sub/superscripts flattened;
- whitespace collapsed.
- Upper/lower case and Latin–Cyrillic look-alikes count as errors.
**Decision rule:** "Xattot is best" is claimed only when the 95% interval of *Xattot − best other system* lies entirely
below zero. If the interval lies above zero, Xattot is worse and this is listed as a limitation. Groups with fewer than
3 pages get no verdict.
### Book scans (CER %, 95% CI)
| group | blocks | **Xattot** | PaddleOCR-VL-1.6 | Qianfan-OCR | Unlimited-OCR | Tesseract 5 | Indra | Xattot − best other | verdict |
|---|--:|--:|--:|--:|--:|--:|--:|---|---|
| Uzbek Cyrillic | 26 | **0.61** [0.3–1.1] | 3.27 [2.3–4.8] | 1.75 [1.1–2.6] | 8.37 [2.8–17.2] | 3.61 [0.4–10.2] | 3.32 [2.8–3.8] | −1.06 [−1.9…−0.2] (Qianfan) | **Xattot best** |
| Uzbek Latin | 26 | 0.42 [0.2–0.7] | 0.67 [0.3–1.2] | 0.64 [0.2–1.4] | **0.37** [0.2–0.6] | 1.15 [0.6–1.8] | 1.04 [0.2–2.1] | +0.11 [−0.2…+0.5] (Unlimited) | not significant |
| Russian | 23 | 1.16 [0.6–1.8] | **0.59** [0.2–1.1] | 0.86 [0.1–2.2] | 0.81 [0.2–1.6] | 2.19 [1.0–3.6] | 1.50 [0.6–2.8] | +0.34 [−0.1…+0.9] (PaddleOCR-VL) | not significant |
| English | 24 | 0.49 [0.0–1.2] | **0.16** [0.0–0.3] | 0.23 [0.1–0.4] | 0.21 [0.1–0.4] | 1.36 [0.3–3.3] | 0.64 [0.0–2.1] | +0.47 [−0.1…+1.5] (PaddleOCR-VL) | not significant |
| All | 99 | **0.64** [0.4–0.9] | 1.11 [0.8–1.6] | 0.86 [0.6–1.3] | 2.62 [1.0–5.0] | 2.07 [0.9–3.9] | 1.58 [1.1–2.1] | −0.20 [−0.7…+0.3] (Qianfan) | not significant |
### Printed pages (CER %, 95% CI; bootstrap by page)
| group | images | **Xattot** | PaddleOCR-VL-1.6 | Qianfan-OCR | Unlimited-OCR | Tesseract 5 | Indra | Xattot − best other | verdict |
|---|--:|--:|--:|--:|--:|--:|--:|---|---|
| scanner, Uzbek Latin | 3 | 0.11 [0.1–0.1] | 0.27 [0.2–0.3] | 0.46 [0.1–0.7] | 0.45 [0.1–0.6] | 0.24 [0.1–0.5] | **0.01** [0.0–0.0] | +0.10 [+0.1…+0.1] (Indra) | **Xattot worse** |
| scanner, Uzbek Cyrillic | 4 | **1.10** [0.4–1.4] | 2.56 [1.9–3.2] | 26.07 [3.9–63.6] | 15.99 [2.9–36.4] | 1.22 [0.3–2.1] | 26.73 [3.5–67.4] | −0.13 [−1.0…+0.4] (Tesseract) | not significant |
| camera, Uzbek Latin | 3 | **0.02** [0.0–0.1] | 0.27 [0.2–0.3] | 0.19 [0.1–0.2] | 0.35 [0.0–0.5] | 22.29 [10.8–38.0] | 0.20 [0.0–0.8] | −0.16 [−0.2…−0.0] (Qianfan) | **Xattot best** |
| camera, Uzbek Cyrillic | 4 | **0.31** [0.0–0.6] | 2.21 [1.4–3.4] | 3.20 [2.3–4.2] | 18.40 [2.8–45.9] | 6.69 [4.4–8.9] | 3.56 [3.2–4.4] | −1.90 [−2.8…−1.2] (PaddleOCR-VL) | **Xattot best** |
| scanner, all | 10 | **0.79** [0.3–1.2] | 1.48 [0.7–2.3] | 10.77 [0.7–29.5] | 6.63 [0.5–17.2] | 0.87 [0.3–1.5] | 10.97 [0.5–30.6] | −0.08 [−0.6…+0.4] (Tesseract) | not significant |
| camera, all | 10 | **0.45** [0.1–0.8] | 1.22 [0.5–2.1] | 1.37 [0.4–2.5] | 7.55 [0.4–21.5] | 11.81 [7.4–17.2] | 6.53 [0.9–17.0] | −0.77 [−1.8…+0.2] (PaddleOCR-VL) | not significant |
| all | 20 | **0.62** [0.3–0.9] | 1.35 [0.6–2.2] | 6.07 [0.5–15.8] | 7.09 [0.5–19.2] | 6.34 [4.2–8.9] | 8.75 [0.9–18.9] | −0.73 [−1.4…−0.1] (PaddleOCR-VL) | **Xattot best** |
Russian (2 pages) and English (1 page) have too few pages for a verdict:
| group | Xattot | PaddleOCR-VL-1.6 | Qianfan-OCR | Unlimited-OCR | Tesseract 5 | Indra |
|---|--:|--:|--:|--:|--:|--:|
| scanner, Russian | 1.54 | 1.87 | 0.52 | 0.23 | 1.58 | 0.80 |
| camera, Russian | 0.66 | 1.28 | 0.12 | 0.03 | 6.64 | 25.54 |
| scanner, English | 0.14 | 0.00 | 0.00 | 0.00 | 0.03 | 0.00 |
| camera, English | 1.51 | 0.03 | 0.00 | 0.03 | 13.50 | 0.14 |
The other systems' large errors on printed pages come mostly from a few failed pages. Indra mixes up the column order
on two-column pages. Qianfan-OCR and Unlimited-OCR repeat or drop text on one two-column page. Tesseract misses parts
of the photographed pages.
### Cyrillic letters ҳ қ ғ ў
This is the share of these letters in the reference text that each system read correctly, measured on Uzbek Cyrillic
text only.
| set | **Xattot** | PaddleOCR-VL-1.6 | Qianfan-OCR | Unlimited-OCR | Tesseract 5 | Indra |
|---|--:|--:|--:|--:|--:|--:|
| book scans (26 blocks) | **98.6%** | 57.0% | 68.8% | 48.9% | 95.2% | 32.8% |
| printed pages (8 images) | **99.7%** | 73.1% | 52.9% | 51.9% | 95.5% | 33.8% |
### Speed (mean seconds; for information, not part of the decision)
| | Xattot | PaddleOCR-VL-1.6 | Qianfan-OCR | Unlimited-OCR | Tesseract 5 | Indra |
|---|--:|--:|--:|--:|--:|--:|
| hardware | CPU, 2 threads | T4 GPU | T4 GPU | T4 GPU | CPU | CPU, 2 threads |
| per text block | 0.97 | 8.97 | 13.45 | 6.24 | 1.05 | 0.29 |
| per page | 7.1 | 103 | 165 | 58 | 7.5 | 1.3 |
**Notes on the run:**
- **PaddleOCR-VL on printed pages.** On two printed pages our harness gave PaddleOCR-VL-1.6 overlapping layout boxes,
so it read the same text twice. That harness bug was ours, not the model's. We fixed it (a box is dropped when 90% of
its ink lies inside smaller boxes) and re-ran PaddleOCR-VL on the printed pages. The tables show that second run;
only those two pages changed.
- **Small differences across machines.** Xattot's output can differ slightly between machines because onnxruntime uses
different floating-point kernels on different CPUs. The third example page measures 0.26% CER here and 0.24% in the
benchmark run.
## Limitations
- **Printed text only.** Handwriting is not supported, and neither is Arabic script (old Uzbek, Chagatai and Ottoman
texts).
- **Uzbek Latin apostrophe letters** are written with the ASCII apostrophe (`o'`, `g'`), not `ʻ`.
- **Russian and English:** large vision-language models are numerically better on our book scans. The difference is
not significant.
- **Scanned Uzbek Latin pages:** Indra is better (0.01% vs 0.11% CER).
- **Reading order** can break on multi-line headings and complex layouts; see the first example.
- **Formulas and tables:**
- Formula blocks were excluded from the benchmark.
- The `--formula` module is experimental. On a small internal check, 60% of formulas were fully correct.
- Table recognition was not part of this benchmark.
- **Benchmark size:** 99 blocks and 20 images. The confidence intervals are wide for small groups.
## Training
Satr was trained from scratch by Steppe Labs on printed text lines in Uzbek (Latin and Cyrillic), Russian and English:
about 443K real line images, plus synthetic lines rendered on the fly that make up 35% of training samples. The training data sources are
not disclosed. No benchmark document was used in training. The layout, line, orientation, table and formula models are
PaddlePaddle's released models, used unchanged.
## License
- **Satr weights, Xattot code and figures:** [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
(full text in [LICENSE](https://huggingface.co/steppelabs/xattot/blob/main/LICENSE)).
- Allowed: non-commercial use, research and fine-tuning.
- Fine-tuned weights may be shared under the same license, with credit to Xattot and Steppe Labs.
- Commercial use needs permission from Steppe Labs: write to **steppelabsai@gmail.com**.
Questions and bug reports: the Community tab of this repository.
- **PaddlePaddle models** that Xattot downloads keep their own license, Apache-2.0 (see [NOTICE](https://huggingface.co/steppelabs/xattot/blob/main/NOTICE)).
- **Your output:** Steppe Labs claims no rights over the text you extract with Xattot.
## Citation
```bibtex
@misc{steppelabs2026xattot,
title = {Xattot: CPU document OCR for Uzbek (Latin and Cyrillic), Russian and English},
author = {{Steppe Labs}},
year = {2026},
howpublished = {\url{https://huggingface.co/steppelabs/xattot}}
}
```
## Oʻzbekcha qisqacha
**Xattot** — bosma hujjat rasmidan (kitob skani, maqola, dissertatsiya, telefonda olingan varaq) Markdown matn oladigan
OCR tizimi. U oʻzbek (lotin va kirill), rus va ingliz tillarini oʻqiydi va oddiy kompyuter protsessorida (CPU) ishlaydi.
Ichidagi **Satr** qator modelini Steppe Labs noldan oʻqitgan.
Mustaqil sinovimizda:
- oʻzbek kirill kitob skanlarida xato **0.61%**. Bu sinalgan 6 tizimning eng yaxshisi, farq statistik jihatdan
ishonchli;
- **ҳ қ ғ ў** harflarining **98.6–99.7%** ini toʻgʻri oʻqiydi;
- oʻzbek lotin, rus va ingliz matnida eng yaxshi tizimlar bilan farq aniqlanmadi.
Litsenziya CC BY-NC-SA 4.0: notijoriy foydalanish va fine-tune mumkin. Tijoriy foydalanish uchun:
**steppelabsai@gmail.com**.