Skip to content

bench: measure the hybrid ceiling — 0.362 to 0.935 with correct boundaries - #23

Merged
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/hal-568-hybrid-ceiling
Aug 2, 2026
Merged

bench: measure the hybrid ceiling — 0.362 to 0.935 with correct boundaries#23
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/hal-568-hybrid-ceiling

Conversation

@hallelx2

@hallelx2 hallelx2 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Answers the question that decides whether a layout model is worth deploying: given perfect rows and columns, how good is extraction?

system precision recall F1
pdftable, current best (lines) 0.865 0.229 0.362
pdftable + ORACLE boundaries 0.948 0.922 0.935
pdftable + oracle + MergeSplitTokens 0.806 0.660 0.726

107 documents (single-region pages).

The verdict

0.362 → 0.935. Given the right grid, pdftable extracts almost perfectly.

Essentially the whole end-to-end gap is table structure, not text extraction. Every cell-filling, coordinate and font-metric fix of the last two days is not the limiting factor — finding rows and columns is.

So a layout model that outputs row/column structure converts nearly the entire gap, and pdftable keeps the two things a generative model cannot give: exact cell text and exact coordinates for citations.

A concrete integration rule falls out

MergeSplitTokens takes the oracle result from 0.935 down to 0.726. That setting exists to repair columns a geometric guess cut through a value; with a correct grid there is nothing to repair, so every merge destroys a correct cell.

Explicit boundaries from a model ⇒ MergeSplitTokens = false. It stays useful on the pure-geometry path.

Integration point already exists

s.VerticalStrategy   = pdftable.StrategyExplicit
s.HorizontalStrategy = pdftable.StrategyExplicit
s.ExplicitVerticalLines   = colBoundaries  // from the layout model
s.ExplicitHorizontalLines = rowBoundaries
s.MergeSplitTokens = false

No new dependency, no HTTP client in the library. The caller owns the model call.

Two harness bugs — worth reading

The first version reported 0.119 F1 with perfect input. Near-random, and obviously measuring itself. Two causes, the same mistake twice:

  1. Every cell bbox edge became a grid line. Adjacent rows have slightly different extents, so this made dozens of near-duplicate boundaries and shredded each table. Fixed by deriving the grid from the ground truth's own start-col/end-col indices — exactly ncols+1 lines.
  2. Ground truth counted for pages the oracle never attempted. Boundaries were supplied only for single-region pages but recall was scored against every region — reporting a deliberate exclusion as an extraction failure.

0.119 → 0.782 → 0.935.

A third suspicion was unfounded: I checked the ground-truth Y origin against pdfplumber word positions and it is bottom-left, same as pdftable (GT y1=619.0 vs word y0=616.9).

Same lesson as the font work: a measurement that disagrees violently with expectation is far more likely to be a broken measurement than a broken system.

Scope

Single-region pages only (107 of 125). Multi-table pages are excluded because merging their regions produces a grid spanning the gap between them — the harness measuring itself again. A real layout model emits one region per table, so this is a fair proxy, but it is not a measurement of multi-table pages.

Relates to HAL-568

Summary by Sourcery

Add a benchmarking harness and evaluation to measure pdftable’s performance when given oracle row/column boundaries, establishing the hybrid layout-model ceiling and documenting integration guidance and harness fixes.

New Features:

  • Introduce an ICDAR 2013 oracle-boundaries benchmark that feeds pdftable ground-truth table grids to measure the best-case hybrid layout-model performance.

Enhancements:

  • Document a concrete integration pattern for supplying explicit row/column boundaries from a layout model into pdftable and disabling MergeSplitTokens in that mode.
  • Refine the benchmarking harness to derive grids from logical cell indices, restrict scoring to single-region pages, and report the resulting ceiling metrics.

Documentation:

  • Add a new evaluation report capturing the oracle-boundaries hybrid ceiling experiment and its implications for layout-model integration and measurement correctness.

Summary by CodeRabbit

  • New Features

    • Added an ICDAR 2013 benchmark for evaluating table extraction with oracle row and column boundaries.
    • Added scoring for cell-relation precision, recall, and F1 across eligible documents.
  • Documentation

    • Added an evaluation report showing F1 improvement from 0.362 to 0.935 when table boundaries are correct.
    • Documented benchmark scope, configuration, limitations, and reproduction steps.

…aries

Answers the question that decides whether a layout model is worth
deploying: given perfect rows and columns, how good is extraction?

  pdftable, current best (lines)   precision 0.865  recall 0.229  F1 0.362
  pdftable + ORACLE boundaries     precision 0.948  recall 0.922  F1 0.935

Given the right grid, pdftable extracts almost perfectly. So essentially
the entire end-to-end gap is table STRUCTURE, not text extraction -- the
cell filling, the coordinates and the text fidelity are not the limiting
factor. A layout model that outputs row/column structure converts almost
the whole gap, and pdftable keeps the two things a generative model
cannot give: exact cell text and exact coordinates for citations.

It also produces a concrete integration rule. MergeSplitTokens drops the
oracle result from 0.935 to 0.726, because that setting exists to repair
columns a geometric guess cut through a value; with a correct grid there
is nothing to repair and every merge destroys a correct cell. Explicit
boundaries from a model therefore imply MergeSplitTokens=false.

The first version of this experiment reported 0.119 with PERFECT input --
near-random, and plainly measuring itself. Two causes, the same mistake
twice: every cell bounding-box edge was treated as a grid line, which
shredded each table into fragments (fixed by deriving the grid from the
ground truth start-col/end-col indices, giving exactly ncols+1 lines);
and ground truth was counted for pages the oracle never attempted, which
reported a deliberate exclusion as an extraction failure. 0.119 -> 0.782
-> 0.935.

A third suspicion was unfounded: the ground-truth Y origin was checked
against pdfplumber word positions and is bottom-left, the same space
pdftable reports.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @hallelx2, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added an ICDAR 2013 oracle-boundaries benchmark. The harness derives grid boundaries from XML, runs pdftable, scores cell relations, and reports aggregate metrics. Documentation records the 2026-08-03 evaluation and reproduction details.

Changes

ICDAR 2013 oracle-boundary evaluation

Layer / File(s) Summary
Oracle grid reconstruction
bench/icdar2013/oracle.py
The harness derives row and column boundaries from indexed XML cell extents, excludes multi-region pages, and reconstructs normalized ground-truth relations.
Benchmark execution and scoring
bench/icdar2013/oracle.py
The benchmark discovers input pairs, runs oracle-boundary variants, handles extraction failures, aggregates precision, recall, and F1, and removes temporary files.
Benchmark and evaluation documentation
bench/README.md, docs/README.md, docs/evaluations/2026-08-03-hybrid-ceiling-oracle-boundaries.md
The documentation links the harness and records the 0.362-to-0.935 F1 result, configuration details, scope, fixes, and reproduction command.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Benchmark as oracle.py
  participant Dataset as ICDAR 2013 XML/PDF
  participant Extractor as pdftable
  participant Scorer as scoring helpers

  Benchmark->>Dataset: discover PDF/XML pairs
  Benchmark->>Dataset: derive oracle row and column boundaries
  Benchmark->>Extractor: run extraction with oracle boundaries
  Extractor-->>Benchmark: extracted cell relations or failure
  Benchmark->>Scorer: compare extracted relations with ground truth
  Scorer-->>Benchmark: precision, recall, and F1
Loading

Possibly related PRs

  • hallelx2/pdftable#20: Introduced the ICDAR 2013 benchmark extended by this oracle-boundaries harness.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the benchmark's main result: improved hybrid extraction with correct table boundaries.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch halleluyaholudele/hal-568-hybrid-ceiling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds an ICDAR 2013 benchmark harness that feeds pdftable ground-truth table boundaries to measure the hybrid ceiling with a layout model, documents the experiment and its integration rules, and wires it into the bench/docs index.

Sequence diagram for the new oracle benchmark harness interaction per document

sequenceDiagram
    participant main as main
    participant extractor as extractor_exe
    participant scoring as score_module

    main->>main: oracle_edges(xml_path)
    main->>main: gt_relations_single_region(xml_path)
    loop for each variant in variants
        main->>extractor: subprocess.run([exe, "-oracle", path] + extra + [pdf])
        extractor-->>main: tables JSON
        main->>scoring: relations_from_grid(t["rows"]) per table
        scoring-->>main: rels Counter
        main->>scoring: score(gt, rels)
        scoring-->>main: c, nd, ng
        main->>main: accumulate totals[name]
    end
    main->>scoring: prf(*totals[name]) for each variant
    scoring-->>main: precision, recall, F1
    main->>main: print summary table and documents scored
Loading

Flow diagram for the oracle harness benchmarking pipeline

flowchart LR
    A["Start main()"] --> B["Collect (pdf, xml) pairs under dataset root"]
    B --> C["Apply limit if provided"]
    C --> D["For each (pdf, xml) pair"]
    D --> E["Compute edges = oracle_edges(xml)"]
    E --> F{"edges exists?"}
    F -->|No| D
    F -->|Yes| G["gt = gt_relations_single_region(xml)"]
    G --> H["Write edges JSON to temp file"]
    H --> I["For each variant in variants"]
    I --> J["Run extractor_exe with -oracle and variant flags"]
    J --> K["Parse tables JSON"]
    K --> L["Build rels via relations_from_grid"]
    L --> M["Update totals using score(gt, rels)"]
    M --> I
    I --> N["Delete temp file"]
    N --> D
    D --> O["Compute precision, recall, F1 via prf"]
    O --> P["Print metrics and scored document count"]
    P --> Q["End"]
Loading

File-Level Changes

Change Details Files
Introduce an oracle-boundaries benchmark harness that derives a clean grid from ground-truth logical indices and evaluates pdftable with and without MergeSplitTokens on single-region pages.
  • Add oracle.py harness that computes row/column boundary lines from ground-truth start/end indices instead of raw cell bounding-box edges.
  • Restrict benchmark scoring to single-region pages by both computing oracle edges only where one region exists and limiting ground-truth relations to those pages.
  • Evaluate two extractor variants (oracle boundaries alone, and oracle boundaries plus token merging) by calling the extractor with an -oracle JSON file and aggregating precision/recall/F1 over documents.
bench/icdar2013/oracle.py
Document the hybrid-ceiling experiment, its results, and integration rules, and register it in the benchmarking and evaluations index.
  • Add a new evaluation markdown file describing the hybrid-ceiling results, the role of explicit boundaries, and guidance to disable MergeSplitTokens when using model-provided grids.
  • Extend bench/README.md to list the new oracle-boundaries ICDAR 2013 benchmark and link to its evaluation report.
  • Update docs/README.md evaluations table to include the hybrid-ceiling experiment alongside existing table-structure and font-metrics evaluations.
docs/evaluations/2026-08-03-hybrid-ceiling-oracle-boundaries.md
bench/README.md
docs/README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
bench/icdar2013/oracle.py (2)

64-64: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Optional: consider defusedxml for parsing dataset XML.

Both ET.parse calls (Line 64, Line 121) parse XML files from the benchmark dataset directory. Static analysis flags standard xml.etree as vulnerable to entity-expansion attacks on untrusted input (S314). Since this is dev-only benchmark tooling parsing a known, locally-downloaded dataset rather than externally-supplied network input, the practical exposure is low, but defusedxml.ElementTree.parse is a drop-in replacement if you want to close the gap.

Also applies to: 121-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/oracle.py` at line 64, Replace both XML parsing calls in the
benchmark flow with defusedxml.ElementTree.parse, updating the import
accordingly while preserving the existing getroot and downstream parsing
behavior.

Source: Linters/SAST tools


55-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: address the ruff RUF007/RUF005 hints.

Line 55 uses zip(idx, idx[1:]); itertools.pairwise(idx) is the idiomatic equivalent for Python's target version and reads more clearly. Line 186 concatenates lists with +; [exe, "-oracle", path, *extra, pdf] is the more idiomatic form ruff suggests. Neither changes behavior.

Also applies to: 186-186

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/oracle.py` at line 55, Update the loop using zip(idx,
idx[1:]) to use itertools.pairwise(idx), adding the required import, and replace
the list concatenation at the command construction near the second occurrence
with list unpacking ([exe, "-oracle", path, *extra, pdf]); preserve existing
behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bench/icdar2013/oracle.py`:
- Around line 49-60: Update _boundaries to handle indices present in only one of
lo or hi without raising KeyError, including asymmetric column and row spans.
For each gap, use the available extent when an edge is missing while preserving
the midpoint calculation when both extents exist, and retain the existing
empty-input and sorted-boundary behavior.
- Around line 183-198: Update the subprocess execution loop over variants to
check the CompletedProcess returncode and surface any subprocess or JSON parsing
failures instead of silently assigning tables = []. Log or print the variant and
relevant error details, while preserving successful output parsing and ensuring
failures are visible in the final benchmark summary rather than included as
empty results.

---

Nitpick comments:
In `@bench/icdar2013/oracle.py`:
- Line 64: Replace both XML parsing calls in the benchmark flow with
defusedxml.ElementTree.parse, updating the import accordingly while preserving
the existing getroot and downstream parsing behavior.
- Line 55: Update the loop using zip(idx, idx[1:]) to use
itertools.pairwise(idx), adding the required import, and replace the list
concatenation at the command construction near the second occurrence with list
unpacking ([exe, "-oracle", path, *extra, pdf]); preserve existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3bb9117e-71a2-4545-b8ce-36772c568030

📥 Commits

Reviewing files that changed from the base of the PR and between 0b39b95 and 3d4b551.

📒 Files selected for processing (4)
  • bench/README.md
  • bench/icdar2013/oracle.py
  • docs/README.md
  • docs/evaluations/2026-08-03-hybrid-ceiling-oracle-boundaries.md

Comment thread bench/icdar2013/oracle.py
Comment on lines +49 to +60
def _boundaries(lo: dict[int, float], hi: dict[int, float]) -> list[float]:
"""Grid lines from per-index extents: outer edges plus gap midpoints."""
idx = sorted(set(lo) | set(hi))
if len(idx) < 1:
return []
out = [lo[idx[0]]]
for a, b in zip(idx, idx[1:]):
# Midpoint of the gutter between two adjacent bands. Using either
# edge alone would clip whichever side is wider on some rows.
out.append((hi[a] + lo[b]) / 2)
out.append(hi[idx[-1]])
return sorted(out)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Reproduce the KeyError in _boundaries with a spanning-cell fixture.
python3 - <<'EOF'
def _boundaries(lo, hi):
    idx = sorted(set(lo) | set(hi))
    if len(idx) < 1:
        return []
    out = [lo[idx[0]]]
    for a, b in zip(idx, idx[1:]):
        out.append((hi[a] + lo[b]) / 2)
    out.append(hi[idx[-1]])
    return sorted(out)

col_lo = {0: 0.0, 1: 10.0}
col_hi = {0: 5.0, 3: 40.0}
print(_boundaries(col_lo, col_hi))
EOF

Repository: hallelx2/pdftable

Length of output: 282


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)oracle\.py$|bench/icdar2013' || true

echo
echo "Relevant file outline:"
ast-grep outline bench/icdar2013/oracle.py --view expanded || true

echo
echo "Relevant lines 1-120:"
sed -n '1,120p' bench/icdar2013/oracle.py | nl -ba

echo
echo "Relevant lines 140-180:"
sed -n '140,180p' bench/icdar2013/oracle.py | nl -ba -v 140

Repository: hallelx2/pdftable

Length of output: 670


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '%s\n' 'bench/icdar2013/oracle.py:1-120'
awk 'NR>=1 && NR<=120 { printf "%6d  %s\n", NR, $0 }' bench/icdar2013/oracle.py

printf '\n%s\n' 'bench/icdar2013/oracle.py:120-190'
awk 'NR>=120 && NR<=190 { printf "%6d  %s\n", NR, $0 }' bench/icdar2013/oracle.py

printf '\n%s\n' 'OracleEdges caller context / imports'
rg -n "def _boundaries|def oracle_edges|oracle_edges\\(|_boundaries\\(|from itertools|import itertools|try:|except Exception|returncode|tables = \\[\\]" bench/icdar2013/oracle.py

Repository: hallelx2/pdftable

Length of output: 9871


Fix the KeyError risk in _boundaries for asymmetric column/row spans.

_boundaries builds idx from lo | hi, then reads both hi[a] and lo[b] for every gap. When a spanning cell starts at column c but no cell ends in column c, hi[c] is missing while c is still in idx, and the loop raises KeyError. The same row-span scenario can fail inside _boundaries(row_lo, row_hi).

Handle missing band extents consistently, for example by using the available edge from lo/hi when one side has no explicit start/end evidence.

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 55-55: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


[warning] 55-55: Prefer itertools.pairwise() over zip() when iterating over successive pairs

Replace zip() with itertools.pairwise()

(RUF007)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/oracle.py` around lines 49 - 60, Update _boundaries to handle
indices present in only one of lo or hi without raising KeyError, including
asymmetric column and row spans. For each gap, use the available extent when an
edge is missing while preserving the midpoint calculation when both extents
exist, and retain the existing empty-input and sorted-boundary behavior.

Comment thread bench/icdar2013/oracle.py
Comment on lines +183 to +198
for name, extra in variants.items():
try:
out = subprocess.run(
[exe, "-oracle", path] + extra + [pdf],
capture_output=True, timeout=180).stdout
tables = json.loads(out or b"[]")
except Exception:
tables = []
rels: Counter = Counter()
for t in tables:
rels += relations_from_grid(
[[norm(c) for c in r] for r in t["rows"]])
c, nd, ng = score(gt, rels)
totals[name][0] += c
totals[name][1] += nd
totals[name][2] += ng

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Surface subprocess failures instead of silently zeroing them out.

Line 189 catches every exception from subprocess.run/json.loads and falls back to tables = [], with no logging. The subprocess result's returncode is also never checked, so a non-zero exit (crash, bad -oracle argument, missing exe) that still writes something to stdout can silently degrade a variant's recall instead of surfacing a tool error. The file's own docstring documents two prior instances of "the harness measuring itself" (edge-clustering and page-scoring bugs); this blanket fallback reintroduces the same class of silent-failure risk for future runs or dataset changes.

Track and print failures so a systemic issue (wrong exe path, crashed binary, flag typo) is visible in the summary rather than blended into the F1 numbers.

🩹 Proposed fix: log failures and check the return code
                 try:
-                    out = subprocess.run(
-                        [exe, "-oracle", path] + extra + [pdf],
-                        capture_output=True, timeout=180).stdout
-                    tables = json.loads(out or b"[]")
-                except Exception:
+                    result = subprocess.run(
+                        [exe, "-oracle", path, *extra, pdf],
+                        capture_output=True, timeout=180)
+                    if result.returncode != 0:
+                        print(f"  [warn] {name} exited {result.returncode} on "
+                              f"{pdf}: {result.stderr.decode(errors='replace')[:200]}",
+                              file=sys.stderr)
+                    tables = json.loads(result.stdout or b"[]")
+                except Exception as exc:
+                    print(f"  [warn] {name} failed on {pdf}: {exc}", file=sys.stderr)
                     tables = []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for name, extra in variants.items():
try:
out = subprocess.run(
[exe, "-oracle", path] + extra + [pdf],
capture_output=True, timeout=180).stdout
tables = json.loads(out or b"[]")
except Exception:
tables = []
rels: Counter = Counter()
for t in tables:
rels += relations_from_grid(
[[norm(c) for c in r] for r in t["rows"]])
c, nd, ng = score(gt, rels)
totals[name][0] += c
totals[name][1] += nd
totals[name][2] += ng
for name, extra in variants.items():
try:
result = subprocess.run(
[exe, "-oracle", path, *extra, pdf],
capture_output=True, timeout=180)
if result.returncode != 0:
print(f" [warn] {name} exited {result.returncode} on "
f"{pdf}: {result.stderr.decode(errors='replace')[:200]}",
file=sys.stderr)
tables = json.loads(result.stdout or b"[]")
except Exception as exc:
print(f" [warn] {name} failed on {pdf}: {exc}", file=sys.stderr)
tables = []
rels: Counter = Counter()
for t in tables:
rels += relations_from_grid(
[[norm(c) for c in r] for r in t["rows"]])
c, nd, ng = score(gt, rels)
totals[name][0] += c
totals[name][1] += nd
totals[name][2] += ng
🧰 Tools
🪛 ast-grep (0.45.0)

[error] 184-186: Use of unsanitized data to create processes
Context: subprocess.run(
[exe, "-oracle", path] + extra + [pdf],
capture_output=True, timeout=180)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(os-system-unsanitized-data)


[error] 184-186: Command coming from incoming request
Context: subprocess.run(
[exe, "-oracle", path] + extra + [pdf],
capture_output=True, timeout=180)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.16.0)

[error] 185-185: subprocess call: check for execution of untrusted input

(S603)


[warning] 186-186: Consider [exe, "-oracle", path, *extra, pdf] instead of concatenation

Replace with [exe, "-oracle", path, *extra, pdf]

(RUF005)


[warning] 189-189: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/oracle.py` around lines 183 - 198, Update the subprocess
execution loop over variants to check the CompletedProcess returncode and
surface any subprocess or JSON parsing failures instead of silently assigning
tables = []. Log or print the variant and relevant error details, while
preserving successful output parsing and ensuring failures are visible in the
final benchmark summary rather than included as empty results.

@hallelx2
hallelx2 merged commit 99f2f83 into main Aug 2, 2026
5 checks passed
@hallelx2
hallelx2 deleted the halleluyaholudele/hal-568-hybrid-ceiling branch August 2, 2026 23:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant