- Dritte Lesart (GLM-4.6V über OpenRouter, ohne Nachdenken) muss zustimmen;
keine 2:1-Annahme; fehlender Schlusspunkt der dritten Lesart zählt nicht
- run in Paketen zu 50 mit Statuszeile, beide lokalen Modelle parallel,
GLM nur für Einige; nur die übernommene Lesart wird gegen das Bild geprüft
- Bild-Veto: Ähnlichkeit >= 0,65, Schriftgrößen-Prüfung abgeschaltet
- Antworten auf 800 Tokens begrenzt; Prompt-Version und Nachdenk-Einstellung
im Cache-Schlüssel; Verfahrensversion für Prüfergebnisse
- Behoben: Gleichungsnummern aus alten Lesarten ("(3.7)"), gleichzeitiges
Speichern zweier Engines, Absturz bei Formeln ohne aktuelle Lesart
- apply: deutsches Dezimalkomma 3{,}6; Review-Galerie zeigt aktuelle Lesarten
- tools/vergleich.py auf Konsens-Ablauf umgestellt
- Doku: README, BEDIENUNGSANLEITUNG, neues CHANGELOG
Gehrke-Brückenkurs: 942 von 1505 Formelbildern automatisch ersetzt (63 %),
62 von 62 von Hand geprüften korrekt; Pandoc/MathML 945 von 945.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
225 lines
13 KiB
Markdown
225 lines
13 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 drei unabhängige Bildmodelle dieselbe Formel lesen** und ein grober
|
||
Bildabgleich nicht widerspricht. Alles andere geht in eine Review-Galerie oder bleibt Bild.
|
||
|
||
Schritt-für-Schritt-Anleitung: [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md). Änderungen je Version:
|
||
[CHANGELOG.md](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, wenn
|
||
|
||
1. **Qwen3-VL-30B-A3B** (lokal, llama.cpp) und **Gemma-4-26B-A4B** (lokal) dieselbe Formel lesen,
|
||
2. **GLM-4.6V** (Cloud über OpenRouter, andere Modellfamilie) das bestätigt – ein fehlender Schlusspunkt
|
||
zählt dabei nicht,
|
||
3. Pandoc sie nach MathML umsetzen kann (sonst bliebe sie im EPUB/PDF roher TeX-Text) und
|
||
4. das gerenderte LaTeX dem Original grob ähnelt (Bild-Veto, elastischer Vergleich, Ähnlichkeit ≥ 0,65).
|
||
|
||
„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
|
||
|
||
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](#stand).
|
||
|
||
## Ablauf
|
||
|
||
```text
|
||
scan Bildverweise in chapters/*.md (bzw. pdf2md-Markdown) finden, Formel/Abbildung vorklassifizieren
|
||
run in Paketen zu 50: Qwen + Gemma lesen (parallel), prüfen, GLM nur für Einige, prüfen;
|
||
nach jedem Paket eine Statuszeile
|
||
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) laufen lassen
|
||
verify prüfen und entscheiden (ohne neue Modellabfragen)
|
||
status Zählung je Status
|
||
calibrate Messung an Formelbildern mit bekanntem TeX (--consensus: Konsensregel)
|
||
```
|
||
|
||
```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
|
||
```
|
||
|
||
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` | `OPENROUTER_API_KEY`, `FORMULA_CLOUD_MODEL` |
|
||
|
||
`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: 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 würde (die von Qwen; im Konsens ist sie gleichwertig zu den anderen) – eine
|
||
später eintreffende dritte Lesart löst keine neue Prüfung aus. 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
|
||
|
||
```text
|
||
<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`.
|
||
- `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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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
|
||
|
||
```text
|
||
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
|
||
├── 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
|
||
```
|