formula_img2latex/README.md
dschlueter 4638a12571 feat: 0.3.0 – pix2text zuerst, Qwen bestätigt, Gemma nur bei Uneinigkeit
Neue Standardregel p2t (GLM/Cloud wird nicht mehr benutzt):
- pix2text liest zuerst; übernommen wird immer seine Lesart, wenn Qwen sie
  bestätigt oder – nur bei Uneinigkeit gefragt – Gemma. Qwen = Gemma gegen
  pix2text geht an einen Menschen. Schlusspunkt muss bestätigt sein; Bild-Veto
  und Pandoc auf pix2texts Lesart. Alte Regel als consensus_rule=vlm3 wählbar.
- pix2text auf der GPU (onnxruntime-gpu, FORMULA_P2T_DEVICE=auto/cpu/cuda:N)
- Ausgabe bereinigt: post_process aus pix2tex, Fettschreibweisen \boldmath/\bf
  -> \boldsymbol/\mathbf; an 400 Lesarten gleiches gesetztes Bild
- Normalform: \mathop, \begin{array}[t]
- calibrate: --engines, Regel-Tabelle, --reuse; wahre Formeln aus dem alt-Text
  bereinigt (aligned, \mathop{}\!, upgreek)
- tools/zeittest.py, tools/regeltest.py (--reuse, --exclude)

Messung: Göllmann Band 1 (2 x 150) 86 % / 85 % automatisch, 0 falsch
(bisher 78 % / 70 %); Band 2, zwei Stichproben zu 50: 78 % und 86 %, 0 falsch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:45:18 +02:00

15 KiB
Raw Blame History

formula_img2latex

Nachbearbeitungs-Werkzeug für die Markdown-Ausgaben von epub2md und pdf2md: Formeln, die nur als Bild vorliegen, werden durch LaTeX ersetzt – aber nur, wenn zwei unabhängige Erkenner dieselbe Formel lesen (zuerst das Formel-OCR pix2text, bestätigt vom Bildmodell Qwen; bei Uneinigkeit darf Gemma pix2text bestätigen) und ein grober Bildabgleich nicht widerspricht. Alles andere geht in eine Review-Galerie oder bleibt Bild.

Schritt-für-Schritt-Anleitung: BEDIENUNGSANLEITUNG.md. Änderungen je Version: CHANGELOG.md. Hintergrund und ursprüngliche Planung: Idee/.

Annahmeregel

Jedes Zeichen muss stimmen und an der richtigen Stelle relativ zu den anderen stehen. Abstände, Pfeil- und Bruchstrichlängen, Schriftgröße, kursiv/aufrecht und Fettdruck dürfen abweichen.

Eine Formel wird automatisch ersetzt (Regel p2t, Standard), wenn

  1. pix2text (Formel-OCR MFR 1.5, lokal, GPU) sie zuerst liest und Qwen3-VL-30B-A3B (Bildmodell, lokal, llama.cpp) dieselbe Formel liest oder, wenn die beiden uneinig sind, Gemma-4-26B-A4B (lokal) die Lesart von pix2text bestätigt. Übernommen wird immer pix2texts Lesart – sie trifft den Fettdruck am besten. Ihre überflüssigen Leerzeichen entfernt vorher post_process aus pix2tex (LaTeX-OCR): \frac{\alpha} {3 6 0} → \frac{\alpha}{360}; am gesetzten Bild ändert das nichts, geprüft an 400 Lesarten. Auch der Schlusspunkt muss bestätigt sein,
  2. Pandoc sie nach MathML umsetzen kann (sonst bliebe sie im EPUB/PDF roher TeX-Text) und
  3. das gerenderte LaTeX dem Original grob ähnelt (Bild-Veto, elastischer Vergleich, Ähnlichkeit ≥ 0,65).

Sind Qwen und Gemma gemeinsam gegen pix2text einig, entscheidet ein Mensch: Genau das sind die Fehler, die beide Bildmodelle teilen (\mathcal{E}_r statt \varepsilon_r, erfundenes \tilde{z}).

Die frühere Regel vlm3 (Qwen = Gemma, bestätigt von GLM-4.6V über OpenRouter) bleibt wählbar: "consensus_rule": "vlm3" unter settings in workspace.json des Arbeitsordners.

„Dieselbe Formel“ prüft consensus.py: gleiche Normalform (Abstände, \left/\right, überflüssige Klammern, \vec/\overrightarrow, \frac/\dfrac, \iff/\Leftrightarrow, pmatrix/\left(\begin{matrix}, TeX-Umlaute, \atop/\substack, kursiv/aufrecht, fett/normal …) oder – als Rückfallebene – zeichengleiches Rendering der beiden Lesarten (Render gegen Render, nicht gegen das verrauschte Original). Eine Gleichungsnummer muss in allen Lesarten übereinstimmen und wird als Text hinter die Formel gesetzt.

Sind sich die Modelle uneinig oder widerspricht das Veto, entscheidet ein Mensch im Review.

Messung

Regel p2t (Standard)

Göllmann Band 1, 150 echte Bilder und 150 Nachbildungen im Gehrke-Stil, Lesarten aller Modelle:

Regel echt: automatisch / falsch Gehrke-Stil: automatisch / falsch
pix2text = Qwen 81 % / 0 76 % / 0
+ Stichwahl: Gemma bestätigt pix2text, Qwens Lesart (erste Fassung) 85 % / 0 80 % / 0
pix2text zuerst und übernommen, bereinigt, Stichwahl Gemma (Standard) 86 % / 0 85 % / 0
+ Stichwahl auch Qwen = Gemma gegen pix2text 92 % / 1 91 % / 2
Qwen = Gemma = GLM (Regel vlm3) 78 % / 0 70 % / 0

Allein richtig: pix2text 90 % / 85 %, Qwen 86 % / 84 %, Gemma 86 % / 80 %, GLM 86 % / 83 %. Zeit je Formel (seriell, 15 Formeln): pix2text 0,10 s (GPU) bzw. 0,51 s (CPU), Qwen 0,72 s, GLM 1,19 s, Gemma 1,77 s. Gegenprobe an 50 frischen Formeln aus Göllmann Band 2 (tools/regeltest.py): 37 automatisch (74 %), 0 falsch, 13 Review; Gemma wurde für 16 Formeln gefragt.

Regel vlm3 (Standard bis 0.2.0)

An Formelbildern mit bekanntem TeX (Göllmann, Springer: TeX im alt-Text), je 150 echte Bilder und 150 Nachbildungen im Stil von Gehrke (LaTeX + dvipng mit Bitmap-Schriften, ~102 dpi, JPEG):

Regel echt: automatisch / davon falsch Gehrke-Stil: automatisch / davon falsch
Qwen = Gemma 73 % / 1 69 % / 2
Qwen = Gemma = Gemini 2.5 Flash 67 % / 0 57 % / 1
Qwen = Gemma = GLM-4.6V ohne Nachdenken (Standard) 68 % / 0 63 % / 0
  • Fehler, bei denen zwei Modelle gemeinsam irren, gibt es: \mathcal{E}_r statt \varepsilon_r, ein erfundenes \tilde{z}, x^{f} statt x'. Die dritte Lesart aus einer anderen Modellfamilie fängt sie ab; Gemini (mit Gemma verwandt) machte einen davon mit.
  • Selbstauskünfte der Modelle („95 % sicher“) sind wertlos. Token-Wahrscheinlichkeiten (Logprobs) werden gespeichert, brachten aber neben der dritten Lesart keinen Gewinn (Schwellen bleiben einstellbar).
  • „Nachdenken“ (Reasoning) ist für alle Modelle abgeschaltet: Gemma 4 wird ~10× schneller, GLM-4.6V liest ohne Nachdenken sogar besser (85 % statt 79 % allein richtig) und braucht ~25× weniger Tokens.
  • Fettdruck lesen alle Modelle schlecht (fette Vektoren werden zu normalen Buchstaben); nach Vorgabe zählt er nicht. Mit bold_relevant=1 würden diese Fälle ins Review gehen.
  • Das Bild-Veto fing den einzigen Fall ab, in dem alle drei Modelle gemeinsam irrten (Ähnlichkeit 0,5–0,6); richtige Lesarten lagen bei ≥ 0,66. Daher die Schwelle 0,65. Eine Prüfung der Schriftgröße (Pixel pro Punkt gegen das Buchmittel) erzeugte nur Fehlalarme und ist abgeschaltet.
  • Die Messung lief ohne Textausschnitt um die Formel; run gibt den Modellen einen kurzen Textausschnitt (≤ 240 Zeichen) mit. Die Stichproben am Buch (62 von 62 korrekt) sprechen nicht gegen den Kontext, gemessen ist er aber nicht.

Gehrke, Brückenkurs Mathematik (EPUB, 1505 Formelbilder): siehe Stand.

Ablauf

scan        Bildverweise in chapters/*.md (bzw. pdf2md-Markdown) finden, Formel/Abbildung vorklassifizieren
run         in Paketen zu 50: pix2text, dann Qwen lesen, prüfen, Gemma nur für Uneinige, prüfen;
            nach jedem Paket eine Statuszeile (Regel vlm3: Qwen + Gemma, GLM nur für Einige)
review      Zweifelsfälle im Browser: Original / Render / Overlay / Diff, LaTeX korrigieren und neu prüfen
apply       angenommene Formeln ins Markdown schreiben (aus einer Sicherung, idempotent)
revert      Markdown-Dateien auf den Stand vor apply zurücksetzen
recognize   einzelne Engine (local, local2, cloud, p2t) laufen lassen
verify      prüfen und entscheiden (ohne neue Modellabfragen)
status      Zählung je Status
calibrate   Messung an Formelbildern mit bekanntem TeX (--consensus: Konsensregel)
python3 formula_img2latex.py run    output/buch       # epub2md-Ausgabeordner
python3 formula_img2latex.py review output/buch
python3 formula_img2latex.py apply  output/buch
python3 epub2md_merge.py output/buch --force          # Gesamtdatei danach neu bauen

Status je Formel: auto_accepted, needs_review, rejected_keep_image, render_failed, no_candidate, not_formula, nach Review accepted_by_human, corrected_by_human, kept_by_human.

Modelle und Server

Engine Standard Konfiguration
local llama.cpp http://127.0.0.1:8011/v1 (Qwen3-VL-30B-A3B-Instruct Q4_K_M + mmproj) FORMULA_LOCAL_URL, FORMULA_LOCAL_MODEL
local2 llama.cpp http://127.0.0.1:8012/v1 (Gemma-4-26B-A4B-it Q4_0 + mmproj) FORMULA_LOCAL2_URL, FORMULA_LOCAL2_MODEL
cloud OpenRouter, z-ai/glm-4.6v (nur Regel vlm3) OPENROUTER_API_KEY, FORMULA_CLOUD_MODEL
p2t pix2text MFR 1.5 (ONNX) im Prozess, erste sichtbare GPU, sonst CPU FORMULA_P2T_DEVICE (auto, cpu, cuda:N), FORMULA_P2T_MODEL

pix2text auf der GPU braucht onnxruntime-gpu statt onnxruntime (cuDNN kommt aus den nvidia-Paketen von torch). Für post_process wird pix2tex ohne seine Abhängigkeiten installiert (pip install --no-deps pix2tex munch), weil es sonst timm==0.5.4 erzwingen würde. cuda:N zählt innerhalb von CUDA_VISIBLE_DEVICES.

FORMULA_<ENGINE>_THINKING=1 schaltet das Nachdenken wieder ein. Antworten sind auf 800 Tokens begrenzt (lange mehrzeilige Formeln brauchen ~400; einzelne Antworten von Gemma liefen sonst in Wiederholungsschleifen bis 165 s). Jede Lesart wird mit Modell, Prompt-Version und Nachdenk-Einstellung gespeichert; dieselbe Frage wird nie zweimal gestellt, ein abgebrochener Lauf setzt dort fort.

Kosten der Cloud-Stufe (nur Regel vlm3): GLM liest nur Formeln, bei denen Qwen und Gemma einig sind. Gehrke: 967 Anfragen, Ø 2,6 s, zusammen rund 0,15–0,20 $.

Bildvergleich (Veto)

compare.py rendert die Lesart mit pdflatex (Schrift je Buch automatisch: cm, times, txfonts, palatino, stix, euler), richtet sie auf das Original aus und verzerrt sie elastisch (dynamische Zeitverzerrung über Spalten und Zeilen; Dehnen kostet proportional zur Tinte), damit Abstände und Längen keine Rolle spielen. Die Ähnlichkeit danach ist das Veto. Geprüft wird nur die Lesart, die übernommen werden kann: die von pix2text (Regel vlm3: die von Qwen). Bestätigende Lesarten werden nie übernommen und brauchen keine Prüfung. Die Prüfergebnisse tragen eine Versionsnummer des Verfahrens und werden nach Änderungen neu berechnet. Der alte Pixel-Entscheider mit Verwechslungsvarianten und Kontrasttest bleibt als Modus decide=pixel erhalten.

Sicherheit: Kandidaten kommen von Modellen und gelten als unsicher. Vor pdflatex werden Befehle wie \input, \write, \def, \catcode abgelehnt; pdflatex läuft mit -no-shell-escape, openout_any=p, Zeitlimit.

Arbeitsordner

<ausgabe>.formulas/          # neben der Ausgabe, weil epub2md --force die Ausgabe ersetzt
├── workspace.json           # Quelle, Schrift, Einstellungen, apply-Zustand
├── formulas/f-<sha12>-<d|i>.json   # je Bild und Darstellung: Lesarten, Prüfung, Entscheidung
├── review/<id>-{orig,render,rigid,diff}.png
└── backup/                  # Markdown-Dateien vor dem ersten apply

Die Datensätze hängen am SHA-256 des Bildes: Ein erneuter epub2md-Lauf verliert keine Entscheidungen.

Einstellungen in workspace.json → settings.consensus_thresholds: require_third (1), bold_relevant (0), veto_ncc (0,65), veto_scale_dev (0 = aus), min_p_mean/min_p_min (0).

Quellen

Quelle Erkennung Begrenzer
epub2md-Ordner conversion-report.json; Warnungen formula-image markieren sichere Formeln --math auto: $…$/$$…$$, --math tex: \(…\)/\[…\]; deutsche Bücher: 3{,}6
pdf2md (<name>.md + <name>_images/ oder --split-Ordner) Bildname <pdf>-<seite>-<box>; mit --pdf per Template-Matching auf der Seite gefunden und mit 600 dpi aus dem Vektor-PDF neu ausgeschnitten $…$/$$…$$

Codeblöcke werden nie verändert. Inline-Formeln, die pdf2md als Glyphensalat im Fließtext ausgibt, sind nicht Teil dieser Version.

Werkzeuge zum Einstellen

  • tools/vergleich.py AUSGABE – feste Stichprobe (50 Formeln), verarbeitet wie run, schreibt vergleich/<name>/index.html mit Original, gerenderten Lesarten, Entscheidung und Veto-Overlay.
  • tools/konsens_bericht.py BERICHT.json EPUB – Übersichtsseite zu calibrate --consensus --report.
  • tools/zeittest.py EPUB – Zeit und Trefferquote je Tool, seriell an 15 Formeln mit bekanntem TeX.
  • tools/regeltest.py EPUB [--n 50] [--reuse ergebnis.json] – die Standardregel an Formeln mit bekanntem TeX, wie run; Seite mit Original, wahrer Formel, allen Lesarten und Entscheidung.
  • calibrate --consensus --engines local,local2,cloud,p2t --report X.json misst alle Regeln; --reuse X.json rechnet sie ohne Modellabfragen neu.
  • vergleich/ ist nicht versioniert (Ausschnitte aus Büchern).

Stand

Version 0.2.0 (Konsens), getestet am Gehrke, Brückenkurs Mathematik (EPUB, CM-Formeln als kleine JPGs):

Anzahl
Formelbilder verarbeitet (sicher oder unklar klassifiziert) 1505
automatisch ersetzt 942 (63 %)
Review: Qwen und Gemma uneinig 410
Review: GLM weicht ab 65
Review: Bild-Veto / Pandoc 2 / 1
keine Formel (laut Modellen) 85
nicht geprüft (Abbildungen, Symbole) 539
  • Von Hand geprüft: die 42 automatisch ersetzten Formeln der festen 50er-Stichprobe und 20 zufällig gezogene weitere – 62 von 62 Zeichen für Zeichen korrekt (Brüche, Wurzeln mit Index, cases, Grenzwerte, Integrale, Vektorpfeile, Überstriche über mehreren Buchstaben).
  • apply an einer Kopie: 946 Ersetzungen in 258 Kapiteldateien (manche Formeln kommen mehrfach vor). epub2md_merge.py → 945 Formeln in der Gesamtdatei (eine stand in einer doppelten Fußnote, die das Zusammenfügen entfernt). Pandoc --mathml: 945 von 945 als MathML, keine „Could not convert TeX math“-Meldung. EPUB daraus in 10 s gebaut.
  • Laufzeit mit beiden lokalen Modellen parallel (zwei RTX 3090) und GLM: ~5 Minuten je 50 Formeln, das Buch also rund 2,5 Stunden.

Grenzen und offene Punkte:

  • Mehrzeilige Rechnungen mit Ausrichtung, \underbrace oder Zeilenumbrüchen schreiben die Modelle unterschiedlich – sie bilden den Großteil des Reviews.
  • Formeln auf farbig hinterlegten Kästen verlieren beim Ersetzen die Hinterlegung.
  • Fettdruck wird nicht geprüft (Vorgabe).
  • Die Messung umfasst 2 × 150 Formeln eines Buches ohne Textausschnitt; „0 Fehler“ bedeutet eine Fehlerquote sehr wahrscheinlich unter ~2 %.
  • pdf2md-Bücher: der Ablauf ist umgesetzt (600-dpi-Neuausschnitt, Euler-Schrift für Hefferon), aber im Konsens-Modus noch nicht an einem ganzen Buch getestet.

Installation

sudo apt install texlive-latex-extra texlive-fonts-extra texlive-science pandoc dvipng
pip install -r requirements.txt     # pymupdf, numpy, scipy, scikit-image, opencv, Pillow, lxml, requests
pip install -r requirements-dev.txt # pytest

Modelle: siehe BEDIENUNGSANLEITUNG (llama-server starten).

Tests

python3 -m pytest -q     # ca. 45 s; synthetische Bilder, kein Modellserver, keine Buchinhalte

Rückgabecodes: 0 Erfolg, 1 Bedienfehler, 2 Eingabe ist keine epub2md-/pdf2md-Ausgabe, 3 externes Werkzeug fehlt (pdflatex, pandoc, Modellserver), 4 interner Fehler.

Architektur

formula_img2latex/
├── cli.py          Unterbefehle, Exit-Codes
├── pipeline.py     scan, recognize, verify, Entscheidung (Konsens/Pixel), run in Paketen, apply/revert
├── consensus.py    Normalform, Render-Gleichheit: sagen zwei Lesarten dasselbe?
├── decide.py       Annahmeregeln und Schwellen (Konsens- und Pixel-Modus)
├── engines/        Vision-Modelle über die OpenAI-API (llama.cpp, OpenRouter), Logprobs, Prompt-Version;
│                   p2t.py: pix2text-Formel-OCR (ONNX, CPU/GPU)
├── compare.py      Ausrichtung, elastische Verzerrung, Kennzahlen, Kontrasttest, Diff-Bilder
├── confusables.py  Verwechslungsvarianten (Pixel-Modus)
├── verify.py       Bildprüfung einer Lesart (Veto bzw. Pixel-Entscheider)
├── render.py       pdflatex-Batch, Schrift-Presets, Rasterung
├── imageio.py      Originale laden (PNG/GIF/JPG/SVG, Transparenz, Tönung)
├── sources/        epub2md.py, pdf2md.py, base.py (Markdown-Scan, Begrenzer, Dezimalkomma)
├── pdfcrop.py      Formelbild auf der PDF-Seite finden, 600-dpi-Neuausschnitt
├── classify.py     Formel / unklar / Abbildung
├── checks.py       Pandoc/MathML-Prüfung
├── calibrate.py    Messung an Formelbildern mit bekanntem TeX
├── store.py        Arbeitsordner, Datensätze
└── review/         lokaler Review-Server + Seite