from __future__ import annotations
import json
import sys
import time
from pathlib import Path
sys.path.insert(0, "/mnt/c/Users/RyanJ/spectre-rs/scripts")
from bench_markdown_heading_f1 import ( normalize_heading,
headings_from_markdown,
prec_recall_f1,
)
def headings_from_toc(toc):
out = set()
for entry in toc:
if hasattr(entry, "title"):
title = entry.title
elif isinstance(entry, dict):
title = entry.get("title", "")
else:
title = entry[1] if len(entry) > 1 else ""
h = normalize_heading(title or "")
if h and len(h) > 1:
out.add(h)
return out
import spectre_rs
import pymupdf4llm
CACHE_PATH = Path("/tmp/spectre_extra_corpus/pmu4llm_cache.json")
CORPUS = [
("i1040gi", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/markdown/i1040gi.pdf"),
("p17", "/tmp/spectre_extra_corpus/p17.pdf"),
("p334", "/tmp/spectre_extra_corpus/p334.pdf"),
("p550", "/tmp/spectre_extra_corpus/p550.pdf"),
("p525", "/tmp/spectre_extra_corpus/p525.pdf"),
("p590a", "/tmp/spectre_extra_corpus/p590a.pdf"),
("p15", "/tmp/spectre_extra_corpus/p15.pdf"),
("p463", "/tmp/spectre_extra_corpus/p463.pdf"),
("freeculture", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/pdfjs/pdf.js-master/test/pdfs/freeculture.pdf"),
("arxiv_lora", "/tmp/spectre_extra_corpus/2106.09685.pdf"),
("arxiv_mineru", "/tmp/spectre_extra_corpus/2409.18839.pdf"),
("arxiv_tot", "/tmp/spectre_extra_corpus/2305.10601.pdf"),
("arxiv_mistral", "/tmp/spectre_extra_corpus/2310.06825.pdf"),
("issue10900", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/pdfjs/pdf.js-master/test/pdfs/issue10900.pdf"),
("xfa_imm1344e", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/pdfjs/pdf.js-master/test/pdfs/xfa_filled_imm1344e.pdf"),
("file_pdfjs", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/pdfjs/pdf.js-master/test/pdfs/file_pdfjs_test.pdf"),
("labelled_pages","/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/pdfjs/pdf.js-master/test/pdfs/labelled_pages.pdf"),
("nested_outline","/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/pdfjs/pdf.js-master/test/pdfs/nested_outline.pdf"),
("icdar_us008", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/ICDAR_2013_table_evaluate-master/icdar2013-competition-dataset-with-gt/pdf/us-008.pdf"),
("icdar_us036", "/mnt/c/Users/RyanJ/spectre-rs/bench-corpus/ICDAR_2013_table_evaluate-master/icdar2013-competition-dataset-with-gt/pdf/us-036.pdf"),
("p1", "/tmp/spectre_extra_corpus/p1.pdf"),
("p3", "/tmp/spectre_extra_corpus/p3.pdf"),
("p15a", "/tmp/spectre_extra_corpus/p15a.pdf"),
("p15b", "/tmp/spectre_extra_corpus/p15b.pdf"),
("p225", "/tmp/spectre_extra_corpus/p225.pdf"),
("p501", "/tmp/spectre_extra_corpus/p501.pdf"),
("p502", "/tmp/spectre_extra_corpus/p502.pdf"),
("p503", "/tmp/spectre_extra_corpus/p503.pdf"),
("p504", "/tmp/spectre_extra_corpus/p504.pdf"),
("p505", "/tmp/spectre_extra_corpus/p505.pdf"),
("p514", "/tmp/spectre_extra_corpus/p514.pdf"),
("p526", "/tmp/spectre_extra_corpus/p526.pdf"),
("p527", "/tmp/spectre_extra_corpus/p527.pdf"),
("p535", "/tmp/spectre_extra_corpus/p535.pdf"),
("p536", "/tmp/spectre_extra_corpus/p536.pdf"),
("p544", "/tmp/spectre_extra_corpus/p544.pdf"),
("p551", "/tmp/spectre_extra_corpus/p551.pdf"),
("p554", "/tmp/spectre_extra_corpus/p554.pdf"),
("p556", "/tmp/spectre_extra_corpus/p556.pdf"),
("p559", "/tmp/spectre_extra_corpus/p559.pdf"),
("p560", "/tmp/spectre_extra_corpus/p560.pdf"),
("p575", "/tmp/spectre_extra_corpus/p575.pdf"),
("p590b", "/tmp/spectre_extra_corpus/p590b.pdf"),
("p721", "/tmp/spectre_extra_corpus/p721.pdf"),
("p908", "/tmp/spectre_extra_corpus/p908.pdf"),
("p915", "/tmp/spectre_extra_corpus/p915.pdf"),
("p925", "/tmp/spectre_extra_corpus/p925.pdf"),
("p926", "/tmp/spectre_extra_corpus/p926.pdf"),
("p936", "/tmp/spectre_extra_corpus/p936.pdf"),
("p946", "/tmp/spectre_extra_corpus/p946.pdf"),
("p969", "/tmp/spectre_extra_corpus/p969.pdf"),
("p970", "/tmp/spectre_extra_corpus/p970.pdf"),
("p974", "/tmp/spectre_extra_corpus/p974.pdf"),
]
def load_cache() -> dict:
if CACHE_PATH.exists():
return json.loads(CACHE_PATH.read_text())
return {}
def save_cache(c: dict) -> None:
CACHE_PATH.write_text(json.dumps(c, indent=2))
def main() -> None:
cache = load_cache()
rows = []
t_total = time.time()
for label, path in CORPUS:
p = Path(path)
if not p.exists():
print(f"MISS {label} {path}", file=sys.stderr)
continue
b = p.read_bytes()
try:
d = spectre_rs.Document(b)
toc = d.toc()
except Exception as e:
print(f"FAIL {label}: spectre load — {e}", file=sys.stderr)
continue
gt = headings_from_toc(toc)
if not gt:
print(f"SKIP {label} (no usable TOC)", file=sys.stderr)
continue
t0 = time.time()
spec_md = d.markdown()
spec_t = time.time() - t0
spec_h = headings_from_markdown(spec_md)
s_p, s_r, s_f1 = prec_recall_f1(spec_h, gt)
key = f"{path}|{p.stat().st_mtime_ns}"
if key in cache:
entry = cache[key]
pmu_t = entry.get("seconds", -1.0)
pmu_h_count = entry["pmu_h_count"]
p_p, p_r, p_f1 = entry["p"], entry["r"], entry["f1"]
cached = True
else:
t0 = time.time()
pmu_md = pymupdf4llm.to_markdown(path)
pmu_t = time.time() - t0
pmu_h = headings_from_markdown(pmu_md)
p_p, p_r, p_f1 = prec_recall_f1(pmu_h, gt)
pmu_h_count = len(pmu_h)
cache[key] = {
"seconds": pmu_t,
"pmu_h_count": pmu_h_count,
"p": p_p, "r": p_r, "f1": p_f1,
}
save_cache(cache)
cached = False
row = {
"label": label,
"toc": len(gt),
"spec_h": len(spec_h),
"pmu_h": pmu_h_count,
"spec_p": s_p, "spec_r": s_r, "spec_f1": s_f1,
"pmu_p": p_p, "pmu_r": p_r, "pmu_f1": p_f1,
"spec_seconds": spec_t,
"pmu_seconds": pmu_t,
"pmu_cached": cached,
}
rows.append(row)
print(f"OK {label:18s} TOC={len(gt):4d} "
f"spec F1={s_f1:.2f} ({spec_t:5.2f}s) "
f"pmu F1={p_f1:.2f} ({'cache' if cached else f'{pmu_t:5.1f}s'})",
file=sys.stderr)
out = {"rows": rows, "wall_seconds": time.time() - t_total}
print(json.dumps(out, indent=2))
if __name__ == "__main__":
main()