Engineering Journal
Pdf Processor
Pdf Processor

Your detector scope should match your content scope

2026-07-09

TLDR

Tuning layout detection thresholds to handle multi-column pages without pre-partitioning content leads to continuous threshold oscillation. Running a table or column detector on text items that span multiple independent page columns creates statistical cross-contamination. Pre-splitting unclaimed text into isolated column zones before running spatial layout detectors ensures that every calculated metric operates on a single coherent content stream.
Processing ParadigmInput ScopeThreshold SensitivityFailure Mode
In-Detector Multi-Column TuningFull Page (Interleaved columns)High (Fragile magic thresholds)Silent false positives / negatives
Pre-Split Zone PartitioningSingle Column ZoneZero (Stable, honest thresholds)Graceful degradation

Problem statement: the fallacy of in-detector multi-column modes

When document processing detectors fail on multi-column layouts, developers often add "multi-column modes" with tightened thresholds (e.g., lower fill rate requirements, stricter item count caps).

This approach relies on coincidence: pathological cross-column inputs happen to fail tighter thresholds on specific test documents.

However, inter-column gaps vary based on layout geometry, font sizes, and margin styles. No single global threshold set accurately distinguishes left-column tables from right-column prose across arbitrary layouts.


Technical architecture: aligning detector scope with content scope

Instead of tuning thresholds to handle mixed inputs, partition the input domain into isolated single-column content zones before running spatial layout detectors:

[Unclaimed Text Items] ---> [Page Column Splitter] ---> [Zone 1 Items] ---> [Detector (Zone 1)]
                                                  ---> [Zone 2 Items] ---> [Detector (Zone 2)]

Pre-Split zone orchestration

export function runScopedLayoutDetection(unclaimedItems, viewport, scale) {
  // 1. Determine vertical page column splits across unclaimed text
  const { splits } = detectPageColumns(unclaimedItems, viewport, scale);
  const splitXs = splits.map(s => s.x).filter(x => x > viewport.width  0.1 && x < viewport.width  0.9);

// 2. Fallback to full-page detection if no column splits exist if (splitXs.length === 0) { return detectStreamTables(unclaimedItems, scale); }

// 3. Partition text items into isolated column zones const boundaries = [-Infinity, ...splitXs, Infinity]; const detectedRegions = [];

for (let i = 0; i < boundaries.length - 1; i++) { const minX = boundaries[i]; const maxX = boundaries[i + 1];

const zoneItems = unclaimedItems.filter(item => item.vx >= minX && item.vx < maxX); if (zoneItems.length < 6) continue;

// Detect strictly within single-column scope const zoneResults = detectStreamTables(zoneItems, scale, { isZoneMode: true }); detectedRegions.push(...zoneResults); }

return detectedRegions; }

Rule of thumb: Match detector input scope to content domain scope. Partition multi-column page text into isolated zones before running spatial layout algorithms.
Read this post in the full Engineering Journal →