tesseract.js reads as well as Tesseract. Two defaults differ.
1
word apart, of 14,446, same model and mode
For printed scans: $0.006 a page
Same 55 book pages, same accuracy
Set like the command, 15 of 55 pages came out the same, letter for letter. Each program won some of the rest.
How I tested it
- Tesseract 5.5.4 from Homebrew, and tesseract.js 7.0.0 in Node 22, on one Mac. One page at a time, one thread.
- Scanned book pages from Wikisource, each proofread by two people, picked at random with a fixed seed. Five more were a handwritten manuscript, so I left them out.
- A word counts as found when the proofread page has it and the output has it too, page by page. Curly quotes count as straight ones.
-
Set like the command: Homebrew's eng file and mode 3. No
--dpi: both programs guessed the resolution.
Two defaults differ from the command
| Setting | tesseract.js 7 | tesseract command |
|---|---|---|
| Page mode | 6, one block of text | 3, automatic |
| Model | best_int, 5.2 MB, downloaded | Homebrew's eng: fast, 4.1 MB |
| Engine | 5.1.0-288, WebAssembly | 5.5.4 |
The Tesseract library starts in mode 6. The command switches to mode 3 itself, and tesseract.js calls the library directly. As installed, its text matched mode 6 on all 75 test pages.
Set the mode in one line
PSM.AUTO is mode 3, what the command and pytesseract use.
import { createWorker, PSM } from "tesseract.js";
const worker = await createWorker("eng");
await worker.setParameters({ tessedit_pageseg_mode: PSM.AUTO });
const { data } = await worker.recognize("page.jpg");
console.log(data.text);
await worker.terminate();
For speed, swap the createWorker line for this one. It
loads the command's smaller model:
const worker = await createWorker("eng", 1, {
langPath: "/opt/homebrew/share/tessdata",
gzip: false,
cacheMethod: "none",
});
Keep cacheMethod: "none". tesseract.js saves
eng.traineddata in the working folder and reads it first
next time. Without the line, my swap did nothing.
As installed, 1.7 times slower than the command
The command time includes starting the program for each page, as pytesseract does. tesseract.js loaded its model once, in 0.21 s.
Receipts: mode 6 finds more, and invents more
20 receipt photos from the CORD set, with 509 words in all.
| Setting | Words found | Words written |
|---|---|---|
| Command, as installed | 176 | 338 |
| tesseract.js, as installed | 285 | 1,264 |
| tesseract.js, mode 3 | 181 | 326 |
Every setting got more than half the letters wrong on these photos. For receipts, see receipt OCR in Python.
Limits, and what I did not test
- Browsers. I ran tesseract.js in Node only.
- Other languages, the legacy engine, and Windows.
- Thai: tesseract.js read 82.8% of the letters on 82 scans. Thai is tested here.
- Other machines. Speed is from one Mac.
- Handwriting: the five manuscript pages are not in the totals.
Sources
Our Actor runs the Tesseract program
Our Apify Actor runs the Tesseract program itself, not tesseract.js, with the fast model. It turns each page upright before it reads it.
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
Is tesseract.js less accurate than Tesseract?
Not in a way that matters. Set like the command, it found 14,011 of 14,446 words on 55 book scans, and the command found 14,012.
What page mode does tesseract.js use by default?
Mode 6, one block of text. The tesseract command uses mode 3. Set tessedit_pageseg_mode to PSM.AUTO for mode 3.
Can tesseract.js use my own traineddata file?
Yes. In Node, set langPath to its folder, gzip to false and cacheMethod to none. Without the last one, a cached eng.traineddata can win.