feat: 0.2.0 – Konsens Qwen = Gemma = GLM als Standard, Gehrke komplett verarbeitet

- 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>
This commit is contained in:
Dieter Schlüter 2026-10-07 07:58:16 +02:00
commit 388024a17b

228
README.md
View file

@ -3,27 +3,75 @@
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.
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). Hintergrund und
ursprüngliche Planung: `Idee/`.
Schritt-für-Schritt-Anleitung: [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md). Änderungen je Version:
[CHANGELOG.md](CHANGELOG.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)).
## 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
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
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
run scan + recognize lokal + verify + recognize Cloud (nur Ungelöste) + verify
calibrate Messung an Formelbildern mit bekanntem TeX (Springer-EPUBs: TeX im alt-Text)
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
@ -33,104 +81,121 @@ 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`.
## 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, 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
├── 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 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.
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` (`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 | `$…$`/`$$…$$` |
| 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 | `$…$`/`$$…$$` |
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
## Werkzeuge zum Einstellen
Jede Engine spricht die OpenAI-Chat-API mit Bildteil:
- `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).
| `--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` |
## Stand
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.
Version 0.2.0 (Konsens), getestet am Gehrke, Brückenkurs Mathematik (EPUB, CM-Formeln als kleine JPGs):
## Stand und Grenzen
| | 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 |
Messungen vom 07.10.2026 (Details: `calibrate`):
- **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.
- **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.
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.
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.
- 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 # pdflatex, Schriften, Pandoc
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. 30 s; synthetische Bilder, kein Modellserver, keine Buchinhalte
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,
@ -141,17 +206,18 @@ Rückgabecodes: `0` Erfolg, `1` Bedienfehler, `2` Eingabe ist keine epub2md-/pdf
```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)
├── 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)
├── compare.py Ausrichtung, Kennzahlen, Kontrasttest, Diff-Bilder
├── confusables.py Verwechslungsvarianten, layouttreue Prüfvarianten
├── verify.py Prüfung + Selbstkorrektur
├── decide.py Schwellen und Status
├── 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