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>
This commit is contained in:
parent
be6ba0b926
commit
6a0e9587f5
7 changed files with 871 additions and 581 deletions
|
|
@ -4,17 +4,32 @@ Dieses Programm ersetzt Formelbilder in den Markdown-Dateien von epub2md und pdf
|
||||||
LaTeX-Formeln. Dadurch passen die Formeln beim Neubau als EPUB oder PDF in Größe und Schrift zum Text.
|
LaTeX-Formeln. Dadurch passen die Formeln beim Neubau als EPUB oder PDF in Größe und Schrift zum Text.
|
||||||
|
|
||||||
Es liest jede Formel zuerst mit dem Formel-OCR **pix2text** und ersetzt sie nur, wenn das Bildmodell
|
Es liest jede Formel zuerst mit dem Formel-OCR **pix2text** und ersetzt sie nur, wenn das Bildmodell
|
||||||
**Qwen** dieselbe Formel liest (bei Uneinigkeit darf das zweite Bildmodell Gemma pix2text bestätigen) und
|
**Qwen** dieselbe Formel liest (bei Uneinigkeit darf das zweite Bildmodell **Gemma** pix2text bestätigen)
|
||||||
das gerenderte LaTeX dem Bild grob ähnelt. Eingesetzt wird die Lesart von pix2text, ohne ihre
|
und das gerenderte LaTeX dem Bild grob ähnelt. Eingesetzt wird die Lesart von pix2text. Alles läuft lokal.
|
||||||
überflüssigen Leerzeichen. Alles läuft lokal. Bei Zweifeln fragt es dich in einer
|
Bei Zweifeln fragt es dich in einer Review-Seite im Browser; ohne deine Zustimmung bleibt die Formel ein
|
||||||
Review-Seite im Browser. Ohne deine Zustimmung bleibt die Formel ein Bild.
|
Bild.
|
||||||
|
|
||||||
„Dieselbe Formel“ heißt: jedes Zeichen stimmt und steht an der richtigen Stelle. Abstände, Pfeillängen,
|
„Dieselbe Formel“ heißt: jedes Zeichen stimmt und steht an der richtigen Stelle. Abstände, Pfeillängen,
|
||||||
Schriftgröße, kursiv/aufrecht und Fettdruck dürfen abweichen.
|
Schriftgröße, kursiv/aufrecht und Fettdruck dürfen abweichen. Wie das intern geprüft wird, steht in
|
||||||
|
[FUNKTIONSWEISE.md](FUNKTIONSWEISE.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Einrichtung (einmalig)
|
## Inhalt
|
||||||
|
|
||||||
|
1. [Installation](#1-installation)
|
||||||
|
2. [Modellserver starten und beenden](#2-modellserver-starten-und-beenden)
|
||||||
|
3. [Ein Buch bearbeiten](#3-ein-buch-bearbeiten)
|
||||||
|
4. [Einzelne Schritte und Optionen](#4-einzelne-schritte-und-optionen)
|
||||||
|
5. [Konfiguration](#5-konfiguration)
|
||||||
|
6. [Betrieb](#6-betrieb)
|
||||||
|
7. [Werkzeuge zum Messen und Einstellen](#7-werkzeuge-zum-messen-und-einstellen)
|
||||||
|
8. [Fehlersuche](#8-fehlersuche)
|
||||||
|
9. [Häufige Fragen](#9-häufige-fragen)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install texlive-latex-extra texlive-fonts-extra texlive-science pandoc dvipng
|
sudo apt install texlive-latex-extra texlive-fonts-extra texlive-science pandoc dvipng
|
||||||
|
|
@ -23,40 +38,14 @@ pip install -r requirements.txt
|
||||||
python3 formula_img2latex.py --version
|
python3 formula_img2latex.py --version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Die beiden lokalen Bildmodelle starten
|
- **TeX Live** setzt die Formeln für den Bildabgleich (pdflatex, Schriften `lmodern`, `newtx`, `newpx`,
|
||||||
|
`stix2`, `eulervm`, `txfonts`).
|
||||||
Zwei Vision-Sprachmodelle lesen jede Formel, je eines pro Grafikkarte (je ~18–21 GB). In zwei Terminals:
|
- **Pandoc** prüft, dass jede Formel später als MathML gesetzt werden kann.
|
||||||
|
- **dvipng** braucht nur `calibrate --synthetic`.
|
||||||
```bash
|
|
||||||
# Modell A: Qwen3-VL auf GPU 1, Port 8011
|
|
||||||
M=~/endeavour_home/lm_studio/models/lmstudio-community/Qwen3-VL-30B-A3B-Instruct-GGUF
|
|
||||||
CUDA_VISIBLE_DEVICES=1 ~/.local/bin/llama-server \
|
|
||||||
-m $M/Qwen3-VL-30B-A3B-Instruct-Q4_K_M.gguf --mmproj $M/mmproj-Qwen3-VL-30B-A3B-Instruct-F16.gguf \
|
|
||||||
--host 127.0.0.1 --port 8011 -ngl 99 -c 32768 --parallel 8
|
|
||||||
|
|
||||||
# Modell B: Gemma 4 auf GPU 2, Port 8012
|
|
||||||
M=~/nvme2n1p7_home/huggingface/models/gemma4
|
|
||||||
CUDA_VISIBLE_DEVICES=2 ~/.local/bin/llama-server \
|
|
||||||
-m $M/gemma-4-26B-A4B-it-Q4_0.gguf --mmproj $M/mmproj-gemma-4-26B-A4B-it-BF16.gguf \
|
|
||||||
--host 127.0.0.1 --port 8012 -ngl 99 -c 16384 --parallel 4
|
|
||||||
```
|
|
||||||
|
|
||||||
Qwen läuft mit 8 Plätzen zu je 4096 Tokens (zusammen 32768) und belegt rund 22,6 GB; `run` stellt bis zu
|
|
||||||
8 Anfragen gleichzeitig. Gemma braucht auf GPU 2 rund 17,5 GB. Laufen dort Chatterbox-TTS und der STT-Server, reicht der Speicher
|
|
||||||
nicht (Gemma bricht beim ersten Bild mit „out of memory“ ab); dann vorher stoppen und danach wieder starten:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl --user stop chatterbox-tts.service open-notebook-stt.service
|
|
||||||
systemctl --user start chatterbox-tts.service open-notebook-stt.service # nach der Arbeit
|
|
||||||
```
|
|
||||||
|
|
||||||
pix2text läuft zusätzlich auf der ersten sichtbaren GPU (unter 1 GB). Beenden mit Strg+C in beiden
|
|
||||||
Terminals.
|
|
||||||
|
|
||||||
### pix2text (Formel-OCR)
|
### pix2text (Formel-OCR)
|
||||||
|
|
||||||
pix2text läuft ohne Server im Programm selbst, auf der ersten sichtbaren Grafikkarte (sonst auf der CPU,
|
pix2text läuft ohne Server im Programm selbst. Einmalig:
|
||||||
dann ~5× langsamer). Einmalig:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install "pix2text[multilingual]"
|
pip install "pix2text[multilingual]"
|
||||||
|
|
@ -64,24 +53,64 @@ pip install --no-deps pix2tex munch # nur für post_p
|
||||||
pip uninstall -y onnxruntime && pip install onnxruntime-gpu # für die GPU
|
pip uninstall -y onnxruntime && pip install onnxruntime-gpu # für die GPU
|
||||||
```
|
```
|
||||||
|
|
||||||
Das Modell (~300 MB) wird beim ersten Lauf nach `~/.pix2text` geladen. Ohne Angabe nimmt pix2text die
|
`pix2tex` wird ohne Abhängigkeiten installiert, weil es sonst eine alte `timm`-Version erzwingen würde.
|
||||||
Grafikkarte mit dem meisten freien Speicher und liest die Formeln gebündelt. Gerät festlegen mit
|
Das Modell (~300 MB) wird beim ersten Lauf nach `~/.pix2text` geladen. pix2text nimmt die sichtbare
|
||||||
`FORMULA_P2T_DEVICE=cpu` oder `cuda:N` (`N` zählt innerhalb von `CUDA_VISIBLE_DEVICES`).
|
Grafikkarte mit dem meisten freien Speicher (unter 1 GB) und liest die Formeln gebündelt; ohne
|
||||||
|
`onnxruntime-gpu` läuft es auf der CPU, dann etwa 5× langsamer.
|
||||||
|
|
||||||
Gemma (Port 8012) wird nur für Formeln gefragt, bei denen pix2text und Qwen uneinig sind. Läuft Gemma
|
### Modelle für die Bildmodelle
|
||||||
nicht, bleiben diese Formeln im Review.
|
|
||||||
|
|
||||||
### Frühere Regel mit Cloud (optional)
|
| Modell | Datei | Ort |
|
||||||
|
|---|---|---|
|
||||||
|
| Qwen3-VL-30B-A3B-Instruct | `Qwen3-VL-30B-A3B-Instruct-Q4_K_M.gguf` + `mmproj-Qwen3-VL-30B-A3B-Instruct-F16.gguf` | `~/endeavour_home/lm_studio/models/lmstudio-community/Qwen3-VL-30B-A3B-Instruct-GGUF/` |
|
||||||
|
| Gemma-4-26B-A4B-it | `gemma-4-26B-A4B-it-Q4_0.gguf` + `mmproj-gemma-4-26B-A4B-it-BF16.gguf` (ggml-org, zusammen 15,8 GB) | `~/nvme2n1p7_home/huggingface/models/gemma4/` |
|
||||||
|
|
||||||
Bis Version 0.2.0 kam die Bestätigung von **GLM-4.6V** über OpenRouter (Qwen = Gemma = GLM). Diese Regel
|
Gemma neu laden:
|
||||||
ist weiter wählbar: im Arbeitsordner in `workspace.json` unter `settings` den Eintrag
|
|
||||||
`"consensus_rule": "vlm3"` setzen; dann muss `OPENROUTER_API_KEY` gesetzt sein.
|
```bash
|
||||||
|
cd ~/nvme2n1p7_home/huggingface/models/gemma4
|
||||||
|
hf download ggml-org/gemma-4-26B-A4B-it-GGUF gemma-4-26B-A4B-it-Q4_0.gguf \
|
||||||
|
mmproj-gemma-4-26B-A4B-it-BF16.gguf --local-dir .
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional, nur für den Strukturvergleich (aus): `pip install sympy lark`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Ein Buch bearbeiten
|
## 2. Modellserver starten und beenden
|
||||||
|
|
||||||
### Schritt 1: Buch wie gewohnt umwandeln
|
Zwei Bildmodelle laufen als llama.cpp-Server, je eines pro Grafikkarte. In zwei Terminals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Qwen3-VL auf GPU 1, Port 8011: 8 Plätze zu je 4096 Tokens, rund 22,6 GB
|
||||||
|
M=~/endeavour_home/lm_studio/models/lmstudio-community/Qwen3-VL-30B-A3B-Instruct-GGUF
|
||||||
|
CUDA_VISIBLE_DEVICES=1 ~/.local/bin/llama-server \
|
||||||
|
-m $M/Qwen3-VL-30B-A3B-Instruct-Q4_K_M.gguf --mmproj $M/mmproj-Qwen3-VL-30B-A3B-Instruct-F16.gguf \
|
||||||
|
--host 127.0.0.1 --port 8011 -ngl 99 -c 32768 --parallel 8
|
||||||
|
|
||||||
|
# Gemma 4 auf GPU 2, Port 8012: rund 17,5 GB
|
||||||
|
M=~/nvme2n1p7_home/huggingface/models/gemma4
|
||||||
|
CUDA_VISIBLE_DEVICES=2 ~/.local/bin/llama-server \
|
||||||
|
-m $M/gemma-4-26B-A4B-it-Q4_0.gguf --mmproj $M/mmproj-gemma-4-26B-A4B-it-BF16.gguf \
|
||||||
|
--host 127.0.0.1 --port 8012 -ngl 99 -c 16384 --parallel 4
|
||||||
|
```
|
||||||
|
|
||||||
|
Laufen auf GPU 2 Chatterbox-TTS und der STT-Server, reicht der Speicher für Gemma nicht. Dann vorher
|
||||||
|
stoppen und nach der Arbeit wieder starten:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user stop chatterbox-tts.service open-notebook-stt.service
|
||||||
|
systemctl --user start chatterbox-tts.service open-notebook-stt.service # nach der Arbeit
|
||||||
|
```
|
||||||
|
|
||||||
|
Gemma wird nur für Formeln gefragt, bei denen pix2text und Qwen uneinig sind. Läuft Gemma nicht, meldet
|
||||||
|
`run` das einmal, und diese Formeln bleiben im Review. Beenden: Strg+C in beiden Terminals.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Ein Buch bearbeiten
|
||||||
|
|
||||||
|
### Schritt 1: Buch umwandeln
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/Python_Programs/epub2md
|
cd ~/Python_Programs/epub2md
|
||||||
|
|
@ -90,7 +119,7 @@ python3 epub2md.py buch.epub output/buch
|
||||||
|
|
||||||
Bei PDFs mit pdf2md immer `--images` (oder `--epub`) angeben, sonst gibt es keine Formelbilder. Für
|
Bei PDFs mit pdf2md immer `--images` (oder `--epub`) angeben, sonst gibt es keine Formelbilder. Für
|
||||||
Mathebücher **`--formulas`** verwenden: Dann findet pdf2md jede Formel auf der Seite, auch im Fließtext und
|
Mathebücher **`--formulas`** verwenden: Dann findet pdf2md jede Formel auf der Seite, auch im Fließtext und
|
||||||
in Merkkästen, und schneidet sie als Bild aus (braucht pix2text, siehe oben):
|
in Merkkästen, und schneidet sie als Bild aus (braucht pix2text):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/Python_Programs/pdf2md
|
cd ~/Python_Programs/pdf2md
|
||||||
|
|
@ -106,10 +135,11 @@ cd ~/Python_Programs/formula_img2latex
|
||||||
python3 formula_img2latex.py run ~/Python_Programs/epub2md/output/buch
|
python3 formula_img2latex.py run ~/Python_Programs/epub2md/output/buch
|
||||||
```
|
```
|
||||||
|
|
||||||
Bei pdf2md zusätzlich die Original-PDF angeben. Dann schneidet das Programm jede Formel scharf aus dem PDF:
|
Bei pdf2md die Markdown-Datei angeben und die Original-PDF dazu; Box-Bilder werden dann scharf aus dem PDF
|
||||||
|
neu ausgeschnitten:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 formula_img2latex.py run Output/buch.md --pdf buch.pdf
|
python3 formula_img2latex.py run ~/Python_Programs/pdf2md/Output/buch/buch.md --pdf buch.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
Das Programm arbeitet in Paketen zu 50 Formeln und meldet nach jedem Paket den Stand und die Zeiten je
|
Das Programm arbeitet in Paketen zu 50 Formeln und meldet nach jedem Paket den Stand und die Zeiten je
|
||||||
|
|
@ -119,7 +149,7 @@ Stufe (die Stufen laufen teils gleichzeitig, daher ist die Summe größer als di
|
||||||
100/940 verarbeitet: automatisch ersetzt 78 (78%), Review 20, bleibt Bild/keine Formel 2 [Paket 35 s; pix2text 3 s, Qwen 30 s, Prüfung 14 s, Gemma 21 s]
|
100/940 verarbeitet: automatisch ersetzt 78 (78%), Review 20, bleibt Bild/keine Formel 2 [Paket 35 s; pix2text 3 s, Qwen 30 s, Prüfung 14 s, Gemma 21 s]
|
||||||
```
|
```
|
||||||
|
|
||||||
Am Ende steht eine Übersicht je Status, beim Gehrke-Brückenkurs zum Beispiel:
|
Am Ende steht eine Übersicht je Status, zum Beispiel:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
auto_accepted 942 ← Lesart bestätigt, wird ersetzt
|
auto_accepted 942 ← Lesart bestätigt, wird ersetzt
|
||||||
|
|
@ -128,15 +158,7 @@ Am Ende steht eine Übersicht je Status, beim Gehrke-Brückenkurs zum Beispiel:
|
||||||
pending 539 ← Abbildungen/Symbole, nicht geprüft
|
pending 539 ← Abbildungen/Symbole, nicht geprüft
|
||||||
```
|
```
|
||||||
|
|
||||||
Dauer: etwa 0,8 s je Formel, also 35–50 Sekunden je Paket (Corral mit `--formulas`: 940 Formeln in
|
Neben dem Buchordner entsteht ein Arbeitsordner `buch.formulas/` mit allen Ergebnissen. Lösch ihn nicht,
|
||||||
13 Minuten, das ganze Buch mit 9239 Formeln rund 2 Stunden). Am längsten braucht die Bestätigung durch Qwen.
|
|
||||||
Der Scan liest beim ersten Mal alle Bilder ein (Corral: 6 Minuten); beim Fortsetzen entfällt er, solange
|
|
||||||
sich das Markdown nicht geändert hat.
|
|
||||||
Abbrechen (Strg+C) und erneut starten ist kein Problem: Bereits gelesene Formeln werden nicht noch einmal
|
|
||||||
gefragt, der Lauf setzt fort. `--chunk 100` ändert die Paketgröße, `--limit 200` verarbeitet nur die ersten
|
|
||||||
200 Bilder.
|
|
||||||
|
|
||||||
Neben dem Buchordner entsteht ein Arbeitsordner `buch.formulas/`. Er enthält alle Ergebnisse. Lösch ihn nicht,
|
|
||||||
solange du am Buch arbeitest.
|
solange du am Buch arbeitest.
|
||||||
|
|
||||||
### Schritt 3: Zweifelsfälle im Browser entscheiden
|
### Schritt 3: Zweifelsfälle im Browser entscheiden
|
||||||
|
|
@ -152,12 +174,10 @@ Der Browser öffnet `http://127.0.0.1:8765/`. Für jede Formel siehst du:
|
||||||
stimmt etwas nicht (Exponent, Vorzeichen, Klammer).
|
stimmt etwas nicht (Exponent, Vorzeichen, Klammer).
|
||||||
- **Diff:** schwarz = beide gleich, rot = nur im Original, blau = nur im LaTeX, gelb = deutliche Abweichung.
|
- **Diff:** schwarz = beide gleich, rot = nur im Original, blau = nur im LaTeX, gelb = deutliche Abweichung.
|
||||||
- **LaTeX:** Text im Eingabefeld (die Lesart von pix2text), darunter die Lesarten der Modelle zum
|
- **LaTeX:** Text im Eingabefeld (die Lesart von pix2text), darunter die Lesarten der Modelle zum
|
||||||
Anklicken. Oben steht der Grund für das Review, z. B. „Modelle uneinig“ mit den abweichenden Lesarten.
|
Anklicken. Oben steht der Grund für das Review, z. B. „pix2text und Qwen uneinig“.
|
||||||
|
|
||||||
Schaltflächen:
|
|
||||||
|
|
||||||
| Schaltfläche | Wirkung |
|
| Schaltfläche | Wirkung |
|
||||||
| --- | --- |
|
|---|---|
|
||||||
| LaTeX prüfen | rendert dein (korrigiertes) LaTeX neu und zeigt Overlay und Messwerte |
|
| LaTeX prüfen | rendert dein (korrigiertes) LaTeX neu und zeigt Overlay und Messwerte |
|
||||||
| LaTeX übernehmen | die Formel wird beim nächsten `apply` ersetzt |
|
| LaTeX übernehmen | die Formel wird beim nächsten `apply` ersetzt |
|
||||||
| Bild behalten | die Formel bleibt ein Bild |
|
| Bild behalten | die Formel bleibt ein Bild |
|
||||||
|
|
@ -165,7 +185,8 @@ Schaltflächen:
|
||||||
| Zurücksetzen | Entscheidung zurücknehmen |
|
| Zurücksetzen | Entscheidung zurücknehmen |
|
||||||
|
|
||||||
Jede Entscheidung wird sofort gespeichert. Du kannst jederzeit aufhören (Strg+C im Terminal) und später
|
Jede Entscheidung wird sofort gespeichert. Du kannst jederzeit aufhören (Strg+C im Terminal) und später
|
||||||
weitermachen. `--all` zeigt auch die automatisch angenommenen Formeln.
|
weitermachen. `--all` zeigt auch die automatisch angenommenen Formeln, `--port` ändert den Port,
|
||||||
|
`--no-browser` öffnet keinen Browser.
|
||||||
|
|
||||||
### Schritt 4: Formeln ins Markdown schreiben
|
### Schritt 4: Formeln ins Markdown schreiben
|
||||||
|
|
||||||
|
|
@ -176,31 +197,21 @@ python3 formula_img2latex.py apply ~/Python_Programs/epub2md/output/buch
|
||||||
|
|
||||||
Aus `` wird `$L = \{-\sqrt{2};\sqrt{2}\}$` im Text bzw. `$$…$$` als eigener
|
Aus `` wird `$L = \{-\sqrt{2};\sqrt{2}\}$` im Text bzw. `$$…$$` als eigener
|
||||||
Absatz. Die Bilddateien bleiben liegen. Hat die Formelerkennung bei einer Formel im Text ein Satzzeichen
|
Absatz. Die Bilddateien bleiben liegen. Hat die Formelerkennung bei einer Formel im Text ein Satzzeichen
|
||||||
mit ausgeschnitten, steht es danach als normaler Text hinter der Formel (`$a^{x}$,`).
|
mit ausgeschnitten, steht es danach als normaler Text hinter der Formel (`$a^{x}$,`). In deutschen
|
||||||
|
Büchern werden Dezimalzahlen zu `3{,}6`; eine Gleichungsnummer steht als Text hinter der Formel.
|
||||||
|
|
||||||
Bei pdf2md-Büchern baut `pdf2md.py --from-md` das EPUB neu, mit allen Formeln als MathML:
|
**Danach das Buch neu bauen:**
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ~/Python_Programs/pdf2md && python3 pdf2md.py --from-md Output/buch/buch.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**Danach die Gesamtdatei neu bauen**, sie enthält sonst noch die Bilder:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# epub2md: Gesamtdatei neu, dann z. B. EPUB mit MathML
|
||||||
cd ~/Python_Programs/epub2md
|
cd ~/Python_Programs/epub2md
|
||||||
python3 epub2md_merge.py output/buch --force
|
python3 epub2md_merge.py output/buch --force
|
||||||
```
|
cd output/buch && P=~/Python_Programs/epub2md/pandoc
|
||||||
|
|
||||||
Für EPUB und PDF mit Formeln gelten die Pandoc-Befehle aus der epub2md-Anleitung mit `--mathml`, z. B.:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ~/Python_Programs/epub2md/output/buch
|
|
||||||
P=~/Python_Programs/epub2md/pandoc
|
|
||||||
pandoc buch.md --toc --mathml --lua-filter $P/callouts.lua -o buch.epub
|
pandoc buch.md --toc --mathml --lua-filter $P/callouts.lua -o buch.epub
|
||||||
```
|
|
||||||
|
|
||||||
In deutschen Büchern schreibt `apply` Dezimalzahlen als `3{,}6`, sonst setzt LaTeX „3, 6“. Eine
|
# pdf2md: EPUB mit allen Formeln als MathML
|
||||||
Gleichungsnummer steht als Text hinter der Formel, wie bei epub2md.
|
cd ~/Python_Programs/pdf2md && python3 pdf2md.py --from-md Output/buch/buch.md
|
||||||
|
```
|
||||||
|
|
||||||
### Rückgängig machen
|
### Rückgängig machen
|
||||||
|
|
||||||
|
|
@ -208,64 +219,173 @@ Gleichungsnummer steht als Text hinter der Formel, wie bei epub2md.
|
||||||
python3 formula_img2latex.py revert ~/Python_Programs/epub2md/output/buch
|
python3 formula_img2latex.py revert ~/Python_Programs/epub2md/output/buch
|
||||||
```
|
```
|
||||||
|
|
||||||
Das stellt die Kapiteldateien wie vor dem ersten `apply` wieder her. Hast du eine Datei seitdem von Hand
|
Das stellt die Markdown-Dateien wie vor dem ersten `apply` wieder her. Hast du eine Datei seitdem von Hand
|
||||||
geändert, wird sie übersprungen. `--force` setzt sie trotzdem zurück, dann ist die Handänderung weg.
|
geändert, wird sie übersprungen; `--force` setzt sie trotzdem zurück, dann ist die Handänderung weg.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Einzelne Schritte statt `run`
|
## 4. Einzelne Schritte und Optionen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 formula_img2latex.py scan AUSGABE # Bilder finden
|
python3 formula_img2latex.py scan AUSGABE # Bilder finden (immer neu)
|
||||||
python3 formula_img2latex.py recognize AUSGABE --engine p2t # pix2text (zuerst)
|
python3 formula_img2latex.py recognize AUSGABE --engine p2t # pix2text
|
||||||
python3 formula_img2latex.py recognize AUSGABE --engine local # Qwen (bestätigt)
|
python3 formula_img2latex.py recognize AUSGABE --engine local # Qwen
|
||||||
python3 formula_img2latex.py verify AUSGABE # prüfen und entscheiden
|
python3 formula_img2latex.py verify AUSGABE # prüfen und entscheiden
|
||||||
python3 formula_img2latex.py recognize AUSGABE --engine local2 --only-unresolved # Gemma (Stichwahl)
|
python3 formula_img2latex.py recognize AUSGABE --engine local2 --only-unresolved # Gemma
|
||||||
python3 formula_img2latex.py verify AUSGABE
|
python3 formula_img2latex.py verify AUSGABE
|
||||||
python3 formula_img2latex.py status AUSGABE
|
python3 formula_img2latex.py status AUSGABE # Zählung je Status (--json)
|
||||||
```
|
```
|
||||||
|
|
||||||
Nützliche Optionen:
|
| Option | Befehle | Wirkung |
|
||||||
|
|---|---|---|
|
||||||
|
| `--limit 50` | run, recognize | nur die ersten 50 Bilder (zum Ausprobieren) |
|
||||||
|
| `--chunk 100` | run | Paketgröße (Standard 50) |
|
||||||
|
| `--pdf BUCH.pdf` | run, scan | pdf2md: Original-PDF für scharfe Neuausschnitte |
|
||||||
|
| `--preset cm` | run, verify | Schrift für den Bildabgleich fest vorgeben (`cm`, `times`, `txfonts`, `palatino`, `stix`, `euler`); Standard: automatisch je Buch |
|
||||||
|
| `--include-figures` | run, recognize, verify | auch Bilder versuchen, die wie Abbildungen aussehen |
|
||||||
|
| `--model NAME`, `--url URL` | run, recognize | anderes Modell bzw. anderer Server |
|
||||||
|
| `--workers N` | run, recognize, verify | parallele Anfragen bzw. Prüfprozesse |
|
||||||
|
| `--redo` | verify | bereits geprüfte Formeln neu prüfen |
|
||||||
|
| `--workspace ORDNER` | alle außer calibrate | Arbeitsordner an anderem Ort |
|
||||||
|
| `--quiet`, `-q` | alle | nur Fehler ausgeben |
|
||||||
|
|
||||||
| Option | Wirkung |
|
Alle Optionen: `python3 formula_img2latex.py BEFEHL --help`.
|
||||||
| --- | --- |
|
|
||||||
| `--limit 50` | nur die ersten 50 Bilder (zum Ausprobieren) |
|
|
||||||
| `--preset cm` | Schrift fest vorgeben (`cm`, `times`, `txfonts`, `palatino`, `stix`, `euler`); Standard: automatisch |
|
|
||||||
| `--include-figures` | auch Bilder versuchen, die wie Abbildungen aussehen |
|
|
||||||
| `--model NAME` | anderes Modell (lokal: Servername, Cloud: OpenRouter-ID) |
|
|
||||||
| `--workspace ORDNER` | Arbeitsordner an anderem Ort |
|
|
||||||
| `--redo` (verify) | bereits geprüfte Formeln neu prüfen, z. B. nach einem Programm-Update |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Häufige Fragen
|
## 5. Konfiguration
|
||||||
|
|
||||||
|
### Umgebungsvariablen
|
||||||
|
|
||||||
|
| Variable | Standard | Wirkung |
|
||||||
|
|---|---|---|
|
||||||
|
| `FORMULA_LOCAL_URL`, `FORMULA_LOCAL_MODEL` | `http://127.0.0.1:8011/v1`, Modell des Servers | Qwen-Server |
|
||||||
|
| `FORMULA_LOCAL2_URL`, `FORMULA_LOCAL2_MODEL` | `http://127.0.0.1:8012/v1`, Modell des Servers | Gemma-Server |
|
||||||
|
| `FORMULA_LOCAL_API_KEY`, `FORMULA_LOCAL2_API_KEY` | – | falls der Server einen Schlüssel verlangt |
|
||||||
|
| `FORMULA_P2T_DEVICE` | `auto` | pix2text: `auto`, `cpu` oder `cuda:N` (`N` zählt innerhalb von `CUDA_VISIBLE_DEVICES`) |
|
||||||
|
| `FORMULA_P2T_MODEL` | `mfr-1.5` | pix2text-Modell |
|
||||||
|
| `FORMULA_CLOUD_URL`, `FORMULA_CLOUD_MODEL` | `https://openrouter.ai/api/v1`, `z-ai/glm-4.6v` | nur Regel `vlm3` |
|
||||||
|
| `OPENROUTER_API_KEY` | – | nur Regel `vlm3` |
|
||||||
|
| `FORMULA_LOCAL_THINKING`, `FORMULA_LOCAL2_THINKING`, `FORMULA_CLOUD_THINKING` | `0` | `1` schaltet das „Nachdenken“ der Modelle wieder ein |
|
||||||
|
| `FORMULA_STRUCTURAL` | `0` | `1` schaltet den Strukturvergleich mit sympy ein |
|
||||||
|
|
||||||
|
### Einstellungen je Buch (`workspace.json`)
|
||||||
|
|
||||||
|
Im Arbeitsordner unter `settings`, zum Beispiel:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"consensus_rule": "p2t",
|
||||||
|
"consensus_thresholds": {"veto_ncc": 0.65, "bold_relevant": 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Schlüssel | Werte | Wirkung |
|
||||||
|
|---|---|---|
|
||||||
|
| `consensus_rule` | `p2t` (Standard), `vlm3` | Regel `vlm3`: Qwen = Gemma, bestätigt von GLM-4.6V über OpenRouter (bis 0.2.0) |
|
||||||
|
| `decide` | `consensus` (Standard), `pixel` | `pixel`: alter Pixel-Entscheider (bis 0.1.0) |
|
||||||
|
| `consensus_thresholds` | siehe [FUNKTIONSWEISE.md, Schwellen](FUNKTIONSWEISE.md#schwellen-settingsconsensus_thresholds-in-workspacejson) | Veto-Schwelle, Fettdruck, Schiedsrichter, PDF-Textebene … |
|
||||||
|
| `preset` | wird beim ersten Prüfen gesetzt | Schrift für den Bildabgleich |
|
||||||
|
|
||||||
|
Nach einer Änderung `verify` aufrufen; die Modelle werden nicht neu gefragt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Betrieb
|
||||||
|
|
||||||
|
**Dauer:** etwa 0,8 s je Formel, also 35–50 Sekunden je Paket (Corral mit `--formulas`: 940 Formeln in
|
||||||
|
13 Minuten, das ganze Buch mit 9239 Formeln rund 2 Stunden). Am längsten braucht die Bestätigung durch
|
||||||
|
Qwen. Der Scan liest beim ersten Mal alle Bilder ein (Corral: 6 Minuten); beim Fortsetzen entfällt er,
|
||||||
|
solange sich das Markdown nicht geändert hat.
|
||||||
|
|
||||||
|
**Abbrechen und fortsetzen:** Strg+C und erneut `run` ist kein Problem. Bereits gelesene Formeln werden
|
||||||
|
nicht noch einmal gefragt; fehlgeschlagene Anfragen werden wiederholt.
|
||||||
|
|
||||||
|
**Neu umgewandeltes Buch:** Nach `epub2md --force` erst `scan`, dann `apply`. Die Entscheidungen hängen am
|
||||||
|
Bildinhalt und bleiben erhalten.
|
||||||
|
|
||||||
|
**Arbeitsordner:** `<ausgabe>.formulas/` neben der Ausgabe. Er enthält die Datensätze, Ausschnitte,
|
||||||
|
Vergleichsbilder und die Sicherung von `apply`. Aufbau: [FUNKTIONSWEISE.md, Arbeitsordner](FUNKTIONSWEISE.md#3-arbeitsordner-und-datensätze).
|
||||||
|
|
||||||
|
**Datenschutz:** Mit der Standardregel verlässt nichts den Rechner. Die Regel `vlm3` schickt Formelbilder,
|
||||||
|
bei denen Qwen und Gemma einig sind, samt eines kurzen Textausschnitts um die Formel (höchstens 240
|
||||||
|
Zeichen) an OpenRouter; beim Gehrke-Brückenkurs waren das 967 Anfragen für zusammen rund 0,15–0,20 $.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Werkzeuge zum Messen und Einstellen
|
||||||
|
|
||||||
|
Die Werkzeuge schreiben nach `vergleich/` bzw. in den Arbeitsordner; beides wird nicht ins Git übernommen
|
||||||
|
(Buchausschnitte).
|
||||||
|
|
||||||
|
| Werkzeug | Zweck |
|
||||||
|
|---|---|
|
||||||
|
| `tools/vergleich.py AUSGABE` | feste Stichprobe von 50 Formeln eines Buchs, verarbeitet wie `run`; Seite `vergleich/<name>/index.html` mit Original, gerenderten Lesarten, Entscheidung und Bildabgleich |
|
||||||
|
| `tools/buch_bericht.py AUSGABE` | Ergebnisseite eines ganzen Buchs nach `run`, filterbar; liegt in `<arbeitsordner>/bericht/index.html` |
|
||||||
|
| `tools/regeltest.py EPUB [--n 50] [--reuse …] [--refresh local,local2]` | Standardregel an Formeln mit bekanntem TeX, wie `run`; Seite mit wahrer Formel, allen Lesarten und Urteil |
|
||||||
|
| `tools/zeittest.py EPUB` | Zeit und Trefferquote je Erkenner, seriell an 15 Formeln mit bekanntem TeX |
|
||||||
|
| `tools/vergleich_bericht.py --regeltest VORHER:NACHHER` | Vorher/Nachher-Seite zweier Läufe auf denselben Lesarten |
|
||||||
|
| `tools/konsens_bericht.py BERICHT.json EPUB` | Übersichtsseite zu `calibrate --consensus --report` |
|
||||||
|
| `formula_img2latex.py calibrate --consensus --engines local,local2,cloud,p2t --report X.json EPUB` | alle Regeln an Formeln mit bekanntem TeX messen; `--reuse X.json` rechnet ohne Modellabfragen neu, `--synthetic` erzeugt zusätzlich Gehrke-ähnliche Nachbildungen |
|
||||||
|
|
||||||
|
Formeln mit bekanntem TeX liefern Springer-EPUBs, die das TeX jeder Formel im alt-Text tragen (z. B.
|
||||||
|
Göllmann).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Fehlersuche
|
||||||
|
|
||||||
|
| Meldung oder Beobachtung | Ursache | Abhilfe |
|
||||||
|
|---|---|---|
|
||||||
|
| `Lokaler Modellserver unter http://127.0.0.1:8011/v1 nicht erreichbar` | Qwen-Server läuft nicht | Server starten ([2](#2-modellserver-starten-und-beenden)) |
|
||||||
|
| `Stichwahl (Gemma) nicht möglich, strittige Formeln bleiben im Review` | Gemma-Server läuft nicht | Gemma starten und `run` erneut aufrufen |
|
||||||
|
| Gemma bricht beim ersten Bild mit „out of memory“ ab | GPU 2 ist belegt | Chatterbox-TTS und STT stoppen ([2](#2-modellserver-starten-und-beenden)) |
|
||||||
|
| `pix2text ist nicht installiert` / `pix2tex ist nicht installiert` | Pakete fehlen | Befehle aus [1](#pix2text-formel-ocr) ausführen |
|
||||||
|
| `onnxruntime ohne CUDA – pip install onnxruntime-gpu` | nur die CPU-Version von onnxruntime | `pip uninstall -y onnxruntime && pip install onnxruntime-gpu` |
|
||||||
|
| pix2text sehr langsam | läuft auf der CPU | `onnxruntime-gpu` installieren oder `FORMULA_P2T_DEVICE=cuda:0` |
|
||||||
|
| `pdflatex nicht gefunden` / `pandoc nicht gefunden` | TeX Live bzw. Pandoc fehlen | `sudo apt install texlive-latex-extra pandoc` |
|
||||||
|
| `Kein Arbeitsordner gefunden … (zuerst 'scan' ausführen)` | `verify`, `apply` usw. vor dem ersten `scan`/`run` | `scan` oder `run` ausführen |
|
||||||
|
| `… ist weder eine epub2md-Ausgabe … noch eine pdf2md-Ausgabe` | falscher Pfad | epub2md-Ordner bzw. pdf2md-`.md`-Datei angeben |
|
||||||
|
| `Arbeitsordner existiert und ist kein formula_img2latex-Ordner` | Zielordner von `--workspace` belegt | anderen Ordner wählen |
|
||||||
|
| `Bildverweis nicht mehr gefunden: datei.md:123` | Markdown wurde neu erzeugt oder von Hand geändert | `scan` ausführen |
|
||||||
|
| `revert` überspringt Dateien | seit `apply` von Hand geändert | prüfen, dann `revert --force` |
|
||||||
|
| `OPENROUTER_API_KEY ist nicht gesetzt` | Regel `vlm3` oder `--engine cloud` ohne Schlüssel | Schlüssel setzen oder Regel `p2t` verwenden |
|
||||||
|
| Formel bleibt in EPUB/PDF als TeX-Text stehen | Pandoc konnte sie nicht umsetzen (sollte die Pandoc-Prüfung verhindern) | Formel im Review korrigieren, Fall melden |
|
||||||
|
|
||||||
|
**Rückgabecodes:**
|
||||||
|
|
||||||
|
| Code | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| 0 | Erfolg |
|
||||||
|
| 1 | Bedienfehler: Parameter, Eingabe oder Arbeitsordner nicht lesbar oder belegt |
|
||||||
|
| 2 | Eingabe ist keine epub2md- oder pdf2md-Ausgabe |
|
||||||
|
| 3 | Externes Werkzeug fehlt oder versagt: pdflatex, pandoc, Modellserver, pix2text |
|
||||||
|
| 4 | Unerwarteter interner Fehler (Traceback wird ausgegeben) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Häufige Fragen
|
||||||
|
|
||||||
**Das Buch wurde mit epub2md `--force` neu umgewandelt. Ist meine Arbeit weg?**
|
**Das Buch wurde mit epub2md `--force` neu umgewandelt. Ist meine Arbeit weg?**
|
||||||
Nein. Die Entscheidungen hängen am Inhalt der Bilder, nicht an Dateinamen. `scan`, dann `apply`.
|
Nein. Die Entscheidungen hängen am Inhalt der Bilder, nicht an Dateinamen. `scan`, dann `apply`.
|
||||||
|
|
||||||
**Warum landet ein Teil im Review?**
|
**Warum landet ein Teil im Review?**
|
||||||
Ersetzt wird nur, was pix2text liest und Qwen (oder bei Uneinigkeit Gemma) bestätigt. Sind Qwen und Gemma
|
Ersetzt wird nur, was pix2text liest und Qwen (oder bei Uneinigkeit Gemma) bestätigt. Sind Qwen und Gemma
|
||||||
gemeinsam anderer Meinung als pix2text, entscheidest du. Häufige Gründe für Review: ein Modell liest ein
|
gemeinsam anderer Meinung als pix2text, entscheidest du. Häufige Gründe: ein Modell liest ein Zeichen anders
|
||||||
Zeichen anders (oft pix2text: `\nu` statt fettem v, Wörter wie „fir“ statt „für“), sehr kleine oder
|
(oft pix2text: `\nu` statt fettem v, Wörter wie „fir“ statt „für“), sehr kleine oder unscharfe Bilder,
|
||||||
unscharfe Bilder, mehrzeilige Rechnungen mit anderen Zeilenumbrüchen oder `\underbrace`. Verschiedene
|
mehrzeilige Rechnungen mit anderen Zeilenumbrüchen oder `\underbrace`. Im Review stehen die Lesarten
|
||||||
Rahmen derselben Zeilen (`aligned`, `cases`, `&`) gelten seit 0.4.0 als gleich. Im Review stehen die Lesarten
|
|
||||||
nebeneinander; oft genügt ein Blick und „LaTeX übernehmen“.
|
nebeneinander; oft genügt ein Blick und „LaTeX übernehmen“.
|
||||||
|
|
||||||
**Wie sicher ist „automatisch ersetzt“?**
|
**Wie sicher ist „automatisch ersetzt“?**
|
||||||
Gemessen an 300 Formeln mit bekannter Lösung: keine einzige falsche Formel wurde angenommen (Fehlerquote
|
An einigen hundert Formeln mit bekannter Lösung wurde keine falsche Formel angenommen (Fehlerquote sehr
|
||||||
sehr wahrscheinlich unter 2 %). Am Gehrke-Brückenkurs wurden 62 automatisch ersetzte Formeln von Hand
|
wahrscheinlich unter 2 %). Fettdruck wird bewusst nicht geprüft: Ein fetter Vektor **v** kann als normales
|
||||||
geprüft, alle korrekt. Fettdruck wird bewusst nicht geprüft: Ein fetter Vektor **v** kann als normales v
|
v übernommen werden. Messungen: [FUNKTIONSWEISE.md](FUNKTIONSWEISE.md#12-messungen).
|
||||||
übernommen werden.
|
|
||||||
|
|
||||||
**Ich will erst ein paar Formeln ansehen, bevor das ganze Buch läuft.**
|
**Ich will erst ein paar Formeln ansehen, bevor das ganze Buch läuft.**
|
||||||
`python3 tools/vergleich.py AUSGABE` verarbeitet eine feste Stichprobe von 50 Formeln genau wie `run`
|
`python3 tools/vergleich.py AUSGABE` verarbeitet eine feste Stichprobe von 50 Formeln genau wie `run` und
|
||||||
und schreibt `vergleich/<name>/index.html`: Originalbild, die gerenderten Lesarten aller Modelle, die
|
schreibt `vergleich/<name>/index.html`.
|
||||||
Entscheidung mit Grund und den Bildabgleich. Der Ordner `vergleich/` wird nicht ins Git übernommen.
|
|
||||||
|
|
||||||
**Geht die blaue Hinterlegung mancher Formeln verloren?**
|
**Geht die blaue Hinterlegung mancher Formeln verloren?**
|
||||||
Ja. Das Bild hatte die Farbe eingebaut, LaTeX-Formeln haben keinen Hintergrund.
|
Ja. Das Bild hatte die Farbe eingebaut, LaTeX-Formeln haben keinen Hintergrund.
|
||||||
|
|
||||||
**Werden Buchinhalte ins Internet geschickt?**
|
|
||||||
Nein, mit der Standardregel bleibt alles lokal. Nur die frühere Regel `vlm3` schickt die Formelbilder, bei
|
|
||||||
denen Qwen und Gemma einig sind, samt eines kurzen Textausschnitts um die Formel (höchstens 240 Zeichen)
|
|
||||||
an OpenRouter/GLM.
|
|
||||||
|
|
|
||||||
247
CHANGELOG.md
247
CHANGELOG.md
|
|
@ -1,136 +1,151 @@
|
||||||
# Changelog
|
# Änderungsprotokoll
|
||||||
|
|
||||||
## Unveröffentlicht
|
Alle nennenswerten Änderungen an formula_img2latex. Format nach
|
||||||
|
[Keep a Changelog](https://keepachangelog.com/de/1.1.0/), Versionen nach
|
||||||
|
[Semantic Versioning](https://semver.org/lang/de/) (vor 1.0: Nebenversion = möglicher Bruch).
|
||||||
|
Messergebnisse im Einzelnen: [FUNKTIONSWEISE.md, Messungen](FUNKTIONSWEISE.md#12-messungen).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
Alle Formeln eines PDF-Buchs, auch im Fließtext, und etwa dreimal schneller.
|
Alle Formeln eines PDF-Buchs, auch im Fließtext, und etwa dreimal schneller.
|
||||||
|
|
||||||
- **pdf2md --formulas** (Nachbarprojekt): Die Formelerkennung von pix2text (MFD) findet jede Formel auf der
|
### Neu
|
||||||
Seite, eingebettet oder abgesetzt, auch in Merkkästen. Sie wird zeichengenau aus dem Text geschnitten
|
- **Formeln im Fließtext** über `pdf2md --formulas` (Nachbarprojekt): Die Formelerkennung von pix2text (MFD)
|
||||||
(300 dpi) und mit Seite, Rechteck und PDF-Text in `formulas-*.json` vermerkt. "Formel"-Boxen mit Text
|
findet jede Formel auf der Seite, auch in Merkkästen; pdf2md schneidet sie zeichengenau aus (300 dpi) und
|
||||||
werden zu Text, solche mit Kurven zu Grafiken. formula_img2latex liest die Liste
|
vermerkt Seite, Rechteck und PDF-Text in `formulas-*.json`. formula_img2latex liest die Liste
|
||||||
(`sources/pdf2md.listed`). Corral, 10 % der 9239 Formeln: **79 % automatisch** (im Text 83 %).
|
(`sources/pdf2md.listed`). Corral, Stichprobe 10 % von 9239 Formeln: 79 % automatisch (im Text 83 %).
|
||||||
- **Eingebettete Formeln:** Ein Satzzeichen am Ende wird vor dem Vergleich abgetrennt und als Text hinter
|
- Ein Satzzeichen am Ende einer Formel im Text wird vor dem Vergleich abgetrennt und als Text hinter die
|
||||||
die Formel gesetzt (`final_suffix`).
|
Formel gesetzt (`final_suffix`).
|
||||||
- **Normalform:** `\underset{…}{\lim}` = `\lim_{…}`, Gleichungsnummer am Ende = `\tag`, `\textbf` als
|
- Normalform: `\underset{…}{\lim}` = `\lim_{…}`, Gleichungsnummer am Ende = `\tag`, `\textbf` als Fettdruck,
|
||||||
Fettdruck, `\cfrac` = `\frac` (pix2text schreibt `\dfrac`), wörtliches `\n` aus Modellantworten entfällt.
|
`\cfrac` = `\frac`, wörtliches `\n` aus Modellantworten entfällt.
|
||||||
- **Tempo** (Pilot 869 Formeln: 70 → ~24 min):
|
- Schalter `pdftext` (aus): Die PDF-Textebene einer flachen Formel darf pix2text bestätigen.
|
||||||
- Markdown-Verweise und Ausschnitte nur einmal lesen (`crops/`);
|
- `LICENSE` (MIT) und `FUNKTIONSWEISE.md` (Aufbau, Datenfluss, Designentscheidungen, Grenzen).
|
||||||
- pix2text gebündelt nach Pixelbudget, auf der freiesten GPU, einmal geladen;
|
|
||||||
- Lesen und Prüfen überlappen, die Prüfprozesse starten per `forkserver`;
|
|
||||||
- Qwen mit 8 parallelen Anfragen (`llama-server --parallel 8 -c 32768`);
|
|
||||||
- pdflatex beim Entscheiden nur, wenn gleiche Bilder möglich sind;
|
|
||||||
- kein neuer Scan beim Fortsetzen;
|
|
||||||
- je Paket nur dessen Datensätze laden;
|
|
||||||
- Zeiten je Stufe in der Statuszeile.
|
|
||||||
- **Bildprüfung:** Originale höchstens 1600 px breit, über 2,5 MP nicht, Lesarten mit mehr als 2,5-fach
|
|
||||||
falschen Proportionen sofort abgelehnt. Vorher hielten einzelne Formeln den Lauf über 10 Minuten auf.
|
|
||||||
- **Gemessen und verworfen:**
|
|
||||||
- PDF-Textebene als Bestätigung: 12 von 319 widersprachen Qwen, fehlende Punkte und Indizes; Schalter
|
|
||||||
`pdftext` bleibt aus.
|
|
||||||
- pix2texts eigene Sicherheit: Fehler auch bei 1,0.
|
|
||||||
- Qwen3-VL-8B: nicht schneller als das MoE-30B-Modell (3B aktiv), etwas schlechter.
|
|
||||||
- **Offen:** Formeln in Tabellen.
|
|
||||||
|
|
||||||
## 0.4.0 – 2026-10-09
|
### Geändert
|
||||||
|
- **Tempo** (Pilot 869 Formeln: 70 → ~24 Minuten): Markdown-Verweise und Ausschnitte nur einmal lesen
|
||||||
|
(`crops/`); pix2text gebündelt nach Pixelbudget auf der freiesten GPU, einmal geladen; Lesen und Prüfen
|
||||||
|
überlappen, die Prüfprozesse starten per `forkserver`; Qwen mit 8 parallelen Anfragen; pdflatex beim
|
||||||
|
Entscheiden nur, wenn gleiche Bilder möglich sind; kein neuer Scan beim Fortsetzen; je Paket nur dessen
|
||||||
|
Datensätze laden; Zeiten je Stufe in der Statuszeile.
|
||||||
|
- Bildprüfung: Originale höchstens 1600 px breit, über 2,5 MP nicht verglichen, Lesarten mit mehr als
|
||||||
|
2,5-fach falschen Proportionen sofort abgelehnt (einzelne Formeln hielten den Lauf über 10 Minuten auf).
|
||||||
|
- Dokumentation nach dem Projektstandard gegliedert: README knapp mit Abschnitt „Documentation“,
|
||||||
|
Bedienungsanleitung mit Konfiguration, Betrieb und Fehlersuche, Hintergrund und Messungen in
|
||||||
|
`FUNKTIONSWEISE.md`.
|
||||||
|
- Hilfetexte der Kommandozeile beschreiben die Regel `p2t` statt des Pixelvergleichs; Cloud-Standard
|
||||||
|
`z-ai/glm-4.6v` statt `google/gemini-2.5-flash`.
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
- `requirements.txt` enthält `requests` (von den Modell-Engines benötigt).
|
||||||
|
|
||||||
|
Gemessen und verworfen: PDF-Textebene als Bestätigung (12 von 319 widersprachen Qwen), pix2texts eigene
|
||||||
|
Sicherheit (Fehler auch bei 1,0), Qwen3-VL-8B (nicht schneller, etwas schlechter).
|
||||||
|
|
||||||
|
### Beim Update zu tun
|
||||||
|
- `pip install -r requirements.txt`.
|
||||||
|
- Qwen-Server mit `--parallel 8 -c 32768` neu starten (BEDIENUNGSANLEITUNG, Abschnitt 2).
|
||||||
|
- Für Formeln im Fließtext: aktuelles pdf2md verwenden und das Buch mit `--formulas` neu umwandeln.
|
||||||
|
- Bestehende Arbeitsordner bleiben gültig; Lesarten werden nicht neu gefragt.
|
||||||
|
|
||||||
|
## [0.4.0] – 2026-10-09
|
||||||
|
|
||||||
Bessere Normalform: Lesarten, die sich nur in der Schreibweise unterscheiden, gelten als gleich.
|
Bessere Normalform: Lesarten, die sich nur in der Schreibweise unterscheiden, gelten als gleich.
|
||||||
Göllmann (400 Formeln mit bekannter Lösung, gleiche Lesarten): **+17 automatisch, weiterhin 0 falsch**.
|
Göllmann (400 Formeln mit bekannter Lösung, gleiche Lesarten): +17 automatisch, weiterhin 0 falsch.
|
||||||
|
|
||||||
- **Mehrzeilige Formeln:** gleich bei gleichen Zeilen mit gleichen Zeichen, egal in welchem Rahmen
|
### Neu
|
||||||
|
- **Mehrzeilige Formeln** sind gleich bei gleichen Zeilen mit gleichen Zeichen, egal in welchem Rahmen
|
||||||
(`aligned`/`gathered`/`split` mit `&` oder nur `\\`; `cases` = `\left\{array\right.` =
|
(`aligned`/`gathered`/`split` mit `&` oder nur `\\`; `cases` = `\left\{array\right.` =
|
||||||
`\left\{aligned\right.`); eine leere letzte Zeile zählt nicht. In Matrizen trennt `&` weiter Spalten.
|
`\left\{aligned\right.`); eine leere letzte Zeile zählt nicht. In Matrizen trennt `&` weiter Spalten.
|
||||||
- **Argumente nach Argumentzahl:** Eine Gruppe ist nur Argument, wenn der Befehl noch eines erwartet
|
- Weitere Gleichsetzungen: `\cdot\cdot\cdot` und `...` = `\dots`, `\root3\of{x}` = `\sqrt[3]{x}`,
|
||||||
(`t^{2}\,{\sqrt{2}}` = `t^2\sqrt{2}`, `\Delta{x_{n}}` = `\Delta x_n`).
|
|
||||||
- **Weitere Gleichsetzungen:** `\cdot\cdot\cdot` und `...` = `\dots`, `\root3\of{x}` = `\sqrt[3]{x}`,
|
|
||||||
`\left.`/`\right.` entfallen.
|
`\left.`/`\right.` entfallen.
|
||||||
- **Messung genauer:** Der Wahrheitsvergleich nutzt dieselbe Normalform; richtige mehrzeilige Lesarten
|
- **Strukturvergleich mit sympy** (`structure.py`, optional, aus): gleicher unausgewerteter
|
||||||
zählten bisher als falsch (pix2text allein richtig: Band 2 jetzt 45/50 und 47/50).
|
Ausdrucksbaum. Einschalten mit `structural` = 1 bzw. `FORMULA_STRUCTURAL=1`.
|
||||||
- **Ergebnis:** Band 1 89 % / 88 % automatisch (bisher 86 % / 85 %), Band 2 44 und 45 von 50 (bisher 39
|
- **Bild-Schiedsrichter** (`verify.image_arbiter`, aus): bei „Qwen = Gemma gegen pix2text“ entscheidet
|
||||||
und 43), 0 falsch.
|
der Bildbeleg. Einstellungen `arbiter`, `arbiter_margin`.
|
||||||
- **Strukturvergleich mit sympy** (`structure.py`, neu, optional, aus): gleicher unausgewerteter
|
- Werkzeuge: `tools/vergleich_bericht.py` (Vorher/Nachher-Seite), `tools/buch_bericht.py`
|
||||||
Ausdrucksbaum, nichts umgerechnet; Relationen nie an sympy, Zeichen durch Platzhalter geschützt.
|
(Ergebnisseite eines ganzen Buchs), `regeltest.py --refresh` und `--arbiter`.
|
||||||
Brachte vor der neuen Normalform +1 Formel, danach nichts – `structural` = 1 bzw.
|
|
||||||
`FORMULA_STRUCTURAL=1` schaltet ihn ein (`pip install sympy lark`).
|
|
||||||
- **Bild-Schiedsrichter** (`verify.image_arbiter`, gemessen, aus): bei „Qwen = Gemma gegen pix2text“
|
|
||||||
entscheidet der Bildbeleg. +11 Formeln ab Schwelle 8, aber der gemeinsame Fehler `\mathcal{E}_r`
|
|
||||||
bekam Beleg 7,2 – zu knapp. Einstellungen `arbiter`, `arbiter_margin`.
|
|
||||||
- **Prompt-Stilvorgabe** getestet und verworfen (Band 2: 44/44 statt 44/45; die Modelle schrieben nach
|
|
||||||
`\\` ein wörtliches `\n`). Prompt-Version bleibt 2, gespeicherte Lesarten bleiben gültig.
|
|
||||||
- **Werkzeuge:** `tools/vergleich_bericht.py` (Vorher/Nachher-Seite zweier Läufe), `regeltest.py
|
|
||||||
--refresh` (Engines trotz `--reuse` neu fragen) und `--arbiter`; `calibrate` misst den
|
|
||||||
Schiedsrichter (Werte je Fall, Schwellen) und listet nur strukturell gefundene Übereinstimmungen;
|
|
||||||
`tools/buch_bericht.py` (Ergebnisseite eines ganzen Buchs: jede Formel mit Original, gesetztem
|
|
||||||
LaTeX, Status, Begründung und allen Lesarten, filterbar).
|
|
||||||
- **Erstes ganzes PDF-Buch** (Corral, *Elementary Calculus*, 346 Seiten, pdf2md): 1150 Ausschnitte,
|
|
||||||
**429 automatisch ersetzt (37 %)**, 633 Review, 61 keine Formel. Gefundene und behobene Probleme:
|
|
||||||
- pix2text schreibt hier Brüche als `\cfrac`: in der Normalform = `\frac`, beim Bereinigen zu
|
|
||||||
`\dfrac` (Pandoc kennt `\cfrac` nicht und ließ die Formel als TeX-Text stehen).
|
|
||||||
- Qwen und Gemma schreiben Zeilenumbrüche als wörtliches `\n` (`\begin{aligned}\n…`): wird zu
|
|
||||||
Leerraum (`tex.fix_literal_newlines`), echte Befehle wie `\nu`, `\neq` bleiben.
|
|
||||||
- 600-dpi-Ausschnitte bis 3800 × 2700 Pixel: Qwen lehnte sie ab („400 Bad Request“, Kontext voll),
|
|
||||||
eine ganzseitige Abbildung (33 MP) brachte den Bildvergleich zum Absturz. Jetzt: Bilder für die
|
|
||||||
Modelle höchstens 1600 Pixel lang, Bildvergleich nur bis 4 MP (größte übernommene Formel: 1,7 MP).
|
|
||||||
- Fehlgeschlagene Modellanfragen werden beim nächsten Lauf wiederholt (zählten als „schon gefragt“).
|
|
||||||
- Göllmann danach unverändert: 0 falsch.
|
|
||||||
- Grenze: pdf2md schneidet in diesem Buch auch Merkkästen mit Text, Tabellen und ganze
|
|
||||||
Herleitungsblöcke als „Formel“ aus; sie bilden den Großteil des Reviews.
|
|
||||||
|
|
||||||
## 0.3.0 – 2026-10-09
|
### Geändert
|
||||||
|
- Eine Klammergruppe ist nur Argument, wenn der Befehl noch eines erwartet (`t^{2}\,{\sqrt{2}}` =
|
||||||
|
`t^2\sqrt{2}`, `\Delta{x_{n}}` = `\Delta x_n`).
|
||||||
|
- Der Wahrheitsvergleich der Messung nutzt dieselbe Normalform (richtige mehrzeilige Lesarten zählten
|
||||||
|
bisher als falsch).
|
||||||
|
- Bilder für die Modelle höchstens 1600 Pixel lang.
|
||||||
|
|
||||||
Neue Standardregel `p2t`: **pix2text liest zuerst, Qwen bestätigt, Gemma nur bei Uneinigkeit**; GLM (Cloud)
|
### Behoben
|
||||||
wird nicht mehr benutzt.
|
- pix2texts `\cfrac` wird beim Bereinigen zu `\dfrac` (Pandoc kennt `\cfrac` nicht und ließ die Formel als
|
||||||
|
TeX-Text stehen).
|
||||||
|
- Wörtliches `\n` in Antworten von Qwen und Gemma wird zu Leerraum (`tex.fix_literal_newlines`).
|
||||||
|
- Sehr große Ausschnitte: Qwen lehnte sie ab („400 Bad Request“), eine ganzseitige Abbildung brachte den
|
||||||
|
Bildvergleich zum Absturz.
|
||||||
|
- Fehlgeschlagene Modellanfragen werden beim nächsten Lauf wiederholt (zählten als „schon gefragt“).
|
||||||
|
|
||||||
|
Gemessen und verworfen: Strukturvergleich (nach der neuen Normalform kein Gewinn), Bild-Schiedsrichter
|
||||||
|
(der gemeinsame Fehler `\mathcal{E}_r` bekam Beleg 7,2), Prompt-Stilvorgabe (keine Verbesserung).
|
||||||
|
Erstes ganzes PDF-Buch: Corral, *Elementary Calculus*, 1150 Ausschnitte, 429 automatisch (37 %).
|
||||||
|
|
||||||
|
### Beim Update zu tun
|
||||||
|
- Nichts Pflichtiges. Prompt-Version bleibt 2, gespeicherte Lesarten bleiben gültig; `run` rechnet die
|
||||||
|
Entscheidungen mit der neuen Normalform neu.
|
||||||
|
- Optional für den Strukturvergleich: `pip install sympy lark`.
|
||||||
|
|
||||||
|
## [0.3.0] – 2026-10-09
|
||||||
|
|
||||||
|
Neue Standardregel `p2t`: pix2text liest zuerst, Qwen bestätigt, Gemma nur bei Uneinigkeit; GLM (Cloud) wird
|
||||||
|
nicht mehr benutzt. Göllmann Band 1: 86 % / 85 % automatisch, 0 falsch (bisher 78 % / 70 %).
|
||||||
|
|
||||||
|
### Neu
|
||||||
- **pix2text** (Formel-OCR MFR 1.5, ONNX) als Engine `p2t`, im Prozess, auf der GPU mit `onnxruntime-gpu`
|
- **pix2text** (Formel-OCR MFR 1.5, ONNX) als Engine `p2t`, im Prozess, auf der GPU mit `onnxruntime-gpu`
|
||||||
(`FORMULA_P2T_DEVICE`: `auto`, `cpu`, `cuda:N`). Allein am häufigsten richtig (Göllmann Band 1: 90 % /
|
(`FORMULA_P2T_DEVICE`: `auto`, `cpu`, `cuda:N`).
|
||||||
85 %) und am schnellsten (0,1 s/Formel auf der GPU, Qwen 0,7 s, Gemma 1,8 s).
|
- **Regel `p2t`:** übernommen wird pix2texts Lesart, wenn Qwen sie bestätigt oder – nur bei Uneinigkeit
|
||||||
- **Regel:** übernommen wird immer pix2texts Lesart (trifft den Fettdruck am besten), wenn Qwen sie
|
gefragt – Gemma. Qwen und Gemma gemeinsam gegen pix2text gehen an einen Menschen.
|
||||||
bestätigt oder – nur bei Uneinigkeit gefragt – Gemma. Qwen und Gemma gemeinsam gegen pix2text gehen
|
- `calibrate`: beliebig viele Engines (`--engines`), Regel-Tabelle, `--reuse` ohne Modellabfragen.
|
||||||
an einen Menschen (so kamen an Band 1 die gemeinsamen Fehler der beiden Bildmodelle durch). Der
|
- Werkzeuge: `tools/zeittest.py`, `tools/regeltest.py`.
|
||||||
Schlusspunkt muss bestätigt sein. Bild-Veto und Pandoc wie bisher, geprüft auf pix2texts Lesart.
|
|
||||||
Die alte Regel bleibt als `"consensus_rule": "vlm3"` in `workspace.json` wählbar.
|
|
||||||
- **pix2text-Ausgabe bereinigt:** Leerzeichen mit `post_process` aus pix2tex (installiert mit
|
|
||||||
`--no-deps`), Fettschreibweisen `\mathrm{\boldmath~ x ~}` / `\mathrm{\bf~ r o t}` →
|
|
||||||
`\boldsymbol{x}` / `\mathbf{rot}`. An 400 Lesarten geprüft: gleiches gesetztes Bild.
|
|
||||||
- **Normalform:** `\operatorname{l i m}` = `\lim`, `\ddot{u}` in Text = ü, `^{\prime}` = `'`,
|
|
||||||
abschließendes `\\` vor `\end`, `\begin{array}[t]`, `\mathop{…}`.
|
|
||||||
- **calibrate:** beliebig viele Engines (`--engines`), Regel-Tabelle, `--reuse` rechnet aus einem
|
|
||||||
früheren Bericht ohne Modellabfragen; wahre Formeln aus dem alt-Text werden bereinigt
|
|
||||||
(einzeiliges `aligned`, `\mathop{}\!\mathrm{d}`, upgreek).
|
|
||||||
- **Werkzeuge:** `tools/zeittest.py` (Zeit und Trefferquote je Tool), `tools/regeltest.py` (Standardregel
|
|
||||||
an Formeln mit bekanntem TeX, Seite zum Mitbeurteilen; `--reuse`, `--exclude`).
|
|
||||||
- **Messung:** Göllmann Band 1 (2 × 150) 86 % / 85 % automatisch, 0 falsch (bisher 78 % / 70 %);
|
|
||||||
Band 2, zwei frische Stichproben zu 50: 78 % und 86 % automatisch, 0 falsch.
|
|
||||||
|
|
||||||
## 0.2.0 – 2026-10-07
|
### Geändert
|
||||||
|
- pix2text-Ausgabe bereinigt: Leerzeichen mit `post_process` aus pix2tex, Fettschreibweisen
|
||||||
|
`\mathrm{\boldmath~ x ~}` → `\boldsymbol{x}`. An 400 Lesarten geprüft: gleiches gesetztes Bild.
|
||||||
|
- Normalform: `\operatorname{l i m}` = `\lim`, `\ddot{u}` in Text = ü, `^{\prime}` = `'`, abschließendes
|
||||||
|
`\\` vor `\end`, `\begin{array}[t]`, `\mathop{…}`.
|
||||||
|
|
||||||
Entscheider ist jetzt der **Konsens dreier Bildmodelle** statt eines Pixelvergleichs.
|
### Beim Update zu tun
|
||||||
|
- pix2text installieren: `pip install "pix2text[multilingual]"`, `pip install --no-deps pix2tex munch`,
|
||||||
|
für die GPU `pip uninstall -y onnxruntime && pip install onnxruntime-gpu`.
|
||||||
|
- Bestehende Arbeitsordner: `run` erneut aufrufen; pix2text liest nach, Qwen-Lesarten bleiben gültig.
|
||||||
|
- Wer die Regel mit GLM behalten will: `"consensus_rule": "vlm3"` in `workspace.json`.
|
||||||
|
|
||||||
- **Annahmeregel:** jedes Zeichen richtig und an der richtigen Stelle; Abstände, Längen, Schriftgröße,
|
## [0.2.0] – 2026-10-07
|
||||||
kursiv/aufrecht und Fettdruck egal.
|
|
||||||
- **Konsens:** Qwen3-VL-30B-A3B (lokal) = Gemma-4-26B-A4B (lokal) = GLM-4.6V (OpenRouter, andere
|
|
||||||
Modellfamilie); Pandoc muss die Formel umsetzen; grobes Bild-Veto (elastischer Vergleich,
|
|
||||||
Ähnlichkeit ≥ 0,65). Bei 2:1 entscheidet ein Mensch.
|
|
||||||
- **`consensus.py`:** Normalform auf Baumbasis (Abstände, `\left`/`\right`, freistehende Klammern,
|
|
||||||
`\vec`/`\overrightarrow`, `\frac`/`\dfrac`, `\iff`/`\Leftrightarrow`, Matrix-Umgebungen, TeX-Umlaute,
|
|
||||||
`\atop`/`\substack`, kursiv/aufrecht, fett/normal) und Render-Gleichheit als Rückfallebene.
|
|
||||||
- **Engines:** zweite lokale Engine `local2`; Token-Wahrscheinlichkeiten aus Logprobs; Nachdenken
|
|
||||||
abgeschaltet (Gemma ~10× schneller, GLM besser und ~25× billiger); Antworten auf 800 Tokens begrenzt;
|
|
||||||
Prompt fragt ausdrücklich nach Fettdruck; Prompt-Version und Nachdenk-Einstellung im Cache-Schlüssel.
|
|
||||||
- **`run`:** in Paketen zu 50 mit Statuszeile; beide lokalen Modelle parallel; GLM nur für Einige;
|
|
||||||
geprüft wird nur die übernommene Lesart; Prüfergebnisse mit Verfahrensversion.
|
|
||||||
- **Bildvergleich:** elastische Ausrichtung (DTW über Spalten und Zeilen, Dehnen kostet proportional zur
|
|
||||||
Tinte); layouttreue Prüfvarianten und rauschnormierter Kontrast im Pixel-Modus (`decide=pixel`).
|
|
||||||
- **apply:** deutsches Dezimalkomma `3{,}6`.
|
|
||||||
- **Behoben:** Gleichungsnummern wurden aus alten, verworfenen Lesarten übernommen (erfundene „(3.7)“);
|
|
||||||
sie kommen jetzt nur aus der übernommenen Lesart. Gleichzeitige Speicherungen zweier Engines sind
|
|
||||||
abgesichert. Verify stürzte bei Formeln ohne aktuelle Lesart ab.
|
|
||||||
- **Werkzeuge:** `calibrate --consensus`, `tools/vergleich.py` (50er-Stichprobe mit Bildern),
|
|
||||||
`tools/konsens_bericht.py`; Ausgaben in `vergleich/` (nicht versioniert).
|
|
||||||
- **Messung** (2 × 150 Formeln mit bekanntem TeX): 63–68 % automatisch, 0 falsch.
|
|
||||||
Gehrke-Brückenkurs: 942 von 1505 automatisch (63 %), 62 von 62 geprüften korrekt.
|
|
||||||
|
|
||||||
## 0.1.0 – 2026-10-07
|
Entscheider ist der Konsens dreier Bildmodelle statt eines Pixelvergleichs.
|
||||||
|
Gehrke-Brückenkurs: 942 von 1505 automatisch (63 %), 62 von 62 geprüften korrekt.
|
||||||
|
|
||||||
Erste Fassung: scan, recognize (ein Vision-Modell), verify mit Pixelvergleich und Kontrasttest gegen
|
### Neu
|
||||||
Verwechslungsvarianten, Review-Galerie, apply/revert, calibrate. Sicher (0 Falsch-Annahmen), aber an
|
- **Konsens:** Qwen3-VL-30B-A3B (lokal) = Gemma-4-26B-A4B (lokal) = GLM-4.6V (OpenRouter); Pandoc muss die
|
||||||
echten Büchern nur 1–2 % automatisch.
|
Formel umsetzen; grobes Bild-Veto (elastischer Vergleich, Ähnlichkeit ≥ 0,65).
|
||||||
|
- `consensus.py`: Normalform auf Baumbasis und Render-Gleichheit als Rückfallebene.
|
||||||
|
- Zweite lokale Engine `local2`; Token-Wahrscheinlichkeiten aus Logprobs.
|
||||||
|
- `run` in Paketen zu 50 mit Statuszeile; `calibrate --consensus`; `tools/vergleich.py`,
|
||||||
|
`tools/konsens_bericht.py`.
|
||||||
|
- `apply`: deutsches Dezimalkomma `3{,}6`.
|
||||||
|
|
||||||
|
### Geändert
|
||||||
|
- Nachdenken der Modelle abgeschaltet (Gemma ~10× schneller, GLM besser und ~25× billiger); Antworten auf
|
||||||
|
800 Tokens begrenzt; Prompt fragt ausdrücklich nach Fettdruck.
|
||||||
|
- Elastischer Bildvergleich (DTW über Spalten und Zeilen, Dehnen kostet proportional zur Tinte).
|
||||||
|
- Geprüft wird nur die übernommene Lesart; Prüfergebnisse tragen eine Verfahrensversion.
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
- Gleichungsnummern wurden aus alten, verworfenen Lesarten übernommen (erfundene „(3.7)“).
|
||||||
|
- Gleichzeitiges Speichern zweier Engines; Absturz bei Formeln ohne aktuelle Lesart.
|
||||||
|
|
||||||
|
### Beim Update zu tun
|
||||||
|
- Zwei llama.cpp-Server starten (Qwen auf Port 8011, Gemma auf Port 8012) und Gemma laden
|
||||||
|
(BEDIENUNGSANLEITUNG, Abschnitte 1 und 2); `OPENROUTER_API_KEY` setzen.
|
||||||
|
- Arbeitsordner aus 0.1.0: Standard ist jetzt der Konsens; den alten Entscheider behält
|
||||||
|
`"decide": "pixel"` in `workspace.json`.
|
||||||
|
|
||||||
|
## [0.1.0] – 2026-10-07
|
||||||
|
|
||||||
|
Erstveröffentlichung: scan, recognize (ein Bildmodell), verify mit Pixelvergleich und Kontrasttest gegen
|
||||||
|
Verwechslungsvarianten, Review-Galerie, apply/revert, calibrate. Sicher (0 Falsch-Annahmen), aber an echten
|
||||||
|
Büchern nur 1–2 % automatisch.
|
||||||
|
|
|
||||||
432
FUNKTIONSWEISE.md
Normal file
432
FUNKTIONSWEISE.md
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
# 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](BEDIENUNGSANLEITUNG.md), für Änderungen je Version
|
||||||
|
[CHANGELOG.md](CHANGELOG.md).
|
||||||
|
|
||||||
|
Stand: Version 0.4.0 und unveröffentlichte Änderungen (siehe CHANGELOG, Abschnitt `[Unreleased]`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inhaltsverzeichnis
|
||||||
|
|
||||||
|
1. [Überblick](#1-überblick)
|
||||||
|
2. [Module](#2-module)
|
||||||
|
3. [Arbeitsordner und Datensätze](#3-arbeitsordner-und-datensätze)
|
||||||
|
4. [Ablauf von `run`](#4-ablauf-von-run)
|
||||||
|
5. [Die Erkenner](#5-die-erkenner)
|
||||||
|
6. [Entscheidungsregel](#6-entscheidungsregel)
|
||||||
|
7. [Wann zwei Lesarten dieselbe Formel sind](#7-wann-zwei-lesarten-dieselbe-formel-sind)
|
||||||
|
8. [Bild-Veto: der elastische Bildvergleich](#8-bild-veto-der-elastische-bildvergleich)
|
||||||
|
9. [apply und revert](#9-apply-und-revert)
|
||||||
|
10. [Quellen: epub2md und pdf2md](#10-quellen-epub2md-und-pdf2md)
|
||||||
|
11. [Sicherheit](#11-sicherheit)
|
||||||
|
12. [Messungen](#12-messungen)
|
||||||
|
13. [Designentscheidungen](#13-designentscheidungen)
|
||||||
|
14. [Grenzen](#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.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```text
|
||||||
|
<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.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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](#6-entscheidungsregel).
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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](#12-messungen)) 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 | – |
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Dieter Schlüter
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
377
README.md
377
README.md
|
|
@ -3,357 +3,56 @@
|
||||||
Nachbearbeitungs-Werkzeug für die Markdown-Ausgaben von
|
Nachbearbeitungs-Werkzeug für die Markdown-Ausgaben von
|
||||||
[epub2md](https://kitux.de/forgejo/dschlueter/epub2md) und
|
[epub2md](https://kitux.de/forgejo/dschlueter/epub2md) und
|
||||||
[pdf2md](https://kitux.de/forgejo/dschlueter/pdf2md): Formeln, die nur als Bild vorliegen, werden durch
|
[pdf2md](https://kitux.de/forgejo/dschlueter/pdf2md): Formeln, die nur als Bild vorliegen, werden durch
|
||||||
LaTeX ersetzt – **aber nur, wenn zwei unabhängige Erkenner dieselbe Formel lesen** (zuerst das Formel-OCR
|
LaTeX ersetzt. Beim Neubau als EPUB oder PDF passen sie dann in Größe und Schrift zum Text.
|
||||||
pix2text, bestätigt vom Bildmodell Qwen; bei Uneinigkeit darf Gemma pix2text bestätigen) und ein grober
|
|
||||||
Bildabgleich nicht widerspricht. Alles andere geht in eine Review-Galerie oder bleibt Bild.
|
|
||||||
|
|
||||||
Für PDF-Bücher findet `pdf2md --formulas` jede Formel auf der Seite, auch im Fließtext und in Merkkästen,
|
- **Nur bestätigte Formeln:** Das Formel-OCR **pix2text** liest jede Formel, das Bildmodell **Qwen3-VL**
|
||||||
und schneidet sie als Bild aus; formula_img2latex übersetzt sie dann ebenso (siehe [Quellen](#quellen)).
|
muss dieselbe Formel lesen (bei Uneinigkeit darf **Gemma 4** pix2text bestätigen), und ein grober
|
||||||
|
Bildabgleich darf nicht widersprechen. Alles andere geht in eine Review-Galerie oder bleibt Bild.
|
||||||
|
- **Annahmeregel:** jedes Zeichen richtig und an der richtigen Stelle; Abstände, Längen, Schriftgröße,
|
||||||
|
kursiv/aufrecht und Fettdruck dürfen abweichen.
|
||||||
|
- **Lokal:** pix2text im Prozess, Qwen und Gemma über llama.cpp; keine Buchinhalte verlassen den Rechner.
|
||||||
|
- **Wiederholbar und umkehrbar:** Ergebnisse liegen in einem Arbeitsordner neben der Ausgabe, `apply`
|
||||||
|
schreibt aus einer Sicherung, `revert` stellt den alten Stand wieder her.
|
||||||
|
- **PDF-Bücher:** Mit `pdf2md --formulas` werden auch Formeln im Fließtext und in Merkkästen erfasst.
|
||||||
|
|
||||||
Schritt-für-Schritt-Anleitung: [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md). Änderungen je Version:
|
## Schnellstart
|
||||||
[CHANGELOG.md](CHANGELOG.md). Hintergrund und ursprüngliche Planung: `Idee/`.
|
|
||||||
|
|
||||||
## Annahmeregel
|
Voraussetzungen und das Starten der Modellserver: [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md).
|
||||||
|
|
||||||
> 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 (Regel `p2t`, Standard), wenn
|
|
||||||
|
|
||||||
1. **pix2text** (Formel-OCR MFR 1.5, lokal, GPU) sie zuerst liest und **Qwen3-VL-30B-A3B** (Bildmodell,
|
|
||||||
lokal, llama.cpp) dieselbe Formel liest **oder**, wenn die beiden uneinig sind, **Gemma-4-26B-A4B**
|
|
||||||
(lokal) die Lesart von pix2text bestätigt. Übernommen wird immer **pix2texts Lesart** – sie trifft den
|
|
||||||
Fettdruck am besten. Ihre überflüssigen Leerzeichen entfernt vorher `post_process` aus pix2tex (LaTeX-OCR):
|
|
||||||
`\frac{\alpha} {3 6 0}` → `\frac{\alpha}{360}`; am gesetzten Bild ändert das nichts, geprüft an
|
|
||||||
400 Lesarten. Auch der Schlusspunkt muss bestätigt sein,
|
|
||||||
2. Pandoc sie nach MathML umsetzen kann (sonst bliebe sie im EPUB/PDF roher TeX-Text) und
|
|
||||||
3. das gerenderte LaTeX dem Original grob ähnelt (Bild-Veto, elastischer Vergleich, Ähnlichkeit ≥ 0,65).
|
|
||||||
|
|
||||||
Sind Qwen und Gemma gemeinsam gegen pix2text einig, entscheidet ein Mensch: Genau das sind die Fehler,
|
|
||||||
die beide Bildmodelle teilen (`\mathcal{E}_r` statt `\varepsilon_r`, erfundenes `\tilde{z}`).
|
|
||||||
|
|
||||||
Die frühere Regel `vlm3` (Qwen = Gemma, bestätigt von **GLM-4.6V** über OpenRouter) bleibt wählbar:
|
|
||||||
`"consensus_rule": "vlm3"` unter `settings` in `workspace.json` des Arbeitsordners.
|
|
||||||
|
|
||||||
„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`, `\cdot\cdot\cdot`/`\cdots`, `\root3\of{x}`/`\sqrt[3]{x}`, 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.
|
|
||||||
|
|
||||||
**Mehrzeilige Formeln** (seit 0.4.0): gleich, wenn sie dieselben Zeilen mit denselben Zeichen haben,
|
|
||||||
egal in welchem Rahmen. `aligned`/`gathered`/`split` und ihr `&` fallen weg (`a\\b` =
|
|
||||||
`\begin{aligned}a&\\b\end{aligned}`), eine leere letzte Zeile zählt nicht; `cases`,
|
|
||||||
`\left\{\begin{array}{ll}…\right.` und pix2texts `\left\{\begin{aligned}…\right.` sind dasselbe.
|
|
||||||
Matrizen bleiben Matrizen: Dort trennt `&` Spalten (`1&2` ≠ `12`). Ob eine Gruppe ein Argument ist,
|
|
||||||
entscheidet die Argumentzahl des Befehls (`\frac` 2, `\sqrt` 1, `\Delta` 0), nicht ihre Stellung:
|
|
||||||
`t^{2}\,{\sqrt{2}}` = `t^2\sqrt{2}`, `\Delta{x_{n}}` = `\Delta x_n`.
|
|
||||||
|
|
||||||
**Gleiche Struktur** (`structure.py`, sympy, **optional, standardmäßig aus**): Beide Lesarten ergeben
|
|
||||||
denselben *unausgewerteten* Ausdrucksbaum (`\frac{a}{b}` = `a/b`, `a\cdot b` = `ab`); nichts wird
|
|
||||||
umgerechnet oder umgestellt (`a+b` ≠ `b+a`, `2x=4` ≠ `x=2`, `a-b` ≠ `a+(-b)`). Relationen gehen nie an
|
|
||||||
sympy (es wertet `v=(3,4)ᵀ` und `\nu=(3,4)ᵀ` beide zu `False` aus), jedes Zeichen wird durch einen
|
|
||||||
Platzhalter geschützt (sympy macht aus `\varepsilon` und `\epsilon` ein Symbol), sichtbare Klammern
|
|
||||||
zählen, Ungedecktes fällt auf den Render-Vergleich zurück. Gemessen: vor der mehrzeiligen Normalform
|
|
||||||
+1 Formel bei 400, danach **nichts mehr** – deshalb aus. Einschalten: `structural` = 1 oder
|
|
||||||
`FORMULA_STRUCTURAL=1`; braucht `pip install sympy lark`.
|
|
||||||
|
|
||||||
Sind sich die Modelle uneinig oder widerspricht das Veto, entscheidet ein Mensch im Review.
|
|
||||||
|
|
||||||
## Messung
|
|
||||||
|
|
||||||
### Regel `p2t` (Standard)
|
|
||||||
|
|
||||||
Göllmann Band 1, 150 echte Bilder und 150 Nachbildungen im Gehrke-Stil, Lesarten aller Modelle:
|
|
||||||
|
|
||||||
| Regel | echt: automatisch / falsch | Gehrke-Stil: automatisch / falsch |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| pix2text = Qwen | 81 % / 0 | 76 % / 0 |
|
|
||||||
| + Stichwahl: Gemma bestätigt pix2text, Qwens Lesart (erste Fassung) | 85 % / 0 | 80 % / 0 |
|
|
||||||
| **pix2text zuerst und übernommen, bereinigt, Stichwahl Gemma** (Standard) | **86 % / 0** | **85 % / 0** |
|
|
||||||
| + Stichwahl auch Qwen = Gemma gegen pix2text | 92 % / 1 | 91 % / 2 |
|
|
||||||
| Qwen = Gemma = GLM (Regel `vlm3`) | 78 % / 0 | 70 % / 0 |
|
|
||||||
|
|
||||||
Ab 0.4.0 mit mehrzeiliger Normalform, gleiche Lesarten (`--reuse`): **89 % / 0** (134) und **88 % / 0**
|
|
||||||
(132); Band 2 (`tools/regeltest.py`, zwei Stichproben zu 50): **44 und 45** automatisch statt 39 und 43,
|
|
||||||
**0 falsch**, 6 bzw. 5 Review. Zusammen +17 Formeln bei 400. Die Tabelle und die folgenden Absätze
|
|
||||||
zeigen den Stand 0.3.0.
|
|
||||||
|
|
||||||
Allein richtig: pix2text 90 % / 85 %, Qwen 86 % / 84 %, Gemma 86 % / 80 %, GLM 86 % / 83 %.
|
|
||||||
Zeit je Formel (seriell, 15 Formeln): pix2text 0,10 s (GPU) bzw. 0,51 s (CPU), Qwen 0,72 s, GLM 1,19 s,
|
|
||||||
Gemma 1,77 s. Gegenprobe an Göllmann Band 2 (`tools/regeltest.py`, zwei getrennte Stichproben zu 50
|
|
||||||
Formeln): 39 bzw. 43 automatisch (78 % bzw. 86 %), **0 falsch**, 11 bzw. 7 Review; Gemma wurde für 14
|
|
||||||
bzw. 9 Formeln gefragt. Lesen je Formel im Mittel 2,3 bzw. 1,5 s (alle Tools nacheinander, ohne Bildprüfung).
|
|
||||||
pix2text ist nicht unfehlbar: In den Reviews standen auch Fälle, in denen nur pix2text sich verlesen hatte
|
|
||||||
(fehlende Wurzelexponenten, zerfallene Vektoren, `\nu` statt fettem v) – deshalb bleibt die Bestätigung
|
|
||||||
durch Qwen Pflicht.
|
|
||||||
|
|
||||||
**Bild-Schiedsrichter** (0.4.0, gemessen, **aus**): Bei „Qwen = Gemma gegen pix2text“ misst
|
|
||||||
`verify.image_arbiter`, wie deutlich das Original Qwens Lesart zeigt (gleiches Kontrastmaß wie der
|
|
||||||
Variantentest). In 16 solchen Fällen (Band 1 und 2) hatte Qwen 14-mal recht, mit Belegen von 3 bis 50.
|
|
||||||
Aber: Beim gemeinsamen Fehler `\mathcal{E}_r` statt `\varepsilon_r` sprach das Bild mit 7,2 sogar für
|
|
||||||
die falsche Lesart. Ab Schwelle 8 wären es +11 Formeln bei 0 falsch – der Abstand ist bei zwei
|
|
||||||
Gegenbeispielen nicht belastbar. Einschalten: `arbiter` = 1, `arbiter_margin` (5).
|
|
||||||
|
|
||||||
**Prompt-Stilvorgabe** (0.4.0, gemessen, verworfen): feste Schreibweisen für `aligned`, `pmatrix`,
|
|
||||||
`cases`, `\dots`, `\text`. Band 2: 44 und 44 statt 44 und 45 – die Normalform gleicht die Schreibweisen
|
|
||||||
schon aus, und mit der Zeilenvorgabe schrieben beide Modelle nach `\\` ein wörtliches `\n`.
|
|
||||||
|
|
||||||
### Regel `vlm3` (Standard bis 0.2.0)
|
|
||||||
|
|
||||||
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: pix2text, dann Qwen lesen, prüfen, Gemma nur für Uneinige, prüfen;
|
|
||||||
nach jedem Paket eine Statuszeile mit Zeiten je Stufe (Regel vlm3: Qwen + Gemma, GLM nur
|
|
||||||
für Einige). Die GPUs lesen schon das nächste Paket, während die CPU das vorige prüft.
|
|
||||||
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, p2t) 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
|
|
||||||
```
|
|
||||||
|
|
||||||
PDF-Buch mit allen Formeln, auch im Fließtext:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ~/Python_Programs/pdf2md
|
|
||||||
python3 pdf2md.py --formulas --epub buch.pdf Output/buch/buch.md # Formeln als Bilder + Liste
|
|
||||||
cd ~/Python_Programs/formula_img2latex
|
|
||||||
python3 formula_img2latex.py run ~/Python_Programs/pdf2md/Output/buch/buch.md --pdf buch.pdf
|
|
||||||
python3 formula_img2latex.py apply ~/Python_Programs/pdf2md/Output/buch/buch.md
|
|
||||||
cd ~/Python_Programs/pdf2md && python3 pdf2md.py --from-md Output/buch/buch.md # EPUB mit MathML
|
|
||||||
```
|
|
||||||
|
|
||||||
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), 8 parallele Anfragen | `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` (nur Regel `vlm3`) | `OPENROUTER_API_KEY`, `FORMULA_CLOUD_MODEL` |
|
|
||||||
| `p2t` | pix2text MFR 1.5 (ONNX) im Prozess, die sichtbare GPU mit dem meisten freien Speicher, sonst CPU; gebündelt nach Pixelbudget | `FORMULA_P2T_DEVICE` (`auto`, `cpu`, `cuda:N`), `FORMULA_P2T_MODEL` |
|
|
||||||
|
|
||||||
pix2text auf der GPU braucht `onnxruntime-gpu` statt `onnxruntime` (cuDNN kommt aus den nvidia-Paketen
|
|
||||||
von torch). Für `post_process` wird `pix2tex` ohne seine Abhängigkeiten installiert
|
|
||||||
(`pip install --no-deps pix2tex munch`), weil es sonst `timm==0.5.4` erzwingen würde. `cuda:N` zählt innerhalb von `CUDA_VISIBLE_DEVICES`.
|
|
||||||
|
|
||||||
`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.
|
|
||||||
|
|
||||||
Qwen-Server mit `--parallel 8 -c 32768` (je Platz 4096 Tokens) starten; `run` stellt bis zu 8 Anfragen
|
|
||||||
gleichzeitig (BEDIENUNGSANLEITUNG). Gemessen und verworfen: Qwen3-VL-8B statt 30B-A3B ist nicht schneller
|
|
||||||
(das MoE-Modell rechnet je Token nur ~3B Parameter) und liest etwas schlechter (Göllmann 42/44 statt 44/45
|
|
||||||
automatisch).
|
|
||||||
|
|
||||||
Kosten der Cloud-Stufe (nur Regel `vlm3`): 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 werden kann: die von pix2text (Regel `vlm3`: die von Qwen). Bestätigende
|
|
||||||
Lesarten werden nie übernommen und brauchen keine Prüfung. 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
|
|
||||||
├── crops/<id>.png # Original je Formel, einmal ausgeschnitten (PDF-Quellen)
|
|
||||||
├── review/<id>-{orig,render,rigid,diff}.png
|
|
||||||
├── bericht/ # Ergebnisseite (tools/buch_bericht.py)
|
|
||||||
└── 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), `structural` (0), `arbiter` (0), `arbiter_margin` (5), `veto_ncc` (0,65),
|
|
||||||
`veto_scale_dev` (0 = aus), `min_p_mean`/`min_p_min` (0), `pdftext` (0: die PDF-Textebene bestätigt nie;
|
|
||||||
gemessen: in 12 von 319 Fällen fehlten dort Punkte, Striche oder Indizes, die Qwen sah).
|
|
||||||
|
|
||||||
## 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 | `$…$`/`$$…$$` |
|
|
||||||
| pdf2md `--formulas` | Bildname `<pdf>-<seite>-f<nr>.png` (300 dpi), Liste `<name>_images/formulas-*.json` mit Seite, Rechteck, Art (im Text/abgesetzt) und PDF-Text; das Bild wird direkt verwendet | `$…$`/`$$…$$` |
|
|
||||||
|
|
||||||
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 (statt Glyphensalat wie `x4n`).
|
|
||||||
Ein Satzzeichen am Ende einer eingebetteten Formel wird vor dem Vergleich abgetrennt und als Text hinter
|
|
||||||
die Formel gesetzt (`$a^{x}$,`). Codeblöcke werden nie verändert. Formeln in Tabellen erfasst die
|
|
||||||
Erkennung noch nicht.
|
|
||||||
|
|
||||||
## 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`.
|
|
||||||
- `tools/zeittest.py EPUB` – Zeit und Trefferquote je Tool, seriell an 15 Formeln mit bekanntem TeX.
|
|
||||||
- `tools/regeltest.py EPUB [--n 50] [--reuse ergebnis.json] [--refresh local,local2] [--arbiter 8]` – die
|
|
||||||
Standardregel an Formeln mit bekanntem TeX, wie `run`; Seite mit Original, wahrer Formel, allen
|
|
||||||
Lesarten und Entscheidung. `--refresh` fragt trotz `--reuse` diese Engines neu (Prompt-Test).
|
|
||||||
- `tools/buch_bericht.py AUSGABE` – Ergebnisseite eines ganzen Buchs nach `run`: jede Formel mit
|
|
||||||
Original, gesetztem LaTeX, Status, Begründung und allen Lesarten, filterbar; liegt in
|
|
||||||
`<arbeitsordner>/bericht/index.html` (Buchausschnitte, nicht versioniert).
|
|
||||||
- `tools/vergleich_bericht.py --regeltest VORHER:NACHHER --kalibrierung VORHER.json:NACHHER.json` –
|
|
||||||
Vorher/Nachher-Seite zweier Läufe auf denselben Lesarten: Zahlen, jede geänderte Entscheidung und
|
|
||||||
jedes geänderte Urteil gegen die Wahrheit, mit Original und gerenderten Lesarten.
|
|
||||||
- `calibrate --consensus --engines local,local2,cloud,p2t --report X.json` misst alle Regeln;
|
|
||||||
`--reuse X.json` rechnet sie ohne Modellabfragen neu.
|
|
||||||
- `vergleich/` ist nicht versioniert (Ausschnitte aus Büchern).
|
|
||||||
|
|
||||||
## Stand
|
|
||||||
|
|
||||||
**Formeln im Fließtext** (unveröffentlicht, nach 0.4.0): Corral mit `pdf2md --formulas`: 9239 Formelbilder
|
|
||||||
(davon ~88 % eingebettet). Stichprobe 10 % (940): **747 automatisch (79 %)**, im Text 83 %, abgesetzte
|
|
||||||
Box-Bilder (Herleitungen, Kästen) nur ~29 %; 174 Review, 19 keine Formel; 13 Minuten, hochgerechnet ~2 h
|
|
||||||
für das ganze Buch. Beschleunigung am Pilot (869 Formeln): 70 → ~24 Minuten, Entscheidungen zu 863/876
|
|
||||||
gleich (die Abweichungen kommen von den Modellen selbst). Qualität ist hier ohne bekannte Lösung –
|
|
||||||
vor dem Einsatz eine Stichprobe der übernommenen Formeln von Hand prüfen.
|
|
||||||
|
|
||||||
Version 0.4.0 (Regel `p2t`, mehrzeilige Normalform) ist an Göllmann Band 1 und 2 gemessen (siehe
|
|
||||||
[Messung](#messung)), aber noch **nicht am ganzen Gehrke** gelaufen.
|
|
||||||
|
|
||||||
**Erstes ganzes PDF-Buch** (0.4.0): Corral, *Elementary Calculus* (346 Seiten, TeX-PDF, pdf2md mit
|
|
||||||
600-dpi-Neuausschnitt): 1150 Ausschnitte → **429 automatisch ersetzt (37 %)**, 633 Review, 61 keine
|
|
||||||
Formel, 27 Abbildungen. EPUB (`pdf2md --from-md`, jetzt mit `--mathml`): 429 Formeln als MathML, kein
|
|
||||||
TeX-Rest. Laufzeit rund 4 Stunden (pix2text Ø 3,9 s, Qwen Ø 8,1 s, Gemma Ø 15 s je Anfrage – große
|
|
||||||
Ausschnitte). Das Review besteht überwiegend aus Ausschnitten, die keine einzelne Formel sind
|
|
||||||
(Merkkästen mit Text, Tabellen, Diagramme, ganze Herleitungen) – das muss pdf2md besser trennen. Die folgenden Zahlen stammen von Version 0.2.0 (Regel `vlm3`, mit GLM).
|
|
||||||
|
|
||||||
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: Rahmen und `&` gleicht die Normalform seit 0.4.0 aus; verschiedene
|
|
||||||
Zeilenumbrüche, `\underbrace` und fehlende Integrationsgrenzen bleiben im Review.
|
|
||||||
- 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: am ganzen Corral getestet (ohne `--formulas` 0.4.0, mit `--formulas` 10 %); Formeln in
|
|
||||||
Tabellen fehlen noch.
|
|
||||||
- Einzelne Riesenausschnitte (Grafiken, dreizeilige Herleitungen mit völlig anderer Lesart) werden ohne
|
|
||||||
Bildvergleich abgelehnt: über 2,5 MP oder mehr als 2,5-fach falsche Proportionen; breite Originale werden
|
|
||||||
für den Vergleich auf 1600 px verkleinert.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install texlive-latex-extra texlive-fonts-extra texlive-science pandoc dvipng
|
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.txt
|
||||||
pip install -r requirements-dev.txt # pytest
|
pip install "pix2text[multilingual]" && pip install --no-deps pix2tex munch
|
||||||
|
|
||||||
|
python3 formula_img2latex.py run ~/Python_Programs/epub2md/output/buch # lesen, prüfen, entscheiden
|
||||||
|
python3 formula_img2latex.py review ~/Python_Programs/epub2md/output/buch # Zweifelsfälle im Browser
|
||||||
|
python3 formula_img2latex.py apply ~/Python_Programs/epub2md/output/buch # Formeln ins Markdown schreiben
|
||||||
```
|
```
|
||||||
|
|
||||||
Modelle: siehe BEDIENUNGSANLEITUNG (llama-server starten).
|
## Stand
|
||||||
|
|
||||||
## Tests
|
Version 0.4.0 plus unveröffentlichte Änderungen (siehe [CHANGELOG.md](CHANGELOG.md)). An Formelbildern
|
||||||
|
mit bekannter Lösung (Göllmann, Springer) werden 88–89 % automatisch ersetzt, **keine davon falsch**.
|
||||||
|
Ganze Bücher: Gehrke-Brückenkurs (EPUB) 63 %, alle 62 von Hand geprüften korrekt; Corral mit
|
||||||
|
`pdf2md --formulas` 79 % (Stichprobe). Einzelheiten: [FUNKTIONSWEISE.md, Messungen](FUNKTIONSWEISE.md#12-messungen).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| Dokument | Inhalt |
|
||||||
|
|---|---|
|
||||||
|
| [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md) | Installation, Modellserver, ein Buch bearbeiten, Konfiguration, Betrieb, Fehlersuche |
|
||||||
|
| [FUNKTIONSWEISE.md](FUNKTIONSWEISE.md) | Aufbau, Datenfluss, Entscheidungsregel, Normalform, Bildabgleich, Messungen, Designentscheidungen, Grenzen |
|
||||||
|
| [CHANGELOG.md](CHANGELOG.md) | Änderungen je Version und was beim Update zu tun ist |
|
||||||
|
| [LICENSE](LICENSE) | MIT |
|
||||||
|
| `Idee/` | ursprüngliche Problemstellung und Planung |
|
||||||
|
|
||||||
|
## Entwicklung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
pip install -r requirements-dev.txt
|
||||||
python3 -m pytest -q # ca. 45 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,
|
## License
|
||||||
`3` externes Werkzeug fehlt (pdflatex, pandoc, Modellserver), `4` interner Fehler.
|
|
||||||
|
|
||||||
## Architektur
|
MIT, siehe [LICENSE](LICENSE).
|
||||||
|
|
||||||
```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;
|
|
||||||
│ p2t.py: pix2text-Formel-OCR (ONNX, CPU/GPU)
|
|
||||||
├── 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)
|
|
||||||
├── structure.py Strukturvergleich mit sympy (optional, aus)
|
|
||||||
├── 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
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@ from .render import PRESETS
|
||||||
|
|
||||||
DESCRIPTION = """\
|
DESCRIPTION = """\
|
||||||
Ersetzt Formelbilder in Markdown-Ausgaben von epub2md und pdf2md durch LaTeX – aber nur, wenn
|
Ersetzt Formelbilder in Markdown-Ausgaben von epub2md und pdf2md durch LaTeX – aber nur, wenn
|
||||||
das gerenderte LaTeX nachweislich so aussieht wie das Original. Zweifelsfälle gehen in eine
|
zwei Erkenner dieselbe Formel lesen (pix2text, bestätigt von Qwen oder bei Uneinigkeit von Gemma)
|
||||||
Review-Galerie, alles andere bleibt Bild.
|
und ein grober Bildabgleich nicht widerspricht. Zweifelsfälle gehen in eine Review-Galerie,
|
||||||
|
alles andere bleibt Bild.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
EPILOG = """\
|
EPILOG = """\
|
||||||
Typischer Ablauf:
|
Typischer Ablauf:
|
||||||
formula_img2latex.py run output/buch # scan, erkennen (lokal, dann Cloud), prüfen
|
formula_img2latex.py run output/buch # scan, lesen (pix2text, Qwen, Gemma), prüfen
|
||||||
formula_img2latex.py review output/buch # Zweifelsfälle im Browser entscheiden
|
formula_img2latex.py review output/buch # Zweifelsfälle im Browser entscheiden
|
||||||
formula_img2latex.py apply output/buch # geprüfte Formeln ins Markdown schreiben
|
formula_img2latex.py apply output/buch # geprüfte Formeln ins Markdown schreiben
|
||||||
formula_img2latex.py revert output/buch # alles rückgängig machen
|
formula_img2latex.py revert output/buch # alles rückgängig machen
|
||||||
|
|
@ -56,10 +57,10 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
sp.add_argument("--quiet", "-q", action="store_true", help="nur Fehler ausgeben")
|
sp.add_argument("--quiet", "-q", action="store_true", help="nur Fehler ausgeben")
|
||||||
|
|
||||||
def recog_opts(sp: argparse.ArgumentParser) -> None:
|
def recog_opts(sp: argparse.ArgumentParser) -> None:
|
||||||
sp.add_argument("--model", default=None, help="Modellname (Standard: local = Modell des Servers, "
|
sp.add_argument("--model", default=None, help="Modellname (Standard: local/local2 = Modell des Servers, "
|
||||||
"cloud = $FORMULA_CLOUD_MODEL oder google/gemini-2.5-flash)")
|
"cloud = $FORMULA_CLOUD_MODEL oder z-ai/glm-4.6v, p2t = mfr-1.5)")
|
||||||
sp.add_argument("--url", default=None, help="API-Basis-URL (Standard: local http://127.0.0.1:8011/v1, "
|
sp.add_argument("--url", default=None, help="API-Basis-URL (Standard: local http://127.0.0.1:8011/v1, "
|
||||||
"cloud https://openrouter.ai/api/v1)")
|
"local2 http://127.0.0.1:8012/v1, cloud https://openrouter.ai/api/v1)")
|
||||||
sp.add_argument("--include-figures", action="store_true",
|
sp.add_argument("--include-figures", action="store_true",
|
||||||
help="auch Bilder versuchen, die wie Abbildungen aussehen")
|
help="auch Bilder versuchen, die wie Abbildungen aussehen")
|
||||||
sp.add_argument("--limit", type=int, default=0, help="höchstens N Bilder (zum Ausprobieren)")
|
sp.add_argument("--limit", type=int, default=0, help="höchstens N Bilder (zum Ausprobieren)")
|
||||||
|
|
@ -70,7 +71,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
sp.add_argument("--pdf", type=Path, default=None,
|
sp.add_argument("--pdf", type=Path, default=None,
|
||||||
help="pdf2md: Original-PDF, um Formeln scharf neu auszuschneiden")
|
help="pdf2md: Original-PDF, um Formeln scharf neu auszuschneiden")
|
||||||
|
|
||||||
sp = sub.add_parser("recognize", help="LaTeX-Kandidaten mit einem Vision-Modell erzeugen")
|
sp = sub.add_parser("recognize", help="Lesarten einer Engine erzeugen (p2t, local, local2, cloud)")
|
||||||
common(sp)
|
common(sp)
|
||||||
recog_opts(sp)
|
recog_opts(sp)
|
||||||
sp.add_argument("--engine", choices=["local", "local2", "cloud", "p2t"], default="local")
|
sp.add_argument("--engine", choices=["local", "local2", "cloud", "p2t"], default="local")
|
||||||
|
|
@ -85,13 +86,13 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
sp.add_argument("--include-figures", action="store_true")
|
sp.add_argument("--include-figures", action="store_true")
|
||||||
sp.add_argument("--redo", action="store_true", help="auch bereits geprüfte Formeln neu prüfen")
|
sp.add_argument("--redo", action="store_true", help="auch bereits geprüfte Formeln neu prüfen")
|
||||||
|
|
||||||
sp = sub.add_parser("run", help="scan + recognize (lokal, dann Cloud) + verify")
|
sp = sub.add_parser("run", help="scan + lesen + prüfen + entscheiden, in Paketen mit Statuszeile")
|
||||||
common(sp)
|
common(sp)
|
||||||
recog_opts(sp)
|
recog_opts(sp)
|
||||||
sp.add_argument("--pdf", type=Path, default=None)
|
sp.add_argument("--pdf", type=Path, default=None)
|
||||||
sp.add_argument("--cloud-model", default=None, help="Modell für die Cloud-Stufe")
|
sp.add_argument("--cloud-model", default=None, help="Regel vlm3: Modell für die Cloud-Stufe")
|
||||||
sp.add_argument("--no-local", action="store_true", help="lokale Stufe überspringen")
|
sp.add_argument("--no-local", action="store_true", help="nur Modus decide=pixel: lokale Stufe überspringen")
|
||||||
sp.add_argument("--no-cloud", action="store_true", help="Cloud-Stufe überspringen")
|
sp.add_argument("--no-cloud", action="store_true", help="Regel vlm3 bzw. decide=pixel: Cloud-Stufe überspringen")
|
||||||
sp.add_argument("--preset", choices=["auto", *PRESETS], default="auto")
|
sp.add_argument("--preset", choices=["auto", *PRESETS], default="auto")
|
||||||
sp.add_argument("--chunk", type=int, default=50, help="Paketgröße; nach jedem Paket eine Statuszeile (Standard 50)")
|
sp.add_argument("--chunk", type=int, default=50, help="Paketgröße; nach jedem Paket eine Statuszeile (Standard 50)")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,6 @@ scikit-image>=0.22
|
||||||
opencv-python-headless>=4.8
|
opencv-python-headless>=4.8
|
||||||
Pillow>=10.0
|
Pillow>=10.0
|
||||||
lxml>=5.0
|
lxml>=5.0
|
||||||
|
requests>=2.31
|
||||||
|
# pix2text separat, siehe BEDIENUNGSANLEITUNG: pip install "pix2text[multilingual]"; pip install --no-deps pix2tex munch
|
||||||
# optional, only for the structural comparison (FORMULA_STRUCTURAL=1): sympy>=1.13, lark>=1.1
|
# optional, only for the structural comparison (FORMULA_STRUCTURAL=1): sympy>=1.13, lark>=1.1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue