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 |
- Words: I expected a clear win for the model with "best" in its name. I got 3 words. By page, best beat fast on 24, lost on 29 and tied on 7.
- Ubuntu: Tesseract 5.3.4 told the same story. Fast found 14,299 words and best 14,313. Fast got 2,182 numbers and best 2,224, with wrong numbers at 90 or more rising from 8 to 33. CPU for the 102 pages was 39.5 and 75.5 seconds.
- One odd book: the random sample picked a handwritten manuscript, which all of them read badly (fast 48.65%, best 50.91%). That book is where best's lead comes from. Without it, fast leads, 13,858 to 13,840 of 14,693. The two-model code below keeps its lead there, with 13,893.
- The docs: the Data Files page calls best "Most accurate" and fast "Least accurate", and it says best is for people "willing to trade a lot of speed for slightly better accuracy". It also says best did best "on Google's eval data". My pages are not that data, and the fast repo's own README says of its models: "For some languages, this is still best, but for most not."
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:
-
A folder with only the model. Plain text works, so
it looks fine. Then
image_to_pdf_or_hocr(..., extension="pdf")raises aFileNotFoundErrorfor a temp file namedtess_....pdf. pytesseract passespdfas a config name, Tesseract printsread_params_file: Can't open pdf, carries on, and writes text with no PDF.image_to_osdfails too, withFailed loading language 'osd'. The copy above bringsconfigs,pdf.ttfandosd.traineddataalong. -
A tilde in the path.
--tessdata-dir ~/tessdata_bestfails withError opening data file ~/tessdata_best/eng.traineddata, because pytesseract runs no shell. Hence theexpanduser. -
TESSDATA_PREFIX at the parent. The
Command Line Usage doc
says to set it "to the parent directory", and the
man page
describes
TESSDATA_PREFIX/tessdata/foo.traineddata. On both builds, that failed. It must be the folder that holds the.traineddatafile, which is what ccutil.cpp does: only the compiled-in default gets/tessdata/added. The flag beats the variable. - A wrong TESSDATA_PREFIX path. On 5.5.3, a path that does not exist prints a warning, and Tesseract falls back to its own folder, so you get fast while you think you have best. On 5.3.4 it is an error. The flag fails on both, so I prefer the flag.
-
--oem 0. On fast or best you getError: Tesseract (legacy) engine requested, but components are not present. Those files have no legacy engine.
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.
- $0.006 a page, plus $0.00005 a run at the default 1 GB. Failed and blank pages are free.
- Links in a sheet? Use Bulk OCR from Google Sheets and CSV, $0.005 an OCR page.
- Pages are read inside the run. They are not sent to any AI service.
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
- Any language but English. The fast README says some languages are still best with fast, and it does not name them. The gap may be bigger in others.
- x86 servers. Every run was on arm64, a Mac and Docker on that Mac. The speed ratio can differ, and I have no Windows run.
-
Phone photos. The book scans were 1,920 pixels wide, read with
--dpi 300. Low-resolution scans are on best DPI for OCR, with the default model only. - Fine tuning. The docs say only best can be a base for it, and that is a good reason to download best on its own.
- A large sample. 60 pages from 12 books can hide a small gap. I would not claim fast is better here, only that I could not find a difference worth twice the CPU.
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.