fix(deepdoc): prevent figure and equation crops from merging on scientific PDFs (#15873)

### What problem does this PR solve?

Closes #15872 

On pages that contain both a textless equation and a figure, the layout
recognizer could merge the two unrelated regions into a single cropped
image with concatenated captions. This shows up most often on scientific
and technical PDFs.

The cause is a `layoutno` namespace collision in
`deepdoc/vision/layout_recognizer.py`. Text-overlapping boxes are tagged
per type by `findLayout`: figures become `figure-{ii}` using the
figure-only index, and equations become `equation-{ii}` using the
equation-only index. The fallback loop that handles textless regions,
however, indexes the combined figure plus equation list and always uses
a `figure` prefix.

Because the two paths use different index spaces and prefixes, a page
laid out as `[textless equation, figure with text]` produces two boxes
tagged `figure-0`. `_extract_table_figure` in
`deepdoc/parser/pdf_parser.py` buckets boxes by `f"{page}-{layoutno}"`,
so both fall into the same `page-figure-0` bucket, and `cropout`
stitches the disjoint regions into one image.

**Fix**

The fallback loop now iterates per type, indexes within each type's own
list, and reuses the type as the `layoutno` prefix (`figure-{i}` or
`equation-{i}`). This matches the namespace that `findLayout` already
assigns to text-overlapping boxes. Since the `visited` flag is shared by
reference, each layout is tagged by exactly one path, so per-type
indices stay collision free. Textless equations now land under
`equation-N`, consistent with text-overlapping equations, instead of the
old `figure-N`.

The same fix is applied to `AscendLayoutRecognizer`, which shared the
identical defect.

Downstream consumers of `layoutno` were checked and all treat it as an
opaque equality or bucketing key, so no other code paths are affected.
When a page has no equations, the combined index equals the figure-only
index and the output is unchanged.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
Dexterity
2026-07-22 02:28:44 -04:00
committed by GitHub
parent 863dec759c
commit 18ea0fb7f9

View File

@@ -136,16 +136,23 @@ class LayoutRecognizer(Recognizer):
for lt in ["footer", "header", "reference", "figure caption", "table caption", "title", "table", "text", "figure", "equation"]:
findLayout(lt)
# add box to figure layouts which has not text box
for i, lt in enumerate([lt for lt in lts if lt["type"] in ["figure", "equation"]]):
if lt.get("visited"):
continue
lt = deepcopy(lt)
del lt["type"]
lt["text"] = ""
lt["layout_type"] = "figure"
lt["layoutno"] = f"figure-{i}"
bxs.append(lt)
# add box to figure/equation layouts which have no text box.
# Index within each type's own list and keep the type as the layoutno
# prefix so these match the namespace findLayout() assigns to
# text-overlapping boxes (figure-N vs equation-N). Using a combined
# figure+equation index with a fixed "figure" prefix would collide
# with figure-N tags from findLayout and merge unrelated regions.
for ty in ["figure", "equation"]:
for i, lt in enumerate([lt for lt in lts if lt["type"] == ty]):
if lt.get("visited"):
continue
lt = deepcopy(lt)
lt.pop("type", None)
lt["text"] = ""
lt["layout_type"] = "figure"
lt["layoutno"] = f"{ty}-{i}"
logging.debug(f"Created placeholder box {lt['layoutno']} for textless {ty} region")
bxs.append(lt)
boxes.extend(bxs)
@@ -437,16 +444,22 @@ class AscendLayoutRecognizer(Recognizer):
for ty in ["footer", "header", "reference", "figure caption", "table caption", "title", "table", "text", "figure", "equation"]:
_tag_layout(ty)
figs = [lt for lt in lts if lt["type"] in ["figure", "equation"]]
for i, lt in enumerate(figs):
if lt.get("visited"):
continue
lt = deepcopy(lt)
lt.pop("type", None)
lt["text"] = ""
lt["layout_type"] = "figure"
lt["layoutno"] = f"figure-{i}"
bxs.append(lt)
# Index within each type's own list and keep the type as the layoutno
# prefix so these match the namespace _tag_layout() assigns to
# text-overlapping boxes (figure-N vs equation-N). Using a combined
# figure+equation index with a fixed "figure" prefix would collide
# with figure-N tags from _tag_layout and merge unrelated regions.
for ty in ["figure", "equation"]:
for i, lt in enumerate([lt for lt in lts if lt["type"] == ty]):
if lt.get("visited"):
continue
lt = deepcopy(lt)
lt.pop("type", None)
lt["text"] = ""
lt["layout_type"] = "figure"
lt["layoutno"] = f"{ty}-{i}"
logging.debug(f"Created placeholder box {lt['layoutno']} for textless {ty} region")
bxs.append(lt)
boxes_out.extend(bxs)