TSR Coordinate System Alignment#
The Core Mismatch#
RAGFlow's PDF parser uses two distinct coordinate spaces that must be reconciled before table reconstruction is possible:
| Component | Coordinate Space | Origin |
|---|---|---|
TSR bounding boxes (tbl_det output) | Image-local — relative to each cropped table image | Top-left of the table crop |
OCR text boxes (self.boxes) | Page-cumulative — absolute Y offset across all pages | Top-left of page 1 |
The TSR model (TableStructureRecognizer, a subclass of Recognizer) operates on cropped per-table images and returns rows, columns, headers, and spanning cells in the coordinate space of that crop. Text boxes, by contrast, are stored with Y coordinates shifted by a running sum of all preceding page heights — the page_cum_height array .
How page_cum_height Is Built#
During __images__() :
self.page_cum_height = [0]— initialized with a leading zero.- For each page image,
img.size[1] / zoomin(the page height in PDF points) is appended . - After all pages are processed, the list is converted to a cumulative sum:
self.page_cum_height = np.cumsum(self.page_cum_height).
The result is an array where page_cum_height[i] is the total height of all pages before page i, making it a direct Y-offset lookup.
Pages are rendered at 72 × zoomin DPI (default zoomin=3, so 216 DPI) via pdfplumber , and all coordinate arithmetic is done in these scaled units.
Promoting Text Boxes to Cumulative Space#
After layout recognition (_layouts_rec), every text box's top and bottom are lifted into page-cumulative space :
boxes[i]["top"] += page_cum_height[page_number - 1]
boxes[i]["bottom"] += page_cum_height[page_number - 1]
This means all subsequent operations — including the overlap matching that links TSR structure to text content — work in the same unified Y space.
TSR Processing and Coordinate Bridging (_table_transformer_job)#
_table_transformer_job() is the central routine that:
-
Crops each table from its page image using layout bounding boxes (scaled by
ZM) . The table'stopandx0at this stage are page-local, not cumulative. -
Runs TSR on the cropped images:
recos = self.tbl_det(imgs). Output coordinates are relative to each crop's top-left corner. -
Annotates each TSR item with its page number and table index but does not yet apply the cumulative offset . The raw rotated coordinates are preserved in
x0_rotated,top_rotated, etc. for later rotation correction. -
Matches TSR structure to text boxes via overlap detection . This step requires both parties to share the same coordinate space — the
page_cum_heightoffset is applied toself.boxesin_layouts_rec()before_table_transformer_job()is called, so the TSR structure boxes and the text boxes are directly comparable.
⚠️ Common pitfall: TSR components stored in
self.tb_cpnsretain image-local coordinates (withx0_rotated/top_rotatedcopies). Only the text boxes inself.boxesare in cumulative space. If you add new code that comparestb_cpnsentries directly againstboxesentries, you must account for this asymmetry.
The matching uses Recognizer.find_overlapped_with_threshold() (threshold 0.3) for rows, headers, and spanning cells, and find_horizontally_tightest_fit() for columns. Tags R, H, C, SP written onto each text box drive the downstream TableStructureRecognizer.construct_table() call.
Rotated Table Path: Three-Stage Transformation#
When auto_rotate=True (controlled by the TABLE_AUTO_ROTATE env var, ), tables may be rotated before TSR. _ocr_rotated_tables() then maps coordinates back through three stages:
-
Rotated image → original image coords:
_map_rotated_point()inverts the rotation transform (supports 0°/90°/180°/270°). -
Original image coords → page-local coords: divide by
ZMand add the table'sx0/topoffset from the layout box . -
Page-local → page-cumulative: add
self.page_cum_height[page_index]to everytop/bottom.
The final box is inserted into self.boxes with full cumulative Y coordinates, replacing the original OCR boxes for that table region .
Key Files and Entry Points#
| File | Role |
|---|---|
deepdoc/parser/pdf_parser.py | __images__() builds page_cum_height; _layouts_rec() applies it to text boxes; _table_transformer_job() orchestrates TSR; _ocr_rotated_tables() handles rotation path |
deepdoc/vision/table_structure_recognizer.py | TableStructureRecognizer.__call__() — runs the TSR model; construct_table() — assembles HTML/text table from tagged boxes |
deepdoc/vision/recognizer.py | overlapped_area(), find_overlapped_with_threshold(), find_horizontally_tightest_fit() — geometric overlap utilities used for cross-space matching |