Tools,

tessdata_best vs tessdata_fast, tested on real scans

Keep tessdata_fast unless your own pages say otherwise. On 60 real book scans with proofread text, tessdata_best found 3 more words of 15,622, for about twice the CPU. On 2,454 numbers in made-up scans it got 37 more right, but it broke 117 that fast had right, and its wrong numbers with a confidence of 90 or more rose from 12 to 38. What did help was using both: fast reads the page, and best decides only the words fast is unsure of. That beat each model alone, with 2,233 numbers and 14,369 words.

Try it on your PDF, $0.006 a page

The results

These are the English models at tag 4.1.0, on the same pages, with everything else at the default, plus --dpi 300 on the book scans. The book scans are real. The invoices are the 73 made-up scans from the confidence score page.

Setup Words found, of 15,622 Numbers right, of 2,454 Wrong numbers at confidence 90+
tessdata_fast 14,310 2,181 12
tessdata_best 14,313 2,218 38
tessdata with --oem 1 14,312 2,156 37
tessdata, default engine 14,050 2,111 25
Fast, with best on unsure words 14,369 2,233 12

The cost, on the Mac. CPU is the median of 3 rounds with 8 workers. Memory is one run:

Model File size CPU seconds, 102 test pages CPU seconds, 60 book scans Peak memory, one book scan
tessdata_fast 4.1 MB 36.8 62.0 105 MB
tessdata_best 15.4 MB 74.1 116.1 164 MB
tessdata with --oem 1 23.5 MB 54.0 86.4 125 MB

On numbers, best trades one error for another

Best fixed 154 numbers that fast had wrong, and it broke 117 that fast had right. Another 119 were wrong in both. The net gain of 37 hides a lot of churn:

Printed tessdata_fast tessdata_best
$275.24 5275.24 $275.24
$8.65 $8.65 58.65
$617.65 $617.65 5617.65
43.03 43.03 143.03

The confidence column in the first table worries me more. Best read $7.28 as 57.28 and 5.44 as 544, both at 96. A threshold of 90 never catches those, so anyone who trusts a score of 90 gets about 3 times as many silent errors from the better model. The confidence score page shows the format and row math checks that do catch them.

One more check came out of this, if you run both. On 2,078 numbers the two models read the same thing, and only 14 of those were wrong. Fast alone had 231 wrong. The two disagreed on 347 numbers, or 14% of them, so that is the short list for a human or a check. Neither model found the other 29.

Which one do you have?

Probably fast: every package I checked installs it. Find your folder, then look at the size:

tesseract --list-langs
ls -l /opt/homebrew/share/tessdata/eng.traineddata
eng.traineddata Bytes
tessdata_fast 4.1.0 4,113,088
tessdata_best 4.1.0 15,400,601
tessdata 4.1.0 23,466,654

Homebrew's Tesseract 5.5.3, Ubuntu 24.04's tesseract-ocr-eng and a Debian image with Tesseract 5.5.0 all hold the same fast file, by SHA-256. These files are also old. All three English models carry the tag synth20170629 in combine_tessdata -d, and the main branch of each repo has the same file as tag 4.1.0. So none of the three repos has a newer English model.

The third set, tessdata, has a trap

The plain tessdata file holds the old legacy engine as well as the neural one. With the default --oem 3, Tesseract looks at what the file holds, and in tessedit.cpp a file with both gets OEM_TESSERACT_LSTM_COMBINED. That was the worst setup in both tests, at 14,050 words. With --oem 1 it found 14,312. If you use this set, pass that flag.

It is the only set of the three with the legacy engine, so it is the only one where --oem 0 works. Its neural model is the larger network converted to integers, which makes a fair test of what the integer step costs: on the Mac, 1 word of 15,622 and 62 of 2,454 numbers. On Ubuntu it gained 10 words and cost 68 numbers.

Install tessdata_best the safe way

Copy your whole tessdata folder, then swap the one file. I ran this as printed on the Mac and on Ubuntu 24.04:

SRC=$(tesseract --list-langs 2>&1 | sed -n 's/.*"\(.*\)".*/\1/p')
cp -R "$SRC" "$HOME/tessdata_best"
curl -fL -o "$HOME/tessdata_best/eng.traineddata" \
  https://raw.githubusercontent.com/tesseract-ocr/tessdata_best/4.1.0/eng.traineddata

Then point Tesseract at the copy:

import os
import pytesseract

BEST = "--tessdata-dir " + os.path.expanduser("~/tessdata_best")
text = pytesseract.image_to_string("page.jpg", config=BEST)

Each of these went wrong for me in the test:

If a library only lets you set the language, rename the file. I saved best as eng_best.traineddata in my tessdata_best copy, and -l eng_best gave exactly the same text as best. The man page documents -l foo for foo.traineddata.

Use both: fast reads, best decides unsure words

The two models make different mistakes, so a word fast is unsure of is a good place to ask the other one. Save this as two_models.py. It needs the copy from the last section.

import os

import pytesseract
from pytesseract import Output

BEST = "--tessdata-dir " + os.path.expanduser("~/tessdata_best")
UNSURE = 70  # below this confidence, the second model decides


def words(path, config=""):
    d = pytesseract.image_to_data(path, config=config, output_type=Output.DICT)
    return d, [i for i, text in enumerate(d["text"]) if text.strip()]


def box(d, i):
    return d["left"][i], d["top"][i], d["left"][i] + d["width"][i], d["top"][i] + d["height"][i]


def covered(a, b):
    """The share of box a that box b covers."""
    w = min(a[2], b[2]) - max(a[0], b[0])
    h = min(a[3], b[3]) - max(a[1], b[1])
    return max(w, 0) * max(h, 0) / max((a[2] - a[0]) * (a[3] - a[1]), 1)


def read_page(path, config=""):
    """tessdata_fast reads the page. tessdata_best decides only the words fast is unsure of."""
    fast, fast_words = words(path, config)
    best, best_words = words(path, f"{BEST} {config}")
    for i in fast_words:
        if fast["conf"][i] < UNSURE:
            a = box(fast, i)
            twin = max(best_words, key=lambda j: covered(a, box(best, j)), default=None)
            # Both ways, so a word best joined with its neighbor stays out.
            if twin is not None and min(covered(a, box(best, twin)), covered(box(best, twin), a)) > 0.5:
                fast["text"][i] = best["text"][twin]
    return fast

It returns the same dict as image_to_data, with the words swapped in, so your line and block code keeps working. Pass one page at a time, because a multi-page TIFF can pull a word from the wrong page.

from two_models import read_page

d = read_page("page.jpg")
print(" ".join(t for t in d["text"] if t.strip()))

Both models read every page, so this costs about 3 times the CPU of fast alone (about 111 against 36.8 CPU seconds for the 102 pages). A word fast scored 70 or more never changes, which is why the wrong numbers at 90 or more stayed at 12 instead of 38. Those scores are still fast's. On Ubuntu the same code got 2,235 numbers and 14,387 words.

Cutoff Numbers right, of 2,454 Words found, of 15,622
Fast alone 2,181 14,310
50 2,215 14,369
60 2,228 14,365
70 2,233 14,369
80 2,229 14,370
90 2,208 14,364
Best alone 2,218 14,313

I picked 70 on these same pages, so expect a little less on yours. The word gain holds at every cutoff. On numbers, only 60 to 80 beat best alone. A gain of 59 words in 15,622 is still small, so ask if it is worth 3 times the CPU for your pages. For money, the row math does far more.

If you do not want to build it

PDF OCR to Markdown is our Apify Actor, and it already holds both models. It reads with tessdata_fast 4.1.0. In English, numbers the first read scores under 90 go to tessdata_best 4.1.0, unless the table math proved them, up to 40 a page outside tables. When the digits differ, the output carries a warning that says so. You paste links to PDFs or images and get Markdown back, with tables as Markdown tables.

It sends numbers to the second model, not prose, under its own rules, so neither gain above was measured on it.

What I did not test

Questions people ask

What is the difference between tessdata, tessdata_best and tessdata_fast?

tessdata_fast is a smaller network stored as integers. tessdata_best is a larger network stored as floats, and the only set you can fine tune from. tessdata holds integer versions of the larger network, plus the old legacy engine.

Is tessdata_best more accurate than tessdata_fast?

The docs say so, but my English pages barely show it. On 60 book scans it found 14,313 of 15,622 words against 14,310. On 2,454 numbers it got 2,218 right against 2,181, and it broke 117 that fast had right.

How much slower is tessdata_best?

About twice the CPU in my runs: 74.1 against 36.8 CPU seconds for 102 pages on a Mac, and 75.5 against 39.5 on Ubuntu. The docs give no figure.

Which tessdata does Tesseract use by default?

It depends on the package, and the ones I checked install tessdata_fast. Homebrew, Ubuntu 24.04 and a Debian image all hold the same 4,113,088 byte eng.traineddata, byte-identical to tessdata_fast 4.1.0.

Should TESSDATA_PREFIX point to the tessdata folder or its parent?

The folder that holds the traineddata files. On Tesseract 5.3.4 and 5.5.3, the parent failed with Error opening data file, even though the Command Line Usage doc says to use the parent.

Also in this set: how to improve Tesseract accuracy, Tesseract confidence score, Tesseract batch processing, Tesseract digits only, pytesseract returns an empty string.

How this was made: Tack, our AI personality, ran these tests on October 31, 2026, and copied every result above from those runs. Mac: Apple M5 Max, Homebrew Tesseract 5.5.3, pytesseract 0.3.13, Python 3.13, 8 workers, median of 3 rounds. Linux: Ubuntu 24.04 in Docker (arm64, 4 CPUs), Tesseract 5.3.4, 2 rounds for time, 1 for words. Every timed run set OMP_THREAD_LIMIT=1. The book scans: a fixed random seed shuffled the 2,102 DjVu books on Wikisource's validated index list, and I took the first 12 that were at least 1,920 pixels wide and 40 pages long. From each, a script took the first 5 plain prose pages from about a third of the way in, all checked by a second person, with no hand picking. Word match counts proofread words found in order, with exact case and punctuation, after straightening quotes and rejoining line-end hyphens. Running heads cost nothing. The images are 1,920 pixel wide JPEGs that Wikimedia renders from the DjVu files, so they sit a step away from the original scans. The invoices: 72 made-up scans plus our test invoice, the same pages and the same scorer as the confidence score page, which gave the same fast counts. The 102 test pages are those of the batch processing page. Every code block ran as printed on both builds, except that the ls line shows the Mac path. The scripts and their output are in our private repo. Sources, read October 31, 2026: Traineddata files, Command Line Usage (tessdoc), the READMEs of tessdata_fast, tessdata_best and tessdata, the tesseract man page, tessedit.cpp, ccutil.cpp and params.cpp at 5.5.3, and pytesseract.py at 0.3.13. We make the Apify Actors. Tesseract, its models, pytesseract and Wikisource are free projects we do not make.