formula_img2latex/FUNKTIONSWEISE.md
dschlueter 6a0e9587f5 docs: Dokumentation nach Projektstandard, MIT-Lizenz
- README knapp: Zweck, Schnellstart, Stand, Abschnitt "Documentation", Lizenz
- FUNKTIONSWEISE.md neu: Aufbau, Datenfluss (Mermaid), Entscheidungsregel,
  Normalform, elastischer Bildvergleich, Quellen, Sicherheit, Messungen,
  Designentscheidungen, Grenzen
- BEDIENUNGSANLEITUNG gegliedert: Installation, Modellserver, Benutzung,
  Konfiguration (Umgebungsvariablen, workspace.json), Betrieb, Werkzeuge,
  Fehlersuche mit echten Meldungen und Rückgabecodes
- CHANGELOG im Format Keep a Changelog mit "Beim Update zu tun"
- LICENSE (MIT)
- cli.py: Hilfetexte beschreiben die Regel p2t und z-ai/glm-4.6v statt des
  Pixelvergleichs und gemini-2.5-flash
- requirements.txt: requests ergänzt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:26:49 +02:00

26 KiB
Raw Blame History

formula_img2latex – Funktionsweise

Dieses Dokument erklärt, wie formula_img2latex innen arbeitet und warum es so gebaut ist. Für die Benutzung siehe BEDIENUNGSANLEITUNG.md, für Änderungen je Version CHANGELOG.md.

Stand: Version 0.4.0 und unveröffentlichte Änderungen (siehe CHANGELOG, Abschnitt [Unreleased])


Inhaltsverzeichnis

  1. Überblick
  2. Module
  3. Arbeitsordner und Datensätze
  4. Ablauf von run
  5. Die Erkenner
  6. Entscheidungsregel
  7. Wann zwei Lesarten dieselbe Formel sind
  8. Bild-Veto: der elastische Bildvergleich
  9. apply und revert
  10. Quellen: epub2md und pdf2md
  11. Sicherheit
  12. Messungen
  13. Designentscheidungen
  14. Grenzen

1. Überblick

Ein Formelbild wird nur durch LaTeX ersetzt, wenn zwei verschiedenartige Erkenner dieselbe Formel lesen und ein grober Bildabgleich nicht widerspricht. Die Annahmeregel des Projekts:

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.

flowchart TB
    A["Markdown von<br/>epub2md oder pdf2md<br/>mit Formelbildern"] --> S["scan<br/>Bildverweise finden,<br/>Formel oder Abbildung?"]
    S --> R1["pix2text<br/>(Formel-OCR)<br/>liest jede Formel"]
    R1 --> R2["Qwen3-VL<br/>(Bildmodell)<br/>liest jede Formel"]
    R2 --> V["Bild-Veto<br/>pix2texts Lesart rendern<br/>und elastisch vergleichen"]
    V --> D{"Entscheidung<br/>pix2text = Qwen?"}
    D -->|"uneinig"| G["Gemma 4<br/>(zweites Bildmodell)<br/>Stichwahl"]
    G --> D2{"Gemma = pix2text?"}
    D -->|"einig, Veto und<br/>Pandoc bestanden"| OK["auto_accepted"]
    D2 -->|"ja"| OK
    D2 -->|"nein"| RV["needs_review<br/>Review-Galerie"]
    RV -->|"Mensch übernimmt<br/>oder korrigiert"| OK
    OK --> AP["apply<br/>Bildverweis im Markdown<br/>→ LaTeX-Formel"]

Übernommen wird immer die Lesart von pix2text; Qwen und Gemma bestätigen nur. Was nicht bestätigt ist, bleibt Bild, bis ein Mensch entscheidet.


2. Module

Modul Aufgabe Wichtigste Namen
cli.py Unterbefehle, Rückgabecodes main, build_parser
pipeline.py scan, recognize, verify, Entscheidung, run in Paketen, apply/revert scan, recognize, verify_all, decide_all_consensus, run_consensus, apply, revert
decide.py Annahmeregeln und Schwellen decide_pair_tiebreak (Regel p2t), decide_consensus (Regel vlm3), decide (Pixel-Modus), CONSENSUS_DEFAULTS
consensus.py Sagen zwei Lesarten dasselbe? canonical, same_reading, render_equal
structure.py Strukturvergleich mit sympy (optional, aus) structural_equal
engines/p2t.py pix2text (MFR 1.5, ONNX) im Prozess, gebündelt, CPU oder GPU Pix2TextEngine, tidy_p2t, resolve_device
engines/vlm.py Bildmodelle über die OpenAI-API (llama.cpp, OpenRouter), Logprobs VisionEngine, PROMPT, PROMPT_VERSION, prepare_image, parse_answer
verify.py Bildprüfung einer Lesart (Veto bzw. Pixel-Entscheider) verify, image_arbiter
compare.py Ausrichtung, elastische Verzerrung, Kennzahlen, Kontrast, Diff-Bilder compare, elastic_warp, contrast, diff_image
render.py pdflatex-Batch, Schrift-Presets, Rasterung render_many, PRESETS
confusables.py Verwechslungsvarianten (nur Pixel-Modus) variants, EQUIVALENT
tex.py TeX-Hilfen: Begrenzer, Sicherheitsprüfung, Gleichungsnummern unsafe_reason, split_tag, prepare_for_render, text_to_tex
imageio.py Originale laden: PNG/GIF/JPG/SVG, Transparenz, getönter Hintergrund load_ink, gray_to_ink, color_stats
classify.py Vorfilter: Formel, unklar oder Abbildung classify
checks.py Pandoc/MathML-Prüfung pandoc_mathml_ok
sources/ Quellen: Markdown-Scan, Begrenzer, Dezimalkomma; epub2md, pdf2md open_source, scan_markdown, Epub2MdSource, Pdf2MdSource
pdfcrop.py Box-Bild auf der PDF-Seite finden, 600-dpi-Neuausschnitt PdfCropper
store.py Arbeitsordner, ein JSON-Datensatz je Formel Workspace, Formula, STATUSES
calibrate.py Messung an Formelbildern mit bekanntem TeX calibrate, calibrate_consensus
review/ lokaler Review-Server und Seite serve
tools/ Mess- und Berichtswerkzeuge (nicht Teil des Pakets) vergleich.py, regeltest.py, zeittest.py, buch_bericht.py, konsens_bericht.py, vergleich_bericht.py

3. Arbeitsordner und Datensätze

<ausgabe>.formulas/
├── workspace.json                 # Quelle, Schrift, Einstellungen, letzter Scan, apply-Zustand
├── formulas/f-<sha12>-<d|i>.json  # ein Datensatz je Bild und Darstellung (d = abgesetzt, i = im Text)
├── crops/<id>.png                 # Original je Formel, einmal ausgeschnitten (PDF-Quellen)
├── review/<id>-{orig,render,rigid,diff}.png   # Bilder des Bildabgleichs
├── bericht/                       # Ergebnisseite (tools/buch_bericht.py)
└── backup/                        # Markdown-Dateien vor dem ersten apply

Der Arbeitsordner liegt neben der Ausgabe, weil epub2md --force die Ausgabe vollständig ersetzt.

Ein Datensatz (store.Formula) hält: Bildpfad und SHA-256, Darstellung (abgesetzt oder im Text), alle Fundstellen im Markdown (Datei, Zeile, genauer Bildverweis, Textausschnitt), die Vorklassifikation, alle Lesarten (je Engine, Modell, Prompt-Version und Nachdenk-Einstellung, mit Zeit und Token-Wahrscheinlichkeiten), das Ergebnis des Bildabgleichs, Status, Begründung und das endgültige LaTeX (final_tex, dazu final_suffix für ein abgetrenntes Satzzeichen).

Schlüssel und Zwischenspeicher:

Was Schlüssel Wirkung
Datensatz SHA-256 des Bildes + Darstellung ein erneuter epub2md-Lauf verliert keine Entscheidung
Lesart Engine, Modell, PROMPT_VERSION, Nachdenken dieselbe Frage wird nie zweimal gestellt; fehlgeschlagene Anfragen werden wiederholt
Bildabgleich VERIFY_VERSION + geprüfte Lesart nach einer Änderung am Vergleich wird neu gerechnet
Scan Größe und Zeitstempel aller Markdown-Dateien beim Fortsetzen entfällt der Scan, solange sich das Markdown nicht ändert

Status: pending, auto_accepted, needs_review, not_formula, rejected_keep_image, render_failed, no_candidate; nach dem Review accepted_by_human, corrected_by_human, kept_by_human. apply schreibt auto_accepted, accepted_by_human und corrected_by_human.


4. Ablauf von run

run verarbeitet die Formeln in Paketen zu 50 und meldet nach jedem Paket eine Statuszeile mit Zeiten je Stufe. Beide Stufen laufen gleichzeitig: Während die CPU Paket n gegen die Bilder prüft, lesen pix2text und Qwen auf der GPU schon Paket n+1.

flowchart TB
    subgraph GPU["GPU: Lesen (Paket n+1)"]
        direction TB
        P["pix2text<br/>gebündelt nach<br/>Pixelbudget"] --> Q["Qwen<br/>bis 8 Anfragen<br/>gleichzeitig"]
    end
    subgraph CPU["CPU: Prüfen (Paket n)"]
        direction TB
        V1["Bild-Veto<br/>Prozesspool"] --> E1["Entscheidung"]
        E1 --> T{"Stichwahl<br/>nötig?"}
        T -->|"ja"| G["Gemma<br/>(zweite GPU)"]
        G --> E2["Entscheidung"]
        T -->|"nein"| Z["Statuszeile"]
        E2 --> Z
    end
  • Scan (pipeline.scan): findet jeden Bildverweis außerhalb von Codeblöcken, fasst gleiche Bilder zusammen, klassifiziert (Farbe der Tinte, Graustufen, Höhe, Stellung im Text) und speichert. Abbildungen (figure) werden nicht gelesen, unklare (maybe) schon.
  • Lesen: pix2text liest jede Formel, Qwen ebenfalls. Gemma nur, wenn pix2text und Qwen uneinig sind.
  • Prüfen (verify_all): rendert nur die Lesart, die übernommen werden kann (pix2texts), und vergleicht sie mit dem Original. Die Prozesse starten per forkserver, weil der Lesefaden gleichzeitig in CUDA/pix2text arbeitet und ein fork mitten darin halb gehaltene Sperren erbt.
  • Entscheiden (decide_all_consensus): siehe 6.

Ein abgebrochener Lauf setzt beim nächsten run fort: Lesarten und Prüfergebnisse liegen im Arbeitsordner.


5. Die Erkenner

Engine Modell Art Rolle (Regel p2t)
p2t pix2text MFR 1.5 (ONNX) Formel-OCR, im Prozess liest zuerst, seine Lesart wird übernommen
local Qwen3-VL-30B-A3B-Instruct (llama.cpp, Port 8011) Bildmodell (MoE, ~3B aktiv) bestätigt
local2 Gemma-4-26B-A4B-it (llama.cpp, Port 8012) Bildmodell Stichwahl bei Uneinigkeit
cloud GLM-4.6V über OpenRouter Bildmodell nur Regel vlm3

pix2text (engines/p2t.py) läuft auf der sichtbaren GPU mit dem meisten freien Speicher (sonst CPU) und liest gebündelt. Seine Ausgabe wird bereinigt (tidy_p2t): überflüssige Leerzeichen entfernt post_process aus pix2tex (\frac{\alpha} {3 6 0} → \frac{\alpha}{360}), Fettschreibweisen wie \mathrm{\boldmath~ x ~} werden zu \boldsymbol{x}, \cfrac zu \dfrac (Pandoc kennt \cfrac nicht), eine Gleichungsnummer nach großem Abstand zu \tag{…}.

Bildmodelle (engines/vlm.py) bekommen das Bild vorbereitet (prepare_image): auf Weiß gelegt, kleine Formeln auf mindestens 96 px Höhe vergrößert, große auf höchstens 1600 px Kantenlänge verkleinert. Der Prompt verlangt eine buchstabengetreue Abschrift (ausdrücklich mit Fettdruck), \tag{} für Gleichungsnummern und eine JSON-Antwort; ein kurzer Textausschnitt um die Formel wird zur Orientierung mitgegeben. Weitere Festlegungen:

  • Nachdenken aus: Gemma 4 wird ~10× schneller, GLM liest ohne besser und billiger.
  • Antwortlänge höchstens 800 Tokens: lange mehrzeilige Formeln brauchen ~400, Endlosschleifen werden gekappt.
  • Token-Wahrscheinlichkeiten (Logprobs) des LaTeX-Teils werden gespeichert. Die Selbstauskunft („confidence“) wird erfragt, entscheidet aber nicht.
  • Robuste Auswertung: JSON mit einfachen Backslashes (\frac → Seitenvorschub + rac) wird erkannt und wörtlich neu gelesen; wörtliches \n in Antworten wird zu Leerraum.

6. Entscheidungsregel

Regel p2t (Standard, decide.decide_pair_tiebreak)

Lesarten: Qwen (0), pix2text (1), Gemma (2, nur bei Bedarf).

flowchart TB
    S["Qwen: keine Formel<br/>(und Gemma auch)?"] -->|"ja"| NF["not_formula"]
    S -->|"nein"| A{"pix2text = Qwen?"}
    A -->|"ja"| C["Prüfungen"]
    A -->|"nein"| B{"Gemma gefragt?"}
    B -->|"nein"| W["needs_review:<br/>Stichwahl fehlt<br/>→ Gemma fragen"]
    B -->|"ja"| G{"Gemma = pix2text?"}
    G -->|"ja"| C
    G -->|"nein, aber<br/>Gemma = Qwen"| H["needs_review:<br/>Mensch entscheidet"]
    G -->|"alle uneinig"| H
    C --> K{"Pandoc ok und<br/>Ähnlichkeit ≥ 0,65?"}
    K -->|"ja"| OK["auto_accepted<br/>(pix2texts Lesart)"]
    K -->|"nein"| R["needs_review"]
  • Übernommen wird immer pix2texts Lesart: Sie trifft den Fettdruck am besten.
  • Qwen und Gemma gemeinsam gegen pix2text gehen an einen Menschen. In der Messung waren genau das die Fehler, die beide Bildmodelle teilen (\mathcal{E}_r statt \varepsilon_r, ein erfundenes \tilde{z}). Der Bild-Schiedsrichter (arbiter, siehe 12) könnte diese Fälle entscheiden; er ist aus.
  • Satzzeichen: Bei abgesetzten Formeln muss auch der Schlusspunkt bestätigt sein. Bei Formeln im Text wird ein Satzzeichen am Ende vor dem Vergleich abgetrennt und als Text hinter die Formel gesetzt ($a^{x}$,), denn es gehört zum Satz.
  • PDF-Textebene (pdftext, aus): Bei pdf2md --formulas könnte der PDF-Text einer „flachen“ Formel (eine Grundlinie, eine Größe) Qwen ersetzen. Gemessen fehlten dort in 12 von 319 Fällen Punkte, Striche oder Indizes.

Regel vlm3 (bis 0.2.0, decide.decide_consensus)

Qwen = Gemma, bestätigt von GLM-4.6V aus einer anderen Modellfamilie; eine 2:1-Mehrheit reicht nicht. Übernommen wird Qwens Lesart; der bestätigenden Lesart darf der Schlusspunkt fehlen. Wählbar mit "consensus_rule": "vlm3" in workspace.json.

Pixel-Modus (bis 0.1.0, decide.decide)

Eine Lesart wird gerendert und muss jede Verwechslungsvariante (confusables.variants: andere Ziffer, hoch statt tief, ,/;, ähnliche Buchstaben …) im Bild deutlich schlagen. Sicher, aber an kleinen JPEG-Formeln zu streng. Wählbar mit "decide": "pixel".

Schwellen (settings.consensus_thresholds in workspace.json)

Schlüssel Standard Bedeutung
veto_ncc 0,65 Mindestähnlichkeit nach elastischer Ausrichtung
veto_scale_dev 0 (aus) erlaubte Abweichung der Schriftgröße vom Buchmittel
bold_relevant 0 1: fett und normal gelten als verschiedene Zeichen
require_third 1 Regel vlm3: dritte Lesart muss zustimmen
min_p_mean, min_p_min 0 Mindest-Token-Wahrscheinlichkeiten (Mittel, kleinste)
structural 0 Strukturvergleich mit sympy
arbiter, arbiter_margin 0, 5 Bild-Schiedsrichter bei „Qwen = Gemma gegen pix2text“
pdftext 0 PDF-Textebene darf pix2text bestätigen

7. Wann zwei Lesarten dieselbe Formel sind

consensus.same_reading prüft in dieser Reihenfolge:

  1. Normalform (canonical): Beide Lesarten werden in Tokens zerlegt und vereinheitlicht.
    • Entfällt: Abstände (\,, \quad …), \left/\right/\big, \displaystyle, aufrecht/kursiv (\mathrm, \text, \operatorname behalten nur ihren Inhalt), standardmäßig auch Fettdruck.
    • Synonyme: \vec = \overrightarrow, \frac = \dfrac = \cfrac, \le = \leq, \iff = \Leftrightarrow, \cdots = \dots, \root3\of{x} = \sqrt[3]{x}, \underset{…}{\lim} = \lim_{…}, TeX-Umlaute (f{\"u}r = für), \atop = \substack.
    • Umgebungen: pmatrix = \left(\begin{matrix}…\right), array = matrix. Mehrzeilige Formeln sind gleich, wenn sie dieselben Zeilen mit denselben Zeichen haben, egal ob aligned, gathered, split oder nur \\; cases = \left\{\begin{array}…\right.. In Matrizen trennt & weiter Spalten.
    • Klammergruppen: Eine Gruppe ist nur dann Argument, wenn der Befehl davor noch eines erwartet (\frac 2, \sqrt 1, \Delta 0). So wird t^{2}\,{\sqrt{2}} = t^2\sqrt{2}, aber \frac{a}{b+c} ≠ \frac{a}{b}+c.
    • Eine Gleichungsnummer ist Teil der Normalform: Alle Lesarten müssen dieselbe tragen.
  2. Strukturvergleich (structure.py, nur mit structural = 1): gleicher unausgewerteter sympy-Baum (\frac{a}{b} = a/b). Nichts wird umgerechnet (a+b ≠ b+a), Relationen gehen nie an sympy, jedes Zeichen ist durch einen Platzhalter geschützt.
  3. Render-Gleichheit (render_equal): Beide Lesarten werden mit pdflatex gesetzt und Pixel gegen Pixel verglichen (höchstens 0,2 % abweichende Tintenpixel). Das ist ein Vergleich Render gegen Render, exakt und rauschfrei. Haben die Normalformen verschiedene Buchstaben oder Ziffern, entfällt dieser Schritt.

8. Bild-Veto: der elastische Bildvergleich

Das Veto fängt Fehler ab, die mehrere Erkenner gemeinsam machen (weggelassener Term, mitgelesene Gleichungsnummer, Halluzination). Es entscheidet nicht über Kleinigkeiten.

  1. Rendern (render.py): pdflatex mit dem Paket preview, viele Formeln je Lauf. Formeln mit TeX-Fehlern werden über die Zeilennummern im Log erkannt und herausgenommen, der Rest neu übersetzt. Die Schrift wählt choose_preset je Buch an einer Stichprobe: cm, times, txfonts, palatino, stix oder euler.
  2. Normalisieren (imageio.gray_to_ink): Transparenz auf Weiß, helle Schrift auf dunklem Grund wird umgekehrt, die Tinte zwischen Papierton und dunkelstem Pixel gestreckt (ein getönter Kasten wird so zu Papier), JPEG-Rauschen unter einer Schwelle entfernt, zugeschnitten. Breite Originale werden auf 1600 px verkleinert, Ausschnitte über 2,5 MP nicht verglichen.
  3. Ausrichten (compare.py): gleichmäßig skalieren (nie strecken), auf Viertelpixel verschieben. Weichen die Proportionen mehr als 2,5-fach ab, gibt es keinen Vergleich.
  4. Elastisch verzerren (elastic_warp): dynamische Zeitverzerrung (DTW) über Spalten, dann Zeilen. Jede Originalspalte schiebt den Kandidaten um 0–3 Spalten weiter. Dehnen kostet proportional zur Tinte der Spalte, übersprungene Tinte ihren vollen Wert. Abstände und Pfeilschäfte dürfen sich so ändern, Zeichenformen praktisch nicht, und kein Zeichen kann verschwinden.
  5. Messen: Korrelation (NCC) nach der Verzerrung; das Veto verlangt ≥ 0,65.

Die Bilder (Original, starr und elastisch ausgerichtetes Render, farbiges Diff) landen in review/ und erscheinen in der Review-Galerie.


9. apply und revert

  • Sicherung: Das erste apply kopiert jede betroffene Markdown-Datei nach backup/. Jedes apply baut aus dieser Sicherung neu auf, ist also wiederholbar. Hat sich eine Datei seitdem geändert (neuer epub2md-Lauf), gilt sie als neues Original.
  • Ersetzen: Der genaue Bildverweis in der gespeicherten Zeile wird ersetzt: abgesetzt durch $$…$$, im Text durch $…$ (epub2md --math tex: \[…\] bzw. \(…\)).
  • Gleichungsnummer: Sie kommt nur aus der übernommenen Lesart und steht bei abgesetzten Formeln als \qquad\text{(3.7)} hinter der Formel, wie bei epub2md.
  • Deutsche Bücher: Dezimalzahlen werden zu 3{,}6, sonst setzt LaTeX „3, 6“.
  • revert spielt die Sicherung zurück, außer bei Dateien, die seit apply von Hand geändert wurden (mit --force auch diese).

10. Quellen: epub2md und pdf2md

Quelle Erkennung Original für den Vergleich
epub2md-Ordner conversion-report.json (tool: epub2md); Warnungen formula-image markieren sichere Formeln das extrahierte Bild
pdf2md (Box-Bilder) Bildname <pdf>-<seite>-<box>; mit --pdf per Template-Matching auf der Seite gefunden 600-dpi-Neuausschnitt aus dem Vektor-PDF (pdfcrop.py), einmal in crops/
pdf2md --formulas Bildname <pdf>-<seite>-f<nr>.png, Liste formulas-*.json mit Seite, Rechteck, Art und PDF-Text das Bild selbst (300 dpi aus dem Vektor-PDF)

Mit pdf2md --formulas findet die Formelerkennung von pix2text (MFD) jede Formel auf der Seite, auch im Fließtext und in Merkkästen; pdf2md schneidet sie zeichengenau aus dem Text. Ob ein Box-Bild eine Formel ist, verraten bei Box-Bildern die Schriften im Ausschnitt (CMMI, CMSY, EURM …).


11. Sicherheit

  • Lesarten sind unsicher: Sie kommen von Modellen. Vor pdflatex lehnt tex.unsafe_reason Befehle ab, die Dateien lesen oder schreiben, Code ausführen oder Makros umdefinieren (\input, \write, \def, \catcode, \csname …), außerdem ^^-Zeichencodes, Begrenzer und unausgeglichene Klammern.
  • pdflatex läuft mit -no-shell-escape, openout_any=p, openin_any=p und Zeitlimit.
  • Review-Server bindet nur an 127.0.0.1; jede Entscheidung muss ein Zufallstoken der Seite mitschicken, damit fremde Webseiten im selben Browser nichts entscheiden können.
  • Daten nach außen: Mit der Standardregel verlässt nichts den Rechner. Nur die Regel vlm3 schickt Formelbilder samt Textausschnitt an OpenRouter.

12. Messungen

Gemessen wird an Formelbildern mit bekanntem TeX: Springer-EPUBs (Göllmann) tragen das TeX jeder Formel im alt-Text. Dazu kommen Nachbildungen im Stil von Gehrke (LaTeX + dvipng mit Bitmap-Schriften, ~102 dpi, JPEG). Werkzeuge: calibrate --consensus, tools/regeltest.py, tools/zeittest.py.

Regeln im Vergleich (Göllmann Band 1, je 150 echte und 150 nachgebildete Formeln, Stand 0.3.0)

Regel echt: automatisch / falsch Gehrke-Stil: automatisch / falsch
Qwen = Gemma 73 % / 1 69 % / 2
Qwen = Gemma = Gemini 2.5 Flash 67 % / 0 57 % / 1
Qwen = Gemma = GLM-4.6V (Regel vlm3) 78 % / 0 70 % / 0
pix2text = Qwen 81 % / 0 76 % / 0
pix2text zuerst, bereinigt, Stichwahl Gemma (Regel p2t) 86 % / 0 85 % / 0
+ Stichwahl auch Qwen = Gemma gegen pix2text 92 % / 1 91 % / 2

Mit der mehrzeiligen Normalform (0.4.0), gleiche Lesarten: 89 % / 0 und 88 % / 0; Göllmann Band 2 (zwei Stichproben zu 50): 44 und 45 automatisch, 0 falsch.

Allein richtig (Stand 0.3.0): pix2text 90 % / 85 %, Qwen 86 % / 84 %, Gemma 86 % / 80 %, GLM 86 % / 83 %. Zeit je Formel seriell: pix2text 0,10 s (GPU) bzw. 0,51 s (CPU), Qwen 0,72 s, GLM 1,19 s, Gemma 1,77 s.

Ganze Bücher (ohne bekannte Lösung)

Buch Version Formelbilder automatisch Review Prüfung von Hand
Gehrke, Brückenkurs Mathematik (EPUB) 0.2.0, Regel vlm3 1505 942 (63 %) 478 62 von 62 korrekt
Corral, Elementary Calculus (PDF, Box-Bilder) 0.4.0 1150 429 (37 %) 633 –
Corral mit pdf2md --formulas, Stichprobe 10 % unveröffentlicht 940 747 (79 %) 174 –

Bei Gehrke ergab Pandoc --mathml nach apply für 945 von 945 Formeln MathML. Bei Corral (Box-Bilder) bestand das Review überwiegend aus Ausschnitten, die keine einzelne Formel waren (Merkkästen mit Text, Tabellen, Herleitungen); --formulas behebt das weitgehend.

Gemessen und verworfen

  • Selbstauskunft der Modelle („95 % sicher“) und pix2texts eigene Sicherheit: Fehler auch bei 1,0.
  • Token-Wahrscheinlichkeiten als Schwelle: neben der Bestätigung kein Gewinn, nur weniger Abdeckung.
  • Gemini als dritte Lesart: mit Gemma verwandt, ließ einen gemeinsamen Fehler durch.
  • GLM mit Nachdenken: 79 % statt 85 % allein richtig, ~25× mehr Tokens.
  • Bild-Schiedsrichter (verify.image_arbiter) bei „Qwen = Gemma gegen pix2text“: In 16 Fällen hatte Qwen 14-mal recht, aber der gemeinsame Fehler \mathcal{E}_r bekam Beleg 7,2 – zu knapp für eine Schwelle von 8.
  • Strukturvergleich mit sympy: nach der mehrzeiligen Normalform kein zusätzlicher Treffer.
  • Prompt-Stilvorgabe (feste Schreibweisen): keine Verbesserung, die Modelle schrieben wörtliches \n.
  • PDF-Textebene als Bestätigung: 12 von 319 widersprachen Qwen, mit fehlenden Punkten und Indizes.
  • Qwen3-VL-8B statt 30B-A3B: nicht schneller (das MoE-Modell rechnet nur ~3B je Token), etwas schlechter.
  • Schriftgrößen-Prüfung im Veto: nur Fehlalarme.

„0 falsch“ bei einigen hundert Formeln bedeutet eine Fehlerquote sehr wahrscheinlich unter ~2 %, nicht null. Die Kalibrierung lief ohne den Textausschnitt, den run mitgibt.


13. Designentscheidungen

Entscheidung Verworfene Alternativen Grund
Eigenes Nachbearbeitungs-Werkzeug Option in epub2md/pdf2md Die Konverter bleiben schlank und offline; torch, Modelle und GPU-Server gehören nicht hinein.
Erkenner entscheiden, Bild nur Veto Pixelvergleich als Entscheider (0.1.0) Bei 15 px hohen JPEG-Formeln mit Bitmap-Hinting trennte der Pixelvergleich richtig und falsch zu schlecht: 1–2 % automatisch.
Zwei verschiedenartige Erkenner ein Modell mit Selbstauskunft; zwei Modelle einer Familie Selbstauskünfte sind wertlos; verwandte Modelle machen dieselben Fehler (Gemini/Gemma). pix2text (Formel-OCR) und Qwen (Bildmodell) irren verschieden.
pix2texts Lesart übernehmen Lesart des Bildmodells pix2text liest allein am häufigsten richtig und trifft den Fettdruck am besten.
Qwen + Gemma gegen pix2text → Mensch 2:1-Mehrheit Genau diese Fälle waren die gemeinsamen Fehler der Bildmodelle (+6 % Abdeckung, aber Fehler).
Normalform statt String-Vergleich Zeichenkette; sympy-Auswertung Die Erkenner schreiben dieselbe Formel verschieden; sympy rechnet um und verwischt Unterschiede (v=(3,4)ᵀ und \nu=(3,4)ᵀ werden beide False).
Render gegen Render als Rückfallebene Render gegen Original Zwei Renders sind exakt vergleichbar; das Original ist verrauscht und anders gesetzt.
Elastischer Vergleich mit Tintenkosten starre Ausrichtung; freie Verzerrung Starr scheitert an anderen Abständen und Pfeillängen; frei könnte ein falsches Zeichen passend biegen.
Fettdruck zählt nicht Fettdruck als eigenes Zeichen Vorgabe des Nutzers; alle Bildmodelle lesen Fettdruck schlecht.
Nachdenken der Modelle aus Standard der Server Schneller, billiger, bei GLM sogar genauer.
Arbeitsordner neben der Ausgabe, Schlüssel = Bildinhalt Daten in der Ausgabe; Dateinamen als Schlüssel epub2md --force ersetzt die Ausgabe; Dateinamen ändern sich, Bildinhalte nicht.
apply aus Sicherung Ersetzen im laufenden Markdown Wiederholbar, rückgängig zu machen, neue Konverterläufe werden erkannt.
Pakete zu 50, Lesen und Prüfen überlappt alles nacheinander Statuszeilen für den Nutzer; die GPUs warten nicht auf die CPU (Pilot 869 Formeln: 70 → ~24 Minuten).
Lokale Modelle, Cloud optional Cloud als Standard Buchinhalte bleiben auf dem Rechner; die lokale Regel misst besser als die mit GLM.
MIT-Lizenz GPL Einfache Wiederverwendung.

14. Grenzen

Grenze Auswirkung Mögliche Abhilfe
Mehrzeilige Rechnungen mit verschiedenen Zeilenumbrüchen, \underbrace, fehlenden Integrationsgrenzen landen im Review Normalform erweitern
Fettdruck wird nicht geprüft ein fetter Vektor kann als normales v übernommen werden bold_relevant = 1 schickt solche Fälle ins Review
Formeln auf farbig hinterlegten Kästen verlieren beim Ersetzen die Hinterlegung –
Formeln in Tabellen erfasst pdf2md --formulas noch nicht Erkennung in Tabellenzellen
Riesenausschnitte (Grafiken, ganze Herleitungen) ohne Bildvergleich abgelehnt (über 2,5 MP, Proportionen über 2,5-fach) bessere Trennung in pdf2md
Messbasis einige hundert Formeln eines Verlags; Textausschnitt nicht kalibriert weitere Bücher mit bekannter Lösung
Laufzeit ~0,8 s je Formel mit zwei RTX 3090 –