#!/usr/bin/env python3 """Time and accuracy per recognition tool on formulas whose TeX is known (serial, one at a time). python3 tools/zeittest.py EPUB [--n 15] [--tools local,local2,cloud,p2t-cpu,p2t-gpu] [--gpu cuda:0] Same sample as `calibrate --consensus` (seed 42, real images). Every tool reads every formula alone, one request after the other, after one uncounted warm-up on another formula. Writes vergleich/zeittest/index.html (not versioned): table per tool, then per formula the original, the true formula and every reading set with pdflatex, with time and verdict; plus ergebnis.json. """ from __future__ import annotations import argparse import html import json import random import statistics import sys import time from pathlib import Path import numpy as np from PIL import Image sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from konsens_bericht import orig_img, png_uri # noqa: E402 from formula_img2latex.calibrate import _best_preset, corpus # noqa: E402 from formula_img2latex.consensus import same_reading # noqa: E402 from formula_img2latex.engines import make_engine # noqa: E402 from formula_img2latex.engines.p2t import Pix2TextEngine # noqa: E402 from formula_img2latex.render import render_many # noqa: E402 from formula_img2latex.tex import strip_delimiters # noqa: E402 LABELS = {"local": "Qwen3-VL-30B (lokal, GPU)", "local2": "Gemma-4-26B (lokal, GPU)", "cloud": "GLM-4.6V (Cloud)", "p2t-cpu": "pix2text MFR 1.5 (CPU)", "p2t-gpu": "pix2text MFR 1.5 (GPU)"} def engine_for(tool: str, gpu: str): if tool == "p2t-cpu": return Pix2TextEngine(device="cpu") if tool == "p2t-gpu": return Pix2TextEngine(device=gpu) return make_engine(tool) def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("epub", type=Path) ap.add_argument("--n", type=int, default=15) ap.add_argument("--tools", default="local,local2,cloud,p2t-cpu,p2t-gpu") ap.add_argument("--gpu", default="cuda", help="Gerät für p2t-gpu (Index relativ zu CUDA_VISIBLE_DEVICES)") ap.add_argument("--name", default="zeittest") args = ap.parse_args() tools = [t.strip() for t in args.tools.split(",") if t.strip()] items = corpus(args.epub) smp = random.Random(42).sample(items, min(150, len(items))) # as calibrate_consensus formulas, warm = smp[:args.n], smp[args.n] preset = _best_preset(smp[:12]) runs: dict[str, dict] = {} for tool in tools: eng = engine_for(tool, args.gpu) t0 = time.time() eng.check() load = time.time() - t0 eng.recognize(warm[1], warm[0], "", warm[3]) # warm-up, not counted reads = [] for name, data, truth, disp in formulas: r = eng.recognize(data, name, "", disp) ok = bool(r.tex) and bool(same_reading(r.tex, truth, disp, preset)) reads.append({"tex": r.tex, "seconds": r.seconds, "ok": ok, "error": r.error, "tokens_in": r.tokens_in, "tokens_out": r.tokens_out}) print(f"{tool:8s} {r.seconds:6.2f}s {'✓' if ok else '✗'} {r.tex or r.error}", file=sys.stderr, flush=True) secs = [x["seconds"] for x in reads] runs[tool] = {"label": LABELS.get(tool, tool), "model": eng.model, "load_s": load, "reads": reads, "mean_s": statistics.mean(secs), "median_s": statistics.median(secs), "correct": sum(x["ok"] for x in reads), "errors": sum(bool(x["error"]) for x in reads), "tokens_in": sum(x["tokens_in"] for x in reads), "tokens_out": sum(x["tokens_out"] for x in reads)} out = Path(__file__).resolve().parents[1] / "vergleich" / args.name out.mkdir(parents=True, exist_ok=True) (out / "ergebnis.json").write_text(json.dumps( {"epub": args.epub.name, "preset": preset, "truth": [f[2] for f in formulas], "runs": runs}, ensure_ascii=False, indent=1), encoding="utf-8") n = len(formulas) rows = "".join( f"
{n} Formeln mit bekanntem TeX (alt-Text), jedes Tool seriell, eine Formel nach der anderen, " "nach einem nicht gezählten Aufwärmlauf. Alle Formeln mit pdflatex gesetzt. ✓ = gleiche Zeichen an " "gleicher Stelle wie die wahre Formel (Abstände, kursiv/aufrecht, Fettdruck egal).
", "| Tool | Ø s/Formel | Median s | richtig | " f"Laden s | Tokens |
|---|
{html.escape(truth)}{html.escape(r['tex'] or r['error'] or '–')}