Tesseract psm modes: which one to use, tested
Use psm 3 for normal pages, psm 4 for tables, psm 6 for receipts, psm 7 for a cropped line or word, and psm 11 for scattered labels. The wrong mode gives no error, only less or scrambled text. On the same 72 invoices and stock lists, psm 6 read 137 of 2,418 numbers when the table had grid lines, and psm 3 put 410 in the right row when it had no lines. psm 4 read 2,349 and 2,359. On a word crop with a 20 pixel white border, psm 8 read 63 of 160 and psm 7 read 158.
Try it on your PDF, $0.006 a page
Which psm to use
| Your image | Use | What I measured |
|---|---|---|
| A normal page, one column or more | --psm 3, the default |
psm 3 and 1 alone kept two columns in order: 7,137 of 7,246 words, against about 3,700 |
| A table, and you do not know if it has lines | --psm 4 |
2,349 of 2,418 numbers with a grid, 2,359 with rules under the rows, 2,359 with no lines |
| A receipt | --psm 6 |
336 of 381 rows, against 239 for psm 4 and 68 for psm 3 |
| Scattered labels, a diagram, a form | --psm 11 |
726 of 800 words, against 682 for psm 3 |
| One cropped line, word or cell |
--psm 7, with 5 to 10 px of white around it
|
159 of 160 words |
| One cropped character | --psm 7 or 10 |
The same text on all 124 crops, 101 right at best |
| A page that may be turned | --psm 1 |
5,459 of 5,910 words at a quarter turn left, against 12 for psm 3 |
To set it:
tesseract scan.jpg out --psm 6
import pytesseract
text = pytesseract.image_to_string("scan.jpg", config="--psm 6")
All 14 modes, and what each one did
The names are from tesseract --help-psm in version 5.5.3,
and you can pass a name in place of the number.
--psm single_block gave the same output as
--psm 6.
| psm | Name | What it did in my tests |
|---|---|---|
| 0 | osd_only |
No text. Prints the page's turn and script. |
| 1 | auto_osd |
The same text as psm 3 on every upright page, for 9 to 51% more CPU. Also reads turned pages. |
| 2 | auto_only |
No text. The help says "not implemented", but it printed the orientation and a deskew angle. |
| 3 | auto |
The default. Best on normal pages, weak on tables with no lines and on receipts. |
| 4 | single_column |
Like psm 3 with no column finding. Best all-round on tables. |
| 5 | single_block_vert_text |
227 of 14,693 words on upright book pages. It read pages turned a quarter right. |
| 6 | single_block |
Best on receipts and tables with no lines. Lost most or all rows of every grid table. |
| 7 | single_line |
Joint best on crops, with 6 and 10. On a full page it read 1 word of 14,693, and 41 of 55 pages came back blank. |
| 8 | single_word |
Fine on a tight crop. Falls apart as the border grows. |
| 9 | circle_word |
Did not read curved text. It only tries to remove a ring around a word. |
| 10 | single_char |
The same text as psm 7 on every crop, and it read whole lines. It does not limit you to one character. |
| 11 | sparse_text |
Best on scattered labels. Loses reading order and table rows. |
| 12 | sparse_text_osd |
Like psm 11 (725 of 800 label words), and it read turned book pages. |
| 13 | raw_line |
The same text as psm 8 on all my pages and crops. They parted only on the ring tests. |
The results on full pages
I ran every mode on each set with Tesseract 5.5.3 and its default English model. The book pages are real: 55 printed pages from 11 books, 14,693 words checked by two people on Wikisource. The rest I made, so I know the true text: invoices, two column pages, receipts and label sheets, tilted, blurred and saved as JPEG like a scan.
| Pages | Scored on | psm 3 | psm 4 | psm 6 | psm 11 |
|---|---|---|---|---|---|
| 55 book pages | Words in order, of 14,693 | 13,858 | 13,848 | 13,839 | 13,747 |
| 12 two column pages | Words in order, of 7,246 | 7,137 | 3,728 | 3,767 | 3,721 |
| 72 invoices and stock lists, grid lines | Numbers in the right row, of 2,418 | 2,337 | 2,349 | 137 | 37 |
| Same, rough scans | Numbers, of 2,418 | 2,079 | 2,090 | 121 | 40 |
| Same, rules under the rows only | Numbers, of 2,418 | 2,356 | 2,359 | 2,358 | 39 |
| Same, no lines | Numbers, of 2,418 | 410 | 2,359 | 2,367 | 42 |
| 24 receipts | Item and price on one line, of 381 | 68 | 239 | 336 | 0 |
| 12 label sheets | Words anywhere, of 800 | 682 | 636 | 637 | 726 |
On plain prose you can pick almost anything. The modes only start to differ once the layout does, and on tables the best read over 50 times as much as the worst.
psm 11's low table scores are about order, not reading. It found 1,984 of the 2,418 numbers somewhere on the page. It just does not keep them in rows. The two column pages show the same thing: psm 4 and 6 read 7,133 and 7,132 words in any order, but they ran straight across the gap, as psm 6 did here:
Not in this country? Now, then, what account do you door; but as you know, sir, it were locked on the
give of what's happened since he came back? inside, and all | could do, | rung up everyone, and
The full page modes are 3, 4, 6, 11 and their turned cousins 1 and 12. The rest are not for pages. On the 55 book pages, psm 5 read 227 words, psm 8 and 13 read 14, psm 10 read 6, psm 9 read 4, and psm 7 read 1.
Tables: the lines decide it
The same 72 invoices and stock lists, the same text and the same scan look. Only the lines changed.
| Table | psm 3 | psm 4 | psm 6 |
|---|---|---|---|
| Grid lines | 2,337 | 2,349 | 137 |
| Rules under the rows | 2,356 | 2,359 | 2,358 |
| No lines | 410 | 2,359 | 2,367 |
psm 6 on a grid. Here is one whole page. All 11 rows of the table are gone, and nothing tells you:
Northfield Supply - Invoice 1735
Date: October 12, 2026. Terms: net 30.
[SKU | Description [ty | Unit price | Amount |
Thank you for your business.
Modes 1 to 4, 11 and 12 go through a layout step, and that step calls
FindAndRemoveLines in
pagesegmain.cpp. psm 6 skips it, so the grid stays in the image. I did not trace the
exact step that drops the text, but rules under the rows did no harm,
so closed cells seem to be what it cannot take. Each of the 4 fonts
read between 1 and 64 numbers of about 600.
psm 3 with no lines. It sees columns and reads them one at a time, so on most pages the numbers leave their rows:
SKU
1001
1002
1003
...
Description
Tile spacers
It read 2,071 numbers somewhere on the page, and 410 in the right row. psm 4 is psm 3 without the column search. pagesegmain.cpp says "no attempt is made to divide the image into columns", and column finding is on for modes 1 to 3 only (publictypes.h). That one change kept all three tables whole, and it is the mode I would try first on a table I have not seen.
psm 4 still gives you words, not cells. To rebuild rows and columns, see Tesseract table extraction.
Receipts: psm 6, and watch fixed width fonts
The gap between psm 4 and 6 is all Courier New. This is item and price read on one line, in 8 receipts of each font:
| Font | Rows | psm 3 | psm 4 | psm 6 |
|---|---|---|---|---|
| Courier New | 123 | 0 | 0 | 98 |
| Arial | 120 | 41 | 112 | 111 |
| Times | 138 | 27 | 127 | 127 |
On Courier New, psm 3 and 4 printed the items as one list, with no
price on any item line. psm 6 was the only mode that held them
together. If your till prints fixed width, start with psm 6. It is not
perfect: it got 345 of 381 prices, and one line read
Lemons tos 32. Add the items up and check them against
the total.
Crops: psm 7, and leave a border
The Tesseract docs say to crop "to a text area with reasonable border
(e.g. 10 px)". The mode you choose decides how much that matters. This
is 160 words and IDs like INV-32931 and
$4,317.61, cut with 2 pixels of margin, then given a pure
white border:
| White border added | psm 3 | psm 7 | psm 8 |
|---|---|---|---|
| None | 87 | 149 | 148 |
| 5 px | 147 | 159 | 154 |
| 10 px | 158 | 158 | 123 |
| 20 px | 158 | 158 | 63 |
| 40 px | 158 | 158 | 9 |
-
psm 8 gets worse past 5 pixels of border. That is
backwards from the usual advice. In
linerec.cpp, psm 8 and 13 "use the whole image without any other row/word
interpretation". The other modes cut to the word. My reading is that
the white becomes part of the line that gets scaled down, so the
text shrinks, but I did not trace it. The reads were close and wrong
in small ways: on crops with a 20 pixel margin,
INV-32931came back asINV-32931.andruinsasruins |. - psm 3 needs the border. With 2 pixels of margin, 69 of 160 crops came back empty. That is a common cause of an empty string from pytesseract.
- Do not cut at the ink. With no margin at all, psm 7 read 96 of 160 and psm 3 read 40.
- Whole lines agree. On 120 lines, psm 7 read 105 with no border and 113 from 10 pixels up. psm 8 read 104 at 10 pixels, 43 at 20 and 1 at 40.
So crop with a little room, add white, and use psm 7:
import pytesseract
from PIL import Image, ImageOps
crop = Image.open("scan.jpg").convert("L").crop((200, 420, 1400, 500))
crop = ImageOps.expand(crop, border=10, fill=255)
text = pytesseract.image_to_string(crop, config="--psm 7")
fill=255 assumes dark text on a white page. For a number
field, add a whitelist too:
Tesseract digits only.
Single characters. I ran 124 of them, 62 in each of 2
fonts. psm 3 returned nothing at every border. psm 7 and 10 gave the
same text every time, with 98 right at 5 pixels, 96 at 10 and 83 with
no border added. The second sweep reached 101 at 10 pixels. Many
misses were look-alikes and doubles: o as O,
l as |, c as Cc.
One letter gives the model little to go on, so read the whole word if
you can.
Turned pages: psm 1, 5 and 12
I turned 22 book pages each way and read them. Words in order, of 5,910:
| Page turned | psm 1 | psm 3 | psm 5 | psm 6 | psm 12 |
|---|---|---|---|---|---|
| A quarter turn right | 5,552 | 5,552 | 5,542 | 94 | 5,499 |
| A quarter turn left | 5,459 | 12 | 15 | 87 | 5,393 |
So psm 3 copes with one direction and not the other, and psm 5 reads the same direction psm 3 does. psm 5 is meant for vertical scripts. I have only the English model, so that use is not tested. Book pages are easy, though. On tables and forms psm 1 did far worse, and turning the page yourself did better. That test is on Tesseract orientation detection.
psm 0, 1 and 12 need osd.traineddata beside your language
file. Without it, psm 1 printed this, then read the page without any
turn:
Failed loading language 'osd'
Tesseract couldn't load any languages!
Warning: Auto orientation and script detection requested, but osd language failed to load
psm 9 does not read around a circle
I hoped for a stamp reader. I drew 20 words bent around a ring, twice
each, and every mode read 0 of them. In
pagesegmain.cpp, psm 9 calls RemoveEnclosingCircle, so it tries to take
a ring away from around a word. It does not unbend anything. With the
same 40 words straight inside a drawn ring, psm 9 read 10, the same as
psm 3, 6 and 7, and psm 8 read 0. For those, crop inside the ring: psm
3 and 7 then read all 40.
The flag, and the errors you will see
| You pass | You get |
|---|---|
-psm 6 |
Error, unknown command line argument '-psm'
|
--psm=6 |
Error, unknown command line argument '--psm=6'
|
--psm 14 |
Invalid PSM value, please enter a symbolic PSM value or a
number between 0-13
|
config="--psm 0" or 2 in pytesseract |
FileNotFoundError on a temp file, because these
modes write no text
|
In pytesseract, the two bad spellings raise
TesseractError with the same message. For psm 0, call the
function made for it:
print(pytesseract.image_to_osd("scan.jpg"))
One trap has no error at all. A config setting worked for psm 4 and 11, so you would expect it to work for 6:
tesseract scan.jpg out -c tessedit_pageseg_mode=6
That gave psm 3 output, byte for byte. The library's own default is 6
and the program's is 3, so the program swaps a 6 for a 3. A comment in
tesseract.cpp
admits it: "the only way to force tesseract::PSM_SINGLE_BLOCK is from
the command line." Use --psm 6.
psm and oem are different things.
--psm is the layout, and --oem is the
engine. --oem 1, --oem 3 and no flag gave
the same output. --oem 0 and 2 ask for the
old engine, which the default model leaves out, so they stop with
Error: Tesseract (legacy) engine requested, but components are not
present. The models are on
tessdata_best vs tessdata_fast.
Do not let confidence pick the mode
The obvious shortcut is to run modes 3, 4, 6 and 11, then keep the one with the highest mean word confidence. I tried it, page by page:
| Pages | Picked by confidence | Best single mode |
|---|---|---|
| Book pages | 13,826 | 13,858 (psm 3) |
| Two column pages | 4,856 | 7,137 (psm 3) |
| Invoices, grid lines | 96 | 2,349 (psm 4) |
| Invoices, no lines | 237 | 2,367 (psm 6) |
| Receipts | 195 | 336 (psm 6) |
| Label sheets | 726 | 726 (psm 11) |
It chose psm 11 on 65 of the 72 grid pages, the mode that scatters the rows. Confidence rates the words, not the layout. More on what it does and does not catch: Tesseract confidence score. Instead, run the modes on a few pages and read the output:
import pytesseract
for psm in (3, 4, 6, 11):
text = pytesseract.image_to_string("scan.jpg", config=f"--psm {psm}")
print(f"--- psm {psm}\n{text}")
If you do not want to build it
PDF OCR to Markdown is our Apify Actor. It renders each scanned page at 300 dpi in gray, turns it upright, and reads it at psm 3. If that misreads columns it reads again at psm 4. Ruled tables are read cell by cell at psm 7 with a white border. 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.
I did not run these tests on it, so no number above describes it.
What I did not test
- Real tables and real receipts. All 72 invoices and stock lists, 24 receipts and the label sheets are made up, so real paper may split the modes differently. Only the book pages are real scans.
- Thermal paper, phone photos of receipts, creases and faded print.
- Any language but English. That includes vertical Japanese and Chinese, which is what psm 5 is for.
- Other models, including tessdata_best.
- Pages that mix layouts, such as a two column report with a grid table in it. psm 3 came close on both parts here, but I did not test them on one page, so try splitting those pages first.
- Any machine but this Mac.
Questions people ask
What is the default psm in Tesseract?
psm 3, fully automatic page segmentation. The tesseract program and pytesseract both use it when you pass no flag. The library's own default is psm 6, which is why -c tessedit_pageseg_mode=6 gave psm 3 output in my test.
What does psm 6 do in Tesseract?
It treats the image as one uniform block of text and skips the layout step. That suited receipts and tables with no lines, where it read 336 of 381 rows and 2,367 of 2,418 numbers. On a table with grid lines it read 137 of 2,418.
What is the difference between psm 6 and psm 11?
psm 6 keeps lines together, and psm 11 looks for any text in no particular order. On scattered labels, psm 11 read 726 of 800 words and psm 6 read 637. On receipts, psm 6 kept 336 of 381 rows together and psm 11 kept 0.
How do I set psm in pytesseract?
Pass it in config, as in image_to_string(image, config="--psm 6"). Use two dashes and a space: -psm 6 and --psm=6 both raise TesseractError. psm 0 and 2 write no text file, so image_to_string raises FileNotFoundError. Use image_to_osd for psm 0.
Which psm is best for a single line or word?
psm 7, with 5 to 10 pixels of white around the crop. It read 158 or 159 of 160 words at every white border from 5 to 40 pixels. psm 8 fell from 154 at 5 pixels to 63 at 20 and 9 at 40.
What is the difference between psm and oem in Tesseract?
psm sets the layout Tesseract expects, and oem sets the engine. With the default model, --oem 1, --oem 3 and no flag gave the same output. --oem 0 and 2 ask for the legacy engine, which that model does not include, so they stop with an error.
Also in this set: how to improve Tesseract accuracy, best DPI for OCR, Tesseract table extraction, Tesseract orientation detection, pytesseract PDF to text.
How this was made: Tack, our AI personality, ran these tests on
October 31, 2026, and copied every result above from those runs. Apple
M5 Max, Homebrew Tesseract 5.5.3 with its default English model
(tessdata_fast 4.1.0), pytesseract 0.3.13, Pillow 13.0.0, Python 3.13,
8 workers, OMP_THREAD_LIMIT=1. CPU figures are one run
each. Every setting but the psm stayed at its default. Modes 1 and 3
to 13 ran on every set, except that the turned pages ran modes 1, 3,
5, 6 and 12, and the border sweeps ran 5 or 6 modes. Modes 0 and 2
give no text. The book pages are the printed Wikisource pages of the
tessdata page, read with
--dpi 300, and a word counts if it matches exactly, in
order. The grid invoices are the clean and rough 300 dpi sets of
best DPI for OCR, scored by the row
scorer of the
confidence score page,
which drops table rules from each word first. Here one read line
counts for one row only. The rules and no lines sets are the clean
invoices drawn again with only the border style changed. The two
column pages use text from the same books, 449 to 620 words a page in
4 fonts. The receipts have 8 to 16 items plus subtotal, tax and total,
and a row counts only if every word of the item and the exact price
sit on one line. The label sheets hold 40 labels each at 9 to 26 point
in 4 fonts. Those 3 sets have the rough scan look: a tilt of up to 1.2
degrees, blur, noise of 10 to 14 gray levels, and JPEG quality 70 to
80. The crops are drawn by Pillow at 46 pixel type, about 11 point at
300 dpi, in 4 fonts (2 for single characters), with blur 0.8, noise of
6 gray levels and JPEG quality 85, and saved with no dpi. A crop
counts only if the output equals the text. The white border table adds
pure white to crops that keep 2 pixels of their own margin. The 20
pixel samples and the no margin counts come from a second sweep whose
margin carries the same noise as the text. Every Python and shell
block ran as printed on copies of those files, apart from the output
name. The scripts and their output are in our private repo. Sources,
read October 31, 2026:
Improving the quality of the output
(tessdoc), and at Tesseract 5.5.3
tesseract.cpp,
pagesegmain.cpp,
linerec.cpp
and
publictypes.h. We make the Apify Actors. Tesseract, pytesseract, Pillow and
Wikisource are free projects we do not make.