Tools,

Surya OCR, tested on a real scanned PDF

1,926

of 1,955 scan words, read in order

Get the setup

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.

The 10-page scan, Mac GPU
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
Same files, Surya on the Mac GPU
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.
Sources

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one 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.