From 463852462ffe271a79c8bd103ccc9d0b92e920dd Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Sat, 6 Jun 2026 16:08:26 +0800 Subject: [PATCH] fix: stop legend-only figures from using fake unmatched assets --- paperforge/worker/ocr_objects.py | 24 +++------- tests/test_ocr_objects.py | 81 ++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/paperforge/worker/ocr_objects.py b/paperforge/worker/ocr_objects.py index 450796f4..bfc5c22b 100644 --- a/paperforge/worker/ocr_objects.py +++ b/paperforge/worker/ocr_objects.py @@ -21,7 +21,11 @@ def render_figure_object_markdown(figure: dict[str, Any]) -> str: else: label = "Orphan Media" - parts = [f"# {label}", "", f"![](../../{image_relpath})", ""] + was_cropped = figure.get("was_cropped", True) + parts = [f"# {label}", ""] + if was_cropped and image_relpath: + parts.append(f"![](../../{image_relpath})") + parts.append("") if caption: parts.append("## Legend") parts.append(caption) @@ -226,23 +230,6 @@ def extract_and_write_objects( was_cropped = True break - if not was_cropped: - for asset in figure_inventory.get("unmatched_assets", []): - bbox = asset.get("bbox", [0, 0, 0, 0]) - asset_page = asset.get("page", 0) - asset_page_width, asset_page_height = _page_dims(asset_page) - if pdf_path and bbox and all(v > 0 for v in bbox) and _crop_asset_from_pdf( - pdf_path, - asset_page, - bbox, - asset_path_abs, - page_width=asset_page_width, - page_height=asset_page_height, - page_cache_dir=page_cache_dir, - ): - was_cropped = True - break - md = render_figure_object_markdown( { "figure_id": fig_id, @@ -250,6 +237,7 @@ def extract_and_write_objects( "caption": caption_text, "image_relpath": asset_path_rel, "confidence": match.get("confidence", 0.5), + "was_cropped": was_cropped, } ) _write_object_markdown(md, figures_render_dir / f"{fig_id}.md") diff --git a/tests/test_ocr_objects.py b/tests/test_ocr_objects.py index 1578f096..bc61e8c0 100644 --- a/tests/test_ocr_objects.py +++ b/tests/test_ocr_objects.py @@ -53,6 +53,87 @@ def test_orphan_object_markdown() -> None: assert "![](../../assets/orphans/orphan_001.jpg)" in md +def test_render_figure_markdown_with_image_when_cropped() -> None: + from paperforge.worker.ocr_objects import render_figure_object_markdown + + md = render_figure_object_markdown({ + "figure_id": "figure_001", + "page": 4, + "caption": "Figure 1. Example.", + "image_relpath": "assets/figures/figure_001.jpg", + "confidence": 0.91, + "was_cropped": True, + }) + + assert "![](../../assets/figures/figure_001.jpg)" in md + + +def test_render_figure_markdown_without_image_when_not_cropped() -> None: + from paperforge.worker.ocr_objects import render_figure_object_markdown + + md = render_figure_object_markdown({ + "figure_id": "figure_001", + "page": 4, + "caption": "Figure 1. Example.", + "image_relpath": "assets/figures/figure_001.jpg", + "confidence": 0.91, + "was_cropped": False, + }) + + assert "![](" not in md + assert "![](../../assets/figures/figure_001.jpg)" not in md + + +def test_legend_only_matched_figure_does_not_use_unmatched_assets(tmp_path: Path) -> None: + from paperforge.worker.ocr_objects import extract_and_write_objects + + render_root = tmp_path / "render" + asset_root = tmp_path / "assets" + + figure_inventory: dict[str, Any] = { + "matched_figures": [ + { + "text": "Figure 1. A legend-only figure.", + "page": 3, + "confidence": 0.85, + "cluster_bbox": None, + "matched_assets": [], + } + ], + "unmatched_assets": [ + {"bbox": [100, 100, 200, 200], "page": 1}, + {"bbox": [300, 300, 400, 400], "page": 1}, + ], + "rejected_legends": [], + "figure_legends": [], + "figure_assets": [], + "official_figure_count": 1, + "unresolved_clusters": [], + } + + extract_and_write_objects( + pdf_path=None, + figure_inventory=figure_inventory, + table_inventory={"tables": [], "unmatched_assets": []}, + asset_root=asset_root, + render_root=render_root, + ) + + render_files = sorted((render_root / "figures").glob("*.md")) + assert len(render_files) == 3, ( + "One matched figure note + two orphan notes from unmatched_assets" + ) + figure_note = render_root / "figures" / "figure_001.md" + assert figure_note.exists() + content = figure_note.read_text() + assert "![](" not in content, ( + "Legend-only figure must not contain any image reference" + ) + # Verify orphan notes still get image paths + orphan_contents = (render_root / "figures" / "orphan_001.md").read_text() + assert "![](../../assets/orphans/orphan_001.jpg)" in orphan_contents + + def test_stabilize_object_wikilink_uses_correct_relative_path() -> None: from paperforge.worker.ocr_objects import render_figure_object_markdown