Your structure detector needs an auditor, not a better threshold
TLDR: When a structure detector misfires, the reflex is to tune its thresholds. The better move is to leave detection greedy and add a separate validation pass that audits each detected structure against its own content: cell occupancy, stray-ink fraction, continuity. Detection proposes, validation disposes.
The position
A table detector should be allowed to be wrong, cheaply and often, as long as a downstream auditor catches it. Separating "find candidate structures" from "verify this structure is real" beats any amount of threshold tuning inside the detector itself.
What the industry does and why
Most geometric extraction pipelines (and plenty of ML ones) put all their quality control at detection time: minimum line counts, intersection density, confidence scores. When a false positive ships, someone tightens a threshold. When a true positive disappears, someone loosens it. The threshold oscillates forever because it is one number trying to encode two unrelated questions: "could this be a table?" and "is this a table?"
Why it fails for this problem class
Detection-time signals are local. A grid reconstructor looking at line intersections cannot know that the "table" it found contains no text, because text is not its input. It cannot know its bottom three rows belong to an unrelated warning box, because it has no concept of content regions.
We measured detected lattices on real documents. Genuine tables: 89 to 98 percent of cells contained text, and under 5 percent of the ink inside the bounding box was off-grid. Spurious grids reconstructed from title boxes and diagrams: 0 to 33 percent occupancy, 72 to 99 percent stray ink. The distributions barely touch. No detection threshold sees these numbers, because they only exist after you overlay the detected grid on the page content.
The better approach
Keep the detector greedy. After it emits a candidate, run cheap audits:
- Occupancy: fraction of grid cells containing at least one text item. Below 0.5, this is not a table. If it has text, demote it to a content box. If it has none, drop it.
- Continuity: every band between adjacent row lines should be spanned by at least one vertical line, excluding page-frame artifacts. An unspanned band that is also a pitch outlier (more than twice the median row gap) marks the boundary between two unrelated structures. Split there and re-detect each half.
- Stray fraction: ink inside the bounding box that aligns to no grid line. High stray means the grid is coincidental.
What you give up
Latency: you reconstruct structures you then throw away. In practice the audit is a rounding error next to rendering.
And you need honest demotion paths. Rejecting a candidate means something else must claim that content (a box, a figure, plain paragraphs), or you trade false structure for missing content.
When the common pattern is right
If your detector's input already contains the ground truth (a struct tree, HTML DOM, spreadsheet grid), detection-time rules are fine because the structure is declared, not inferred. Threshold tuning is also right when the two distributions genuinely overlap and no post-hoc statistic separates them. But if you have never computed your detector's output statistics against page content, you do not yet know whether you are in that world. Measure first. The gap is usually wider than you think.