Tools,

docTR OCR, tested on a real scanned PDF

1,869

of 1,955 scan words, read in order

Get the setup

Ten scanned pages in 13 seconds

13 s for 10 pages, Mac GPU

129 MB model download

  1. Run pip install python-doctr. It installs torch.
  2. Save doctr_ocr.py below next to book.pdf.
  3. Run python3 doctr_ocr.py. It writes book.txt. The first run downloads the two models.
doctr_ocr.py
"""docTR on a PDF: book.txt plus page-NN.txt (brasspage.com/doctr-ocr/, Nov 12, 2026)."""

import argparse

import torch
from doctr.io import DocumentFile
from doctr.models import from_hub, ocr_predictor

ap = argparse.ArgumentParser()
ap.add_argument("--det", default="fast_base")  # the docTR 1.1.0 default
ap.add_argument("--reco", default="crnn_vgg16_bn")  # the default; a name with "/" loads from the Hub
ap.add_argument("--scale", type=float, default=2)  # 2 = 144 dpi, the from_pdf default
a = ap.parse_args()

dev = "mps" if torch.backends.mps.is_available() else "cpu"
reco = from_hub(a.reco) if "/" in a.reco else a.reco
model = ocr_predictor(a.det, reco, pretrained=True).to(dev)
pages = DocumentFile.from_pdf("book.pdf", scale=a.scale)
result = model(pages)
for i, page in enumerate(result.pages, 1):
    with open(f"page-{i:02d}.txt", "w") as f:
        f.write(page.render())
with open("book.txt", "w") as f:
    f.write(result.render())

Do not name your script doctr.py. Python then imports your file, not docTR:

ModuleNotFoundError: No module named 'doctr.io'; 'doctr' is not a package

Curly quotes come out as 66 and 99

The scan has 50 double quote marks.

36 came out as "66"

7 came out as "99"

Lines from book.txt:

Hardy; 66 don't talk tommyrot." What in the
66 Who's this smashed-in boat belong to? 99
tone that implied, 66 And what business is it of
yours? 99

50 of the 76 missed places have a double quote mark. If your text has dialog, fix these marks after the run.

It also kept the running headers and page numbers.

Other models did not read more

Scan words in order, of 1,955
Setup Words Time
Default 1,869 13 s
--scale 3 1,871 13 s
--reco parseq 1,866 20 s
--det db_resnet50 1,852 14 s
Multilingual PARSeq 1,828 12 s

The default pair is fast_base and crnn_vgg16_bn. Scale 3 renders each page at 216 dpi.

German: 0 of 206 umlauts by default

Every pretrained docTR text model uses its "french" letter set. It has ü, but no ä, ö or ß.

The default model wrote "uber" for "über" and "groBe" for "große". The multilingual PARSeq model on Hugging Face fixes most of it:

python3 doctr_ocr.py --reco Felix92/doctr-torch-parseq-multilingual-v1
3 German pages: 206 umlauts, 1,887 words
Model Umlauts Words
Default 0 1,239
Multilingual 147 1,374

On a clean one-column page, the multilingual model read 519 of 521 words. On a two-column page, it read straight across both columns:

ki. Ilauptmann Boris Mantow hatte bis spät in vollkommen entging, wie dieselben Augen aufmerksam

Vision models read more, but slower

The same 10 scanned pages, scorer and Mac GPU:

Scan words in order, of 1,955
Model Words Time
glm-ocr, Ollama 1,953 104 s
Qwen3-VL 8B 1,951 139 s
GOT-OCR 2.0 1,936 134 s
docTR 1,869 13 s

docTR was 8 to 11 times faster. It is the better pick when speed matters more than quote marks.

No GPU: 97 seconds for 10 pages

On the CPU only, with 2 threads, the same script took 97 s. The text was the same as on the GPU, byte for byte.

docTR reads every page as a picture. In an 11-page file with a typed cover page, it read all 13 cover words.

docTR and our Actor: 2 wins, 3 losses

Our Actor, 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, docTR on the Mac GPU
Test docTR Actor
Scan words in order 1,869 1,874
Typed cover words 13 13
Umlauts 147 192
Two columns, in order 482 954
Time, 11 pages 11 s 133 s
Time, 3 German pages 17 s 59 s

docTR used the default models for English, multilingual for German.

The Actor needs no GPU and no model download. It costs $0.006 a page.

Apache 2.0, weights included

The docTR license file is Apache 2.0, and the README says the same. I read both in full before the install.

The default models come from the project's own download links, with no other license. The multilingual model card says apache-2.0.

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. One page has two columns, 972 words.
  • Score: true words found in order in book.txt, with the scorer of the tessdata test. German: the scorer of Tesseract German.
  • python-doctr 1.1.0 and torch 2.15.0, Python 3.12, on an Apple Silicon Mac GPU, at the lowest priority. Multilingual model revision 8bde31c.
  • Times count the whole script, with the model load. The models were already downloaded. One run of each setup.
  • The CPU run changes one line of doctr_ocr.py at run time: the device is "cpu".
  • The glm-ocr, Qwen3-VL and GOT rows: the same files and scorer, on the same Mac, on November 12. They have their own pages.
  • The Actor: two real runs earlier the same day, build 0.1.65, on the same files.
What I did not test
  • Rotated pages, detect_layout, tables and the KIE predictor.
  • The other text models, and community models on the Hub.
  • The docTR API server and Docker images.
  • Windows, Linux and NVIDIA GPUs.
  • Handwriting, receipts, math and photos of signs.
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 docTR need a GPU?

No. On the CPU only, with 2 threads, it read 10 scanned pages in 97 s, with the same text as on the GPU.

Can docTR read German?

Not with the default models: they wrote 0 of 206 umlauts. The multilingual PARSeq model on Hugging Face wrote 147.

Is docTR free for business use?

Yes. The code is Apache 2.0, and the default models come from the project's own links with no other license.