Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions docling/utils/layout_postprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,30 +395,29 @@ def _handle_cross_type_overlaps(self, special_clusters) -> list[Cluster]:
"""
clusters_to_remove = set()

# TABLE is part of WRAPPER_TYPES and therefore always ends up in special_clusters,
# never in regular_clusters. Build the list from special_clusters so the check
# below can actually find TABLE proposals to compare against.
tables = [c for c in special_clusters if c.label == DocItemLabel.TABLE]

for wrapper in special_clusters:
if wrapper.label not in self.WRAPPER_TYPES:
continue # only treat KEY_VALUE_REGION for now.

for regular in self.regular_clusters:
if regular.label == DocItemLabel.TABLE:
# Calculate overlap
overlap_ratio = wrapper.bbox.intersection_over_self(regular.bbox)

conf_diff = wrapper.confidence - regular.confidence
continue
if wrapper.label == DocItemLabel.TABLE:
continue # TABLE cannot be a candidate for removal here

# If wrapper is mostly overlapping with a TABLE, remove the wrapper
if (
overlap_ratio > 0.9 and conf_diff < 0.1
): # self.OVERLAP_PARAMS["wrapper"]["conf_threshold"]): # 80% overlap threshold
clusters_to_remove.add(wrapper.id)
break
for table in tables:
overlap_ratio = wrapper.bbox.intersection_over_self(table.bbox)
conf_diff = wrapper.confidence - table.confidence
if overlap_ratio > 0.9 and conf_diff < 0.1:
clusters_to_remove.add(wrapper.id)
break

# The picture/table buckets are de-overlapped independently elsewhere, so a
# region the layout model proposes as BOTH a PICTURE and a TABLE survives twice.
# When a PICTURE nearly coincides with a TABLE (high IoU), keep the structured
# TABLE and drop the PICTURE. IoU (not containment) is used so a genuine small
# figure fully inside a large table region is not removed.
tables = [c for c in special_clusters if c.label == DocItemLabel.TABLE]
for picture in special_clusters:
if picture.label != DocItemLabel.PICTURE:
continue
Expand Down
31 changes: 31 additions & 0 deletions tests/test_layout_postprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,34 @@ def test_cross_type_overlaps_keeps_small_picture_inside_table() -> None:

ids = {c.id for c in result}
assert ids == {1, 2}


def test_cross_type_overlaps_removes_kvregion_coinciding_with_table() -> None:
# A KEY_VALUE_REGION that nearly covers the same area as a TABLE should be
# dropped in favour of the TABLE. Previously this check was unreachable because
# TABLE is in WRAPPER_TYPES and therefore never appears in regular_clusters.
processor = object.__new__(LayoutPostprocessor)

table = _cluster(1, DocItemLabel.TABLE, (10, 10, 200, 150), confidence=0.85)
kvr = _cluster(
2, DocItemLabel.KEY_VALUE_REGION, (10, 10, 200, 150), confidence=0.80
)

result = processor._handle_cross_type_overlaps([table, kvr])

labels = {c.label for c in result}
assert DocItemLabel.TABLE in labels
assert DocItemLabel.KEY_VALUE_REGION not in labels


def test_cross_type_overlaps_keeps_kvregion_not_overlapping_table() -> None:
# A KEY_VALUE_REGION on a different part of the page should not be affected.
processor = object.__new__(LayoutPostprocessor)

table = _cluster(1, DocItemLabel.TABLE, (10, 10, 200, 150))
kvr = _cluster(2, DocItemLabel.KEY_VALUE_REGION, (10, 300, 200, 450))

result = processor._handle_cross_type_overlaps([table, kvr])

ids = {c.id for c in result}
assert ids == {1, 2}
Loading