159 lines
9.2 KiB
Markdown
159 lines
9.2 KiB
Markdown
|
|
# 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
|
|||
|
|
<ausgabe>.formulas/ # neben der Ausgabe, weil epub2md --force die Ausgabe ersetzt
|
|||
|
|
├── workspace.json # Quelle, Schrift, Pixel/Punkt, Schwellen, apply-Zustand
|
|||
|
|
├── formulas/f-<sha12>-<d|i>.json # je Bild und Darstellung: Kandidaten, Prüfung, Entscheidung
|
|||
|
|
├── review/<id>-{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 `<name>.md` + `<name>_images/` oder `--split`-Ordner | Bildname `<pdf>-<seite>-<box>`; 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
|
|||
|
|
```
|