Skip to content

Commit 178c245

Browse files
fix: add bottom bbox padding to prevent model from dropping last table row
1 parent aff28b4 commit 178c245

10 files changed

Lines changed: 1670 additions & 1140 deletions

File tree

docling/models/stages/table_structure/table_structure_model.py

Lines changed: 9 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,22 @@ def predict_tables(
200200
table_prediction = TableStructurePrediction()
201201
page.predictions.tablestructure = table_prediction
202202

203+
# Small bottom padding (page units) added to the table bbox #3402
204+
_BBOX_BOTTOM_PAD = 5.0
203205
in_tables = [
204206
(
205207
cluster,
206208
[
207209
round(cluster.bbox.l) * self.scale,
208210
round(cluster.bbox.t) * self.scale,
209211
round(cluster.bbox.r) * self.scale,
210-
round(cluster.bbox.b) * self.scale,
212+
round(
213+
min(
214+
cluster.bbox.b + _BBOX_BOTTOM_PAD,
215+
page.size.height,
216+
)
217+
)
218+
* self.scale,
211219
],
212220
)
213221
for cluster in page.predictions.layout.clusters
@@ -227,7 +235,6 @@ def predict_tables(
227235
for table_cluster, tbl_box in in_tables:
228236
# Check if word-level cells are available from backend:
229237
sp = page._backend.get_segmented_page()
230-
using_word_cells = False
231238
if sp is not None:
232239
tcells = sp.get_cells_in_bbox(
233240
cell_unit=TextCellUnit.WORD,
@@ -236,8 +243,6 @@ def predict_tables(
236243
if len(tcells) == 0:
237244
# In case word-level cells yield empty
238245
tcells = table_cluster.cells
239-
else:
240-
using_word_cells = True
241246
else:
242247
# Otherwise - we use normal (line/phrase) cells
243248
tcells = table_cluster.cells
@@ -287,77 +292,6 @@ def predict_tables(
287292
.get("rs_seq", [])
288293
)
289294

290-
# docling-side workaround for the edge case, main root cause is in TFPredictor
291-
# model doesn't see the last row because it's at the very bottom edge of the crop
292-
if (
293-
self.do_cell_matching
294-
and using_word_cells
295-
and tokens
296-
and table_cells
297-
):
298-
matched_ids = {
299-
str(k)
300-
for k in table_out["predict_details"]
301-
.get("matches", {})
302-
.keys()
303-
}
304-
cells_bottom = max(
305-
(tc.bbox.b for tc in table_cells if tc.bbox is not None),
306-
default=0.0,
307-
)
308-
unmatched = []
309-
if cells_bottom > 0.0:
310-
for tok in tokens:
311-
if str(tok["id"]) in matched_ids:
312-
continue
313-
tok_bbox = BoundingBox.model_validate(
314-
tok["bbox"]
315-
).scaled(1 / self.scale)
316-
if (
317-
tok_bbox.t > cells_bottom
318-
and tok_bbox.b <= table_cluster.bbox.b
319-
):
320-
unmatched.append((tok, tok_bbox))
321-
if len(unmatched) > 1:
322-
centres = sorted((tb.t + tb.b) / 2.0 for _, tb in unmatched)
323-
median_centre = centres[len(centres) // 2]
324-
avg_height = sum(tb.b - tb.t for _, tb in unmatched) / len(
325-
unmatched
326-
)
327-
coherent = all(
328-
abs((tb.t + tb.b) / 2.0 - median_centre) <= avg_height
329-
for _, tb in unmatched
330-
)
331-
if not coherent:
332-
unmatched = []
333-
if unmatched:
334-
extra_row_idx = num_rows
335-
num_cols = max(num_cols, 1)
336-
for col_idx, (tok, tok_bbox) in enumerate(unmatched):
337-
table_cells.append(
338-
TableCell(
339-
text=tok["text"],
340-
bbox=tok_bbox,
341-
row_span=1,
342-
col_span=1,
343-
start_row_offset_idx=extra_row_idx,
344-
end_row_offset_idx=extra_row_idx + 1,
345-
start_col_offset_idx=col_idx % num_cols,
346-
end_col_offset_idx=(col_idx % num_cols) + 1,
347-
column_header=False,
348-
row_header=False,
349-
row_section=False,
350-
)
351-
)
352-
num_rows += 1
353-
_log.debug(
354-
"Recovered %d unmatched token(s) into extra row %d "
355-
"for table cluster id=%d",
356-
len(unmatched),
357-
extra_row_idx,
358-
table_cluster.id,
359-
)
360-
361295
tbl = Table(
362296
otsl_seq=otsl_seq,
363297
table_cells=table_cells,

tests/data/groundtruth/docling_v2/2203.01017v2.doctags.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<section_header_level_1><loc_258><loc_138><loc_334><loc_143>a. Picture of a table:</section_header_level_1>
1010
<otsl><loc_258><loc_146><loc_439><loc_191><ched>1<nl></otsl>
1111
<text><loc_437><loc_224><loc_441><loc_228>7</text>
12-
<otsl><loc_258><loc_274><loc_439><loc_313><fcel>0<fcel>1 2 1<lcel><lcel><nl><fcel>3 4<fcel>5<fcel>6<fcel>7<nl><fcel>9 13<fcel>10<fcel>11<fcel>12<nl><fcel>8 2<fcel>14<fcel>15<fcel>16<nl><fcel>17<fcel>18<fcel>19<fcel>20<nl></otsl>
12+
<otsl><loc_258><loc_274><loc_439><loc_313><fcel>0<fcel>1<fcel>2 1<lcel><lcel><nl><fcel>3<fcel>4<fcel>5<fcel>6<fcel>7<nl><fcel>8 2<fcel>9<fcel>10<fcel>11<fcel>12<nl><ucel><fcel>13<fcel>14<fcel>15<fcel>16<nl><ucel><fcel>17<fcel>18<fcel>19<fcel>20<nl></otsl>
1313
<picture><loc_257><loc_143><loc_439><loc_313><caption><loc_252><loc_325><loc_445><loc_353>Figure 1: Picture of a table with subtle, complex features such as (1) multi-column headers, (2) cell with multi-row text and (3) cells with no content. Image from PubTabNet evaluation set, filename: 'PMC2944238 004 02'.</caption></picture>
1414
<text><loc_252><loc_369><loc_445><loc_420>Recently, significant progress has been made with vision based approaches to extract tables in documents. For the sake of completeness, the issue of table extraction from documents is typically decomposed into two separate challenges, i.e. (1) finding the location of the table(s) on a document-page and (2) finding the structure of a given table in the document.</text>
1515
<text><loc_252><loc_422><loc_445><loc_450><loc_41><loc_48><loc_234><loc_61>The first problem is called table-location and has been previously addressed [30, 38, 19, 21, 23, 26, 8] with stateof-the-art object-detection networks (e.g. YOLO and later on Mask-RCNN [9]). For all practical purposes, it can be considered as a solved problem, given enough ground-truth data to train on.</text>

0 commit comments

Comments
 (0)