This commit is contained in:
root
2026-09-20 14:16:41 +02:00
parent 0798933b05
commit b6d1909d7f
56 changed files with 3752 additions and 1989 deletions
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""Measure how legible each page of the photocopied manual is.
check_legibility.py copied.pdf [crisp.pdf]
OCRs every page (tesseract) and scores it against the PDF's own invisible text
layer, which is the known-correct text: the score is the share of true words
that OCR recovers. If a crisp render is given, its score is the baseline (OCR
is imperfect on monospace tables), and a page is judged by how much the copying
cost it. Needs tesseract and poppler's pdftotext/pdftoppm. Development tool;
the build does not run it.
"""
import difflib
import os
import re
import subprocess
import sys
import tempfile
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
def words(text):
return re.findall(r"[A-Za-z0-9][A-Za-z0-9'.\-]*", text)
def ocr_scores(pdf, dpi=130):
"""Return {page: (recall, truth_word_count)} for each page of pdf."""
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
subprocess.run(["pdftoppm", "-r", str(dpi), "-gray", "-png", pdf, str(tmp / "p")], check=True)
images = sorted(tmp.glob("p-*.png"))
def score(img):
n = int(img.stem.split("-")[1])
truth = words(subprocess.run(["pdftotext", "-f", str(n), "-l", str(n), pdf, "-"],
capture_output=True, text=True, check=True).stdout)
got = words(subprocess.run(["tesseract", str(img), "-", "--psm", "6"],
capture_output=True, text=True, env={**os.environ, "OMP_THREAD_LIMIT": "1"}).stdout)
m = difflib.SequenceMatcher(None, truth, got, autojunk=False)
hit = sum(b.size for b in m.get_matching_blocks())
return n, (hit / max(1, len(truth)), len(truth))
with ThreadPoolExecutor(4) as pool:
return dict(pool.map(score, images))
if __name__ == "__main__":
copied = ocr_scores(sys.argv[1])
crisp = ocr_scores(sys.argv[2]) if len(sys.argv) > 2 else {}
print("page words crisp copied loss")
losses = []
for n in sorted(copied):
c, w = copied[n]
b = crisp.get(n, (1.0, w))[0]
losses.append(b - c)
flag = " <-- check" if b - c > 0.10 else ""
print(f"{n:4d} {w:6d} {b:5.0%} {c:5.0%} {b - c:+5.0%}{flag}")
tot = sum(w for _, w in copied.values())
print(f"\nmean copied recall {sum(c * w for c, w in copied.values()) / tot:.1%}"
+ (f", mean crisp {sum(c * w for c, w in crisp.values()) / tot:.1%}" if crisp else ""))