tetrak_ocr.layout

Positioned text: the vocabulary shared by the backends and the PDF writer.

An OCR engine knows more than the string it returns: four of the six backends here compute a box per recognised region and then discard it, because the registry contract is -> str. This module is where that information has a shape, so the backends that keep it can agree on what it means.

Two backends read through it: the vision backend, whose reading-order logic this originally was, lifted out and tested, and easyocr-hy, which needs the column handling because the Armenian pages it exists for are set in two columns. It is deliberately not wired into the searchable PDF writer: a positioned text layer measured worse on reading order than the plain transcript, badly so on multi-column pages, and that is what a screen reader announces. See brief 008 (searchable PDF). The boxes become load-bearing at ALTO/hOCR, where coordinates are the whole point.

Standard library only, so the fast tests can use it with no OCR stack installed.

This module has a twin. tetrak_hy_trainer.align (in the public tetrak-hy-trainer repository) carries its own copy of the gutter detection, the baseline line-grouping and the three straddle constants below, because that repository is public and this one is not, so they cannot share code. The duplication is deliberate; the drift is not. A fix to the column logic here is worth porting there, and vice versa – the two solve the same problem on the same two-column pages, and diverge only in what they do with a span that crosses the gutter: this one must place it (dropping text is not an option when the output is the text), the harvester drops it (the transcripts omit running heads, so it has no truth token to pair with).

Coordinates

TextSpan.bbox is pixels with a top-left origin – the Pillow convention. That choice is deliberate, because three conventions are in play:

Pillow, Tesseract, EasyOCR, Paddle pixels, top-left origin Apple Vision normalised, bottom-left origin PDF points, bottom-left origin

Four of the five sources already speak the Pillow convention, so it wins. Vision converts at its own boundary and the writer converts once on the way out. Anything in between is unambiguous.

Functions

dehyphenate(lines)

Rejoin words that a line break split in two.

find_gutter(spans)

The x of the column gutter, or None if the page reads as one column.

group_lines(spans[, line_tolerance])

Bucket spans into visual lines, top to bottom, each ordered left to right.

reading_order(spans[, line_tolerance])

Return the spans in the order a person would read them.

to_text(spans[, line_tolerance])

Join spans into a transcript: one line per visual line, in reading order.

xy_cut_lines(spans[, line_tolerance])

Visual lines across the page, ordered by recursive XY-cut.

Classes

TextSpan(text[, bbox, confidence])

One recognised piece of text, and where it sits on the page.