On this page

Your table has to come out as rows and columns, not as a heap of words. A pipeline read a dense table with invented numbers and returned every cell as one line with its box. Rows and columns come back from the boxes with a short recipe.

The page is synthetic and the text is invented, so the true text is known cell by cell. Everything below is counted from the stored result; nothing was corrected by hand.

OCR test page: a dense table with 192 cells and invented numbers

The page has a title, a header row, fourteen product groups and a total row. That is sixteen rows of twelve cells: a name column and eleven number columns, in small print, with ruled lines. Some values are negative, and one cell holds -/-, which the footnote explains as "not collected". The numbers come from a fixed random seed.

A dense table with a grid: product groups in rows, quarters and sums in columns, small print

Source: synthetic page synth08 (invented text, free to reuse, CC0), cropped to the table.

What table OCR returns: one line per cell with text, box and confidence score

The pipeline returns lines. On this page that is 194 lines: the title, 192 table cells, and the footnote. Each line has its text, a box and a confidence. The grid is rebuilt from the boxes (next section), and the plain text file has one cell per line. The table pipelines read table cells themselves; see table structure recognition.

Line Text Box (x0, y0, x1, y1) Confidence
1 Quartalsübersicht Warengruppen (Testdaten, Mio. EUR) 148, 122, 1289, 162 0.999
2 Gruppe 154, 230, 252, 263 1.000
3 Q1/24 361, 229, 441, 262 1.000

The first lines of the stored result (run scan-ctc-1-cpu-lokal-synth-20261107, page synth08).

That is enough to rebuild the grid: the boxes say where each cell sits.

How to rebuild table rows and columns from word bounding boxes

Group the boxes by the vertical middle of the box, with a tolerance of ten pixels at this resolution, and sort each group from left to right. On this page that gives eighteen groups: the title alone, sixteen groups of twelve boxes, and the footnote alone. The sixteen groups are the sixteen rows of the table, the first one is the header, the last one the total. The columns follow from the order within a row.

rows = []
for b in sorted(boxes, key=lambda b: (b[1] + b[3]) / 2):
    yc = (b[1] + b[3]) / 2
    if rows and abs(yc - rows[-1]["y"]) <= 10:
        rows[-1]["cells"].append(b)
    else:
        rows.append({"y": yc, "cells": [b]})
grid = [sorted(r["cells"], key=lambda b: b[0]) for r in rows]

This recipe fits a clean page whose rows do not touch. For rotated pages, a pipeline with page orientation turns the page upright first; for cells that span rows, the table pipeline returns the structure.

Table OCR errors: four of 194 cells differ, each with its confidence

Of the 194 lines, 190 equal the invented text. Four differ:

True text Read What differs Confidence
Σ 24 ∑24 another sum sign (U+2211 for U+03A3) and the space is gone 0.934
Σ 25 ∑25 the same 0.924
-/- -1- a slash read as a one 0.881
... „-/-“ steht für: nicht erhoben. ... -/-“ steht für: nicht erhoben. the opening quotation mark is missing 0.999

The one that matters for a sum is -1-: in a column of numbers it looks like a value, and the cell should have been empty. Its confidence of 0.88 is one of only two below 0.9 on the page; the other belongs to the correctly read header Δ %. So a rule that checks every cell below 0.9 would have caught it with one false alarm; the missing quotation mark (confidence 0.999) lies outside such a rule. The rule was tried on this one page.

Four cells, each with what was printed, what the pipeline read and its confidence: two sum signs, a slash read as a one, a missing opening quotation mark.
The four lines that differ; 190 of 194 are equal.

Table test setup: local CPU run on a synthetic page

The run is a local CPU run (Intel i7-1355U, two threads), not a run of the service on a GPU: the same models, line boxes as the pipeline delivers them. The confidences are the pipeline's own account.

Sources

Statement Where it comes from
The page, the true text of each line synthetic page synth08 (image and true text, both own, CC0)
194 lines, boxes, confidences, the four differences stored result of the pipeline, run scan-ctc-1-cpu-lokal-synth-20261107, page synth08
Eighteen groups, sixteen rows of twelve the recipe above applied to those boxes; a test repeats the count