Surya OCR, tested on a real scanned PDF
1,926
of 1,955 scan words, read in order
pip install alone stops on scans
surya-ocr 0.23.0 stopped on page 1:
SpawnError, llama-server binary not found. Surya reads
each page with a vision model in llama.cpp.
I got the official llama.cpp build for Mac with llama.sh:
curl -sL https://github.com/ggml-org/llama.cpp/releases/download/b12316/llama-b12316-bin-macos-arm64.tar.gz | tar xz
Then surya.sh runs it. Run sh surya.sh next to book.pdf:
export LLAMA_CPP_BINARY="$PWD/llama-b12316/llama-server"
surya_ocr book.pdf --output_dir out "$@"
python3 totext.py out/book/results.json > book.txt
1,926 of 1,955 words, in order
56 s for 10 pages, Mac GPU
The first run downloads a 1.4 GB model.
JSON out: totext.py makes plain text
surya_ocr writes results.json. Each block holds its text as HTML. totext.py prints the blocks in reading order:
import html
import json
import re
import sys
from pathlib import Path
for pages in json.loads(Path(sys.argv[1]).read_text()).values():
for page in pages:
for block in page["blocks"]:
print(html.unescape(re.sub(r"<[^>]+>", " ", block["html"])).strip())
Surya reads every page as a picture, typed pages too. In the 11-page file, it read the typed cover page right: 13 of 13 words.
Faster: DPI 96 and the smaller model
Surya's README says to try DPI 96 for speed. On this scan, it lost no words.
| Setting | Words | Time |
|---|---|---|
| Default | 1,926 | 56 s |
| IMAGE_DPI_HIGHRES=96 | 1,928 | 37 s |
| Q8_0 model | 1,926 | 50 s |
The Q8_0 file is 0.6 GB, in the same model repo. Set
SURYA_GGUF_MODEL_FILE to
surya-2.5-Q8_0.gguf. It gave the same text.
German worked with no language setting
Surya has no language option. I gave it 3 German pages. One is a magazine page with two columns.
194 of 206 umlauts
966 of 972 words in order, two columns
No GPU? Expect a long wait
Marker uses this same model. On 2 CPU threads, it got no page done in 10 minutes (Marker OCR test). I did not run Surya on CPU again.
Surya read better than our Actor
Our Actor makes Markdown from a file URL, with no install:
curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
| jq -r '.[].markdown' > mixed.md
| Test | Surya | Actor |
|---|---|---|
| Scan words in order | 1,926 | 1,874 |
| Umlauts | 194 | 192 |
| Two columns, in order | 966 | 954 |
| Time, 11 pages | 50 s | 133 s |
The Actor needs no GPU and no 2.4 GB setup. It costs $0.006 a page. Surya's model weights are free for research, personal use and startups under $5M. Past that, you buy a license.
How I tested it
- Scan: the 10 pages of OCR in n8n, proofread Wikisource book scans, 1,955 true words. The 11-page file adds the 13-word typed cover page of AnythingLLM OCR.
- German: the 3 pages of Paperless-ngx OCR, 1,887 words, 206 umlauts.
- Score: true words found in order in totext.py's text, with the scorer of the tessdata test. German: the scorer of Tesseract German.
- surya-ocr 0.23.0, llama.cpp b12316, Python 3.12 arm64, on an Apple Silicon Mac. 2 threads at the lowest priority. One run each. Times include the model start.
- The code blocks above are the files that ran. The Actor: two real runs earlier the same day, build 0.1.65, on the same files.
What I did not test
- Linux or Windows, and NVIDIA GPUs (Surya uses vLLM there).
- DPI 96 on small print, tables, math and handwriting.
- PaddleOCR on these files, and surya_layout or surya_table.
Two ways to skip the work
Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.
Questions people ask
Does Surya OCR need a GPU?
On a Mac it used the GPU through llama.cpp and read 10 pages in 56 s. Marker uses the same model, and on 2 CPU threads it got no page done in 10 minutes.
How do I get plain text out of surya_ocr?
surya_ocr writes results.json, with the text of each block as HTML. The 10-line totext.py on this page removes the tags and prints the blocks in reading order.
Can Surya OCR read German?
Yes. Surya 0.23.0 has no language option. On 3 German pages with no setting, it kept 194 of 206 umlauts.
Related guides
- Marker OCR for scanned PDFs: setup and results, tested
- PDF to Markdown in Python: 5 libraries tested
- Docling OCR engines: EasyOCR vs Tesseract vs RapidOCR
- EasyOCR vs Tesseract vs PaddleOCR vs RapidOCR, tested
- Obsidian OCR for scanned PDFs: Omnisearch, tested
- Joplin OCR for scanned PDFs: what it reads, tested
- Chandra OCR on a scanned PDF: Mac setup and results, tested
- dots.ocr on a scanned PDF: Mac setup and results, tested