Skip to main content

pdf-table-gate

Decide whether a PDF page actually contains a table — before you try to extract one.

PyMuPDF's table finder is very good at finding tables. It is also very good at finding tables in pages that do not have any. This library answers the prior question, so the extractor is only ever asked where the columns are on pages that genuinely have columns.

import fitz
from pdftablegate import has_table

doc = fitz.open("report.pdf")
for page in doc:
    if has_table(page):
        tables = page.find_tables(strategy="text", min_words_vertical=2)

The problem

PyMuPDF extracts ruled tables (strategy="lines_strict") reliably. For tables held together by alignment alone — which is most real-world statements, invoices and reports — it offers strategy="text", which splits on whitespace.

That strategy cannot tell a table from a paragraph. A paragraph is also words separated by spaces.

Run examples/measure.py and you get this, on a document containing no tables at all:

Document: 7 pages of prose, zero tables.

  find_tables(strategy='text') alone : 7 'tables' found
    largest hallucination            : 35 rows x 17 cols
  with has_table() as a gate         : 0 tables found

Each of those "rows" is a sentence shredded at its spaces:

Paragraph | number | 0 w | ith | s | ome | fi | ller | ...

Nothing in the output signals low confidence. You get a workbook of confetti under a cheerful "7 tables found." The extractor isn't broken — it is answering a question that has no answer on that page.

The approach

A real table without ruling lines has one property prose never has: a band of blank space running top to bottom that every row respects. A column gutter.

Prose fills the full measure on nearly every line, so no vertical channel survives more than a line or two. That is a physical fact about the layout, not a heuristic about content, which is why it is what gets tested.

The implementation clusters words into visual rows, then sweeps the page width with a difference array to find channels no row crosses — tolerating a small fraction of rows that do, because real tables have merged cells, section headers and totals lines that reach across columns.

Use both halves for what each is good at. The gutter detector is too coarse to place columns; a wide cell reaching across a gutter will merge two columns into one. find_tables resolves columns correctly but hallucinates on prose. So the gate decides whether, and find_tables decides where.

Install

pip install pdf-table-gate

Requires Python 3.9+ and PyMuPDF. Nothing else — no ML runtime, no model download.

API

has_table(page, config=DEFAULT) -> bool

True when the page has a real column gutter. One text extraction and a linear sweep, so it is cheap enough to call before every extraction rather than after.

table_pages(doc, config=DEFAULT) -> list[int]

0-based indexes of the pages worth extracting.

gutters(page, config=DEFAULT) -> list[tuple[float, float]]

The detected gutters as (x_start, x_end). Draw them on the page and you can see exactly what the gate reacted to; an empty list is why has_table said no.

GateConfig(row_tol_frac=0.6, min_gutter_pt=5.0, gutter_leak=0.04, min_rows=3)
Knob Default What it does
row_tol_frac 0.6 Words join a row when their vertical centres fall within this fraction of median glyph height. Keeps a subscript with its baseline without swallowing the next row.
min_gutter_pt 5.0 Narrower than this is an inter-word space, not a column gap. Below ~5pt it starts finding "columns" between words of a sentence.
gutter_leak 0.04 Fraction of rows allowed to span a gutter. 0 rejects nearly every real table (merged cells); large values start accepting prose.
min_rows 3 Fewer visual rows than this isn't evidence of anything.

What this is not

  • Not an extractor. It returns a boolean. Use PyMuPDF, Camelot or pdfplumber for the data.
  • Not a content classifier. Aligned code, a two-column CV, or a columnar form will read as tabular — structurally, they are. That is the honest boundary of a geometric test.
  • Not useful on scans. A scanned page holds a picture of a table and no words to measure, so has_table returns False. Correct, but run OCR first and re-check.
  • Not a replacement for lines_strict. If your tables have ruling lines, use that directly; it is already reliable and you don't need a gate.

Why not just use a layout model?

pymupdf-layout was evaluated against this exact problem. It pulls in onnxruntime — roughly 40MB of ML runtime — and on the documents measured here the find_tables results were byte-identical with and without it. It does not change this behaviour. If a ~40MB dependency earns its place in your project for other reasons, fine; it will not fix this.

Tests

pip install -e . pytest
pytest

The negative cases are the point. Any detector finds a table in a table; this library exists because the obvious approach also finds them in paragraphs. Every positive test has a prose counterpart, and fixtures are generated rather than committed so you can read exactly what each page looks like.

Provenance

Extracted from the table-extraction engine behind PDF Cubby's PDF-to-Excel tool, where the failure above was found on a real 7-page prose document during testing — it returned seven confident tables of roughly 29 × 24 shredded words. The numbers in this README come from the reproducible synthetic fixture in examples/, which is a little different but the same failure.

Licence

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pdf_table_gate-0.1.0.tar.gz (9.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pdf_table_gate-0.1.0-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file pdf_table_gate-0.1.0.tar.gz.

File metadata

  • Download URL: pdf_table_gate-0.1.0.tar.gz
  • Upload date:
  • Size: 9.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pdf_table_gate-0.1.0.tar.gz
Algorithm Hash digest
SHA256 022e6058eab9aa7fa7f007f1051afa7258a75b7ce132cdcee0adf8d1a1cfa41c
MD5 35ed1614ce32b32688c13717b9f163bc
BLAKE2b-256 4704ee8c3e401f46c716ee3f2072d97d42307dd7c9e00581f08be803c90ec35a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdf_table_gate-0.1.0.tar.gz:

Publisher: publish.yml on creatorx808-hub/pdf-table-gate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdf_table_gate-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pdf_table_gate-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pdf_table_gate-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 945ee3e5289977e70ab6312c9b20c055d1dec39de29dc757cbe05c7ccb435bc7
MD5 0f483610eeee55960b5e759bf7667bdc
BLAKE2b-256 507718cebf38849b5c09dd9d53ccbb3beacbec7f46b7d94f52c08b14d29eeff2

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdf_table_gate-0.1.0-py3-none-any.whl:

Publisher: publish.yml on creatorx808-hub/pdf-table-gate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page