Tools,

Camelot lattice vs stream: lattice needs every line

0 of 139

tables with no full grid, found by lattice

Try it on your PDF, $0.006 a page

Grid: lattice with split_text. Else: stream

92 of 162 hybrid, split_text: best Camelot setting

80 of 162 stream

Exact tables
Flavor Full grid (23) No full grid (139)
lattice 14 0
lattice, split_text 22 0
stream 11 69
stream, split_text 11 71
network 10 69
hybrid 14 69
hybrid, split_text 22 70

"No full grid" means a box only, rules under some rows, stripes, or no lines. Not sure which you have? Use hybrid with split_text.

The default call returns an empty list

import camelot

tables = camelot.read_pdf("statement.pdf")
print(tables)

tables = camelot.read_pdf("statement.pdf", flavor="stream", pages="all")
for row in tables[0].df.values.tolist()[:2]:
    print(row)
<TableList n=0>
['Test page', '', '', '', '']
['Date', 'Description', 'Withdrawals', 'Deposits', 'Balance']

Grid table wrong? Add split_text=True

import camelot

for split in (False, True):
    table = camelot.read_pdf("grid.pdf", split_text=split)[0]
    print(table.df.values.tolist()[0], table.parsing_report["accuracy"])
['Date', 'Description', '', 'Withdrawals Deposits Balance', ''] 68.97
['Date', 'Description', 'Withdrawals', 'Deposits', 'Balance'] 68.97

8 of 9 lattice grid misses fixed

0 right tables made wrong

One line of text crossed 3 cells, and lattice put it all in one cell. split_text=True cuts it at the cell walls.

The accuracy score missed wrong tables

20 of 79 wrong hybrid tables scored 100

29 of 82 wrong stream tables scored 99 or more

parsing_report["accuracy"] did not tell right tables from wrong ones, and the fix above did not move it. Look at a few cells before you trust a table.

Our Actor: 59 of 78 exact, held out

Exact tables, of 78 made after our code froze
lattice
7
stream
38
hybrid
41
hybrid, split_text
46
Our Actor, old build 0.1.30
28
Our Actor, build 0.1.32
59

87.9% of cells right, our Actor

75.3% hybrid, split_text

Build 0.1.32 finds columns from the strips that no word crosses. The old build got fewer than stream.

Our code was tuned on 2 earlier sets from the same generator, so this is near its best case. Camelot had no tuning.

Scanned PDF? Lattice and stream read nothing

page-1 is image-based, camelot only works on text-based pages.

Lattice, stream and hybrid all gave that warning on an invoice scan, and no table. A scan needs OCR first. See Tesseract table extraction.

Tables in your PDFs? Try our Actor

PDF OCR to Markdown, our Apify Actor, turns each page into Markdown, with tables as Markdown tables.

Questions people ask

Does flavor="auto" pick the right one?

Not always. On the held-out set, 8 tables had only a box around them. It chose lattice for all 8 and gave no table.

Does Camelot need Ghostscript?

Not in version 2.0.0. Lattice draws the page with pdfium by default, and every run here had no Ghostscript on the PATH.

Is lattice slower than stream?

Yes. The median was 133 ms a page for lattice and 7 ms for stream. Lattice draws the page as an image to find the lines.

What about flavor="ml"?

I did not test it. It needs the camelot-py[ml] extra and a Table Transformer model. Its source says it fills cells from the PDF's own text, and can OCR a scan with the camelot-py[ocr] extra.

Camelot or pdfplumber?

On the PDFs of our pdfplumber guide, Camelot stream got half the tables exact, and pdfplumber's default got 12 of 84. A column recipe for pdfplumber got all of them, but it was tuned on those PDFs.

Method, limits and sources

How I tested it

156 made-up one-page PDFs with a text layer. 84 are the invoices, statements, parts lists and payrolls of our pdfplumber guide, written by Chromium, LibreOffice and ReportLab.

72 are a held-out set with 78 tables. A separate generator varied fonts, gaps, wrapping and lines. It was made after our Actor's table code froze.

Our Actor's table code was also tuned on the 84 PDFs.

Each flavor ran with pages="all" and default settings, plus split_text where named. Exact means every cell matches, with spaces made single, and no extra column. Extra rows of page text are allowed.

Scripts and results are in research/bench/camelot-2026-11-02/. The true tables are in the truth files of research/bench/pdfplumber-tables-2026-11-02/ and research/bench/pdf-gridless-2026-11-02/.

What I did not test
  • Real PDFs. These are all made up.
  • flavor="ml", table_areas, columns and row_tol.
  • Tables that run over 2 pages.
  • Linux and Windows.
Versions and sources
  • camelot-py 2.0.0 (MIT), pdfminer.six 20260107.
  • pypdfium2 5.14.0, OpenCV 5.0.0, Python 3.12.
  • A Mac, November 2, 2026.
  • camelot-py 2.0.0 on PyPI
  • Camelot docs
  • We make the Apify Actor. Camelot, pdfminer.six and pdfium are open source tools we do not make.