# formula_img2latex Nachbearbeitungs-Werkzeug für die Markdown-Ausgaben von [epub2md](https://kitux.de/forgejo/dschlueter/epub2md) und [pdf2md](https://kitux.de/forgejo/dschlueter/pdf2md): Formeln, die nur als Bild vorliegen, werden durch LaTeX ersetzt – **aber nur, wenn das gerenderte LaTeX nachweislich so aussieht wie das Original**. Zweifelsfälle gehen in eine Review-Galerie, alles andere bleibt Bild. Schritt-für-Schritt-Anleitung: [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md). Hintergrund und ursprüngliche Planung: `Idee/`. > **Stand 0.1.0 (Prototyp).** Der Ablauf ist vollständig (scan → erkennen → prüfen → Review → apply/revert) > und getestet. Die Annahmeschwellen sind noch nicht endgültig kalibriert; an echten Büchern landet > derzeit der größte Teil im Review (siehe [Stand und Grenzen](#stand-und-grenzen)). ## Ablauf ```text scan Bildverweise in chapters/*.md (bzw. pdf2md-Markdown) finden, Formel/Abbildung vorklassifizieren recognize LaTeX-Kandidaten von einem Vision-Modell holen: lokal (llama.cpp) zuerst, Cloud (OpenRouter) nur für Ungelöste verify Kandidat rendern (pdflatex), mit dem Original vergleichen, Verwechslungsvarianten prüfen, entscheiden 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 run scan + recognize lokal + verify + recognize Cloud (nur Ungelöste) + verify calibrate Messung an Formelbildern mit bekanntem TeX (Springer-EPUBs: TeX im alt-Text) ``` ```bash 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 ``` ## Wie geprüft wird Die OCR liefert nur einen **Kandidaten**. Entschieden wird über das Bild: 1. **Rendern** mit pdflatex (Paket `preview`, viele Formeln je Lauf, `-no-shell-escape`, `openout_any=p`). Unsichere Befehle (`\input`, `\write`, `\def`, `\catcode`, …) werden vorher abgelehnt. Die Schrift wird je Buch automatisch gewählt: `cm`, `times`, `txfonts`, `palatino`, `stix`, `euler`. 2. **Vergleichen** in der Auflösung des Originals: Tinte normalisieren (Transparenz, JPEG-Rauschen, getönter Hintergrund), gleichmäßig skalieren (nie strecken), auf ¼ Pixel ausrichten. Kennzahlen: Korrelation (NCC), SSIM, Abweichungsflecken, Größenabweichung, **Pixel pro Punkt**. Pixel pro Punkt ist innerhalb eines Buches konstant; Abweichungen verraten falsche Stile (`\frac` statt `\tfrac`, `\displaystyle`). 3. **Kontrasttest gegen Verwechslungsvarianten** (`confusables.py`). Erzeugt werden ~50–160 Varianten, die sich durch je einen typischen Erkennungsfehler unterscheiden: andere Ziffer, hoch statt tief, `,`/`;`, `-`/`+`/`=`, ähnliche Buchstaben, Groß/klein, `\mathcal`/`\mathbb`/…, fehlendes Vorzeichen, Strich oder Akzent. Varianten werden **layouttreu** gerendert (`\mathrlap{neu}\mathbin{\phantom{alt}}`), damit sich nur das eine Zeichen ändert. Verglichen wird nur dort, wo sich Kandidat und Variante unterscheiden (Likelihood-Test). Der Kandidat muss **jede** unterscheidbare Variante mit Abstand schlagen. 4. **Selbstkorrektur:** Erklärt eine Variante das Bild deutlich besser, wird sie neuer Kandidat (höchstens 3 Schritte). So werden einzelne OCR-Fehler ohne erneute Modellanfrage repariert. 5. **Pandoc-Prüfung:** Jede angenommene Formel muss mit `pandoc --mathml` umsetzbar sein, sonst bliebe sie im späteren EPUB/PDF roher TeX-Text. 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`. ## Arbeitsordner ```text .formulas/ # neben der Ausgabe, weil epub2md --force die Ausgabe ersetzt ├── workspace.json # Quelle, Schrift, Pixel/Punkt, Schwellen, apply-Zustand ├── formulas/f--.json # je Bild und Darstellung: Kandidaten, Prüfung, Entscheidung ├── review/-{orig,render,diff}.png └── backup/ # Markdown-Dateien vor dem ersten apply ``` Die Datensätze sind über den SHA-256 des Bildes verknüpft. Ein erneuter epub2md-Lauf mit `--force` verliert deshalb keine Entscheidungen. `scan` und `apply` spielen sie ohne neue OCR wieder ein. Modellantworten werden je Modell gespeichert; dasselbe Modell wird nie zweimal nach demselben Bild gefragt. ## Quellen | Quelle | Erkennung | Begrenzer | | --- | --- | --- | | epub2md-Ordner | `conversion-report.json` (`tool: epub2md`); Warnungen `formula-image` markieren sichere Formeln | `--math auto`: `$…$`/`$$…$$`, `--math tex`: `\(…\)`/`\[…\]` | | pdf2md-Datei `.md` + `_images/` oder `--split`-Ordner | Bildname `--`; mit `--pdf` wird jede Formel per Template-Matching auf der Seite gefunden, mit 600 dpi aus dem Vektor-PDF neu ausgeschnitten und an Mathe-Schriften (CMMI, EURM, …) als Formel erkannt | `$…$`/`$$…$$` | Abgesetzte Bildformeln gelten als Display-Mathematik, Bilder im Fließtext als Inline-Mathematik. Codeblöcke werden nie verändert. Inline-Formeln, die pdf2md als Glyphensalat im Fließtext ausgibt, sind nicht Teil dieser Version. ## Engines Jede Engine spricht die OpenAI-Chat-API mit Bildteil: | `--engine` | Standard | Konfiguration | | --- | --- | --- | | `local` | llama.cpp-Server `http://127.0.0.1:8011/v1`, Modell vom Server | `FORMULA_LOCAL_URL`, `FORMULA_LOCAL_MODEL`, `FORMULA_LOCAL_API_KEY` | | `cloud` | OpenRouter, `google/gemini-2.5-flash` | `OPENROUTER_API_KEY`, `FORMULA_CLOUD_MODEL`, `FORMULA_CLOUD_URL` | Getestet: **Qwen3-VL-30B-A3B-Instruct** (Q4_K_M + mmproj) auf einer RTX 3090 mit 0,2–0,5 s je Formel; Gehrke Brückenkurs (1329 Bilder) in 9 Minuten. ## Stand und Grenzen Messungen vom 07.10.2026 (Details: `calibrate`): - **Synthetisch** (Computer Modern, ~1,4 px/pt, JPEG – wie Gehrke): Die korrekte Formel schlägt in 98 % der Fälle alle Verwechslungsvarianten; ein einzelner falscher Startkandidat wird meist repariert. - **Gehrke Brückenkurs** (EPUB, ~1400 Formel-JPGs, CM): Die OCR ist gut. Abgelehnt oder ins Review geschickt wird überwiegend wegen **typografischer** Abweichungen der OCR, z. B. `\xrightarrow{…}` statt `\stackrel{…}{\longrightarrow}`, `\vec` statt `\overrightarrow`, Text neben statt über `=`. Bei einer Pfeillänge verrutscht alles dahinter. Abhilfe in Arbeit: Korrekturrunde, in der das Modell Original und eigenes Render nebeneinander sieht. - **Springer-Bücher** (Liesen, Göllmann): Was im alt-Text kein TeX hat, sind meist komplexe mehrzeilige Formeln eines fremden Satzsystems. Abstände und Klammergrößen weichen ab, der starre Vergleich bestätigt sie nicht → Review. - **PDF** (Hefferon, Euler-Schrift): Einfache Formeln passen mit `euler` sehr gut (NCC 0,96). Wo Ziffern oder Operatornamen in anderen Schnitten gesetzt sind (serifenloses `sgn`), wird der Kontrasttest unzuverlässig → Review. - Formeln auf farbig hinterlegten Kästen verlieren beim Ersetzen die Hinterlegung. Nächste Schritte: Schwellen mit `calibrate` an den layouttreuen Varianten neu festlegen; Korrekturrunde mit Diff-Bild; abschnittsweiser Vergleich (Zeilen/Spalten getrennt ausrichten) für mehrzeilige Formeln fremder Satzsysteme; Inline-Formeln in PDFs. ## Installation ```bash sudo apt install texlive-latex-extra texlive-fonts-extra texlive-science pandoc # pdflatex, Schriften, Pandoc pip install -r requirements.txt # pymupdf, numpy, scipy, scikit-image, opencv, Pillow, lxml, requests pip install -r requirements-dev.txt # pytest ``` ## Tests ```bash python3 -m pytest -q # ca. 30 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 ```text formula_img2latex/ ├── cli.py Unterbefehle, Exit-Codes ├── pipeline.py scan, recognize, verify (Prozess-Pool), Entscheidung, apply/revert ├── sources/ epub2md.py, pdf2md.py, base.py (Markdown-Scan, Begrenzer) ├── pdfcrop.py Formelbild auf der PDF-Seite finden, 600-dpi-Neuausschnitt, Mathe-Schrift-Erkennung ├── classify.py Formel / unklar / Abbildung (Farbe der Tinte, Graustufen, Größe, Kontext) ├── engines/ Vision-Modelle über die OpenAI-API (llama.cpp, OpenRouter) ├── render.py pdflatex-Batch, Schrift-Presets, Rasterung ├── imageio.py Originale laden (PNG/GIF/JPG/SVG, Transparenz, Tönung) ├── compare.py Ausrichtung, Kennzahlen, Kontrasttest, Diff-Bilder ├── confusables.py Verwechslungsvarianten, layouttreue Prüfvarianten ├── verify.py Prüfung + Selbstkorrektur ├── decide.py Schwellen und Status ├── checks.py Pandoc/MathML-Prüfung ├── calibrate.py Messung an Formelbildern mit bekanntem TeX ├── store.py Arbeitsordner, Datensätze └── review/ lokaler Review-Server + Seite ```