docs: OCR-Pipeline v2.0.0 – alle Optimierungen 2026-03-03 dokumentiert
- Schritte 6–8 jetzt vollständig dokumentiert (nicht mehr "Geplant") - Step 3: Full-Width-Scan, Phantom-Filter-Detail - Step 4: Artefakt-Zeilen, Gap-Healing - Step 6: Spell Checker, Char Confusion (_fix_character_confusion), SSE-Protokoll, Env-Vars (REVIEW_ENGINE, OLLAMA_REVIEW_*) - Step 7: Rekonstruktions-Canvas, leere Zellen editierbar - Dependencies-Tabelle mit pyspellchecker als neue Dependency - Änderungshistorie mit allen 2026-03-03 Commits Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
# OCR Pipeline - Schrittweise Seitenrekonstruktion
|
# OCR Pipeline - Schrittweise Seitenrekonstruktion
|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 2.0.0
|
||||||
**Status:** In Entwicklung
|
**Status:** Produktiv (Schritte 1–8 implementiert)
|
||||||
**URL:** https://macmini:3002/ai/ocr-pipeline
|
**URL:** https://macmini:3002/ai/ocr-pipeline
|
||||||
|
|
||||||
## Uebersicht
|
## Uebersicht
|
||||||
|
|
||||||
Die OCR Pipeline zerlegt den OCR-Prozess in **8 einzelne Schritte**, um eingescannte Vokabelseiten Wort fuer Wort zu rekonstruieren. Jeder Schritt kann individuell geprueft, korrigiert und mit Ground-Truth-Daten versehen werden.
|
Die OCR Pipeline zerlegt den OCR-Prozess in **8 einzelne Schritte**, um eingescannte Vokabelseiten
|
||||||
|
aus mehrspaltig gedruckten Schulbuechern Wort fuer Wort zu rekonstruieren.
|
||||||
|
Jeder Schritt kann individuell geprueft, korrigiert und mit Ground-Truth-Daten versehen werden.
|
||||||
|
|
||||||
**Ziel:** 10 Vokabelseiten fehlerfrei rekonstruieren.
|
**Ziel:** 10 Vokabelseiten fehlerfrei rekonstruieren.
|
||||||
|
|
||||||
@@ -16,12 +18,12 @@ Die OCR Pipeline zerlegt den OCR-Prozess in **8 einzelne Schritte**, um eingesca
|
|||||||
|---------|------|--------------|--------|
|
|---------|------|--------------|--------|
|
||||||
| 1 | Begradigung (Deskew) | Scan begradigen (Hough Lines + Word Alignment) | Implementiert |
|
| 1 | Begradigung (Deskew) | Scan begradigen (Hough Lines + Word Alignment) | Implementiert |
|
||||||
| 2 | Entzerrung (Dewarp) | Buchwoelbung entzerren (Vertikalkanten-Analyse) | Implementiert |
|
| 2 | Entzerrung (Dewarp) | Buchwoelbung entzerren (Vertikalkanten-Analyse) | Implementiert |
|
||||||
| 3 | Spaltenerkennung | Unsichtbare Spalten finden (Projektionsprofile) | Implementiert |
|
| 3 | Spaltenerkennung | Unsichtbare Spalten finden (Projektionsprofile + Wortvalidierung) | Implementiert |
|
||||||
| 4 | Zeilenerkennung | Horizontale Zeilen + Kopf-/Fusszeilen-Klassifikation | Implementiert |
|
| 4 | Zeilenerkennung | Horizontale Zeilen + Kopf-/Fusszeilen-Klassifikation + Luecken-Heilung | Implementiert |
|
||||||
| 5 | Worterkennung | Grid aus Spalten x Zeilen, OCR pro Zelle | Implementiert |
|
| 5 | Worterkennung | Grid aus Spalten x Zeilen, OCR pro Zelle, Post-Processing | Implementiert |
|
||||||
| 6 | Koordinatenzuweisung | Exakte Positionen innerhalb Zellen | Geplant |
|
| 6 | Korrektur | Zeichenverwirrung + regel-basierte Rechtschreibkorrektur (SSE-Stream) | Implementiert |
|
||||||
| 7 | Seitenrekonstruktion | Seite nachbauen aus Koordinaten | Geplant |
|
| 7 | Rekonstruktion | Interaktive Zellenbearbeitung auf Bildhintergrund | Implementiert |
|
||||||
| 8 | Ground Truth Validierung | Gesamtpruefung aller Schritte | Geplant |
|
| 8 | Validierung | Ground-Truth-Vergleich und Qualitaetspruefung | Implementiert |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -34,18 +36,19 @@ Admin-Lehrer (Next.js) klausur-service (FastAPI :8086)
|
|||||||
│ │ REST │ │
|
│ │ REST │ │
|
||||||
│ PipelineStepper │◄────────►│ Sessions CRUD │
|
│ PipelineStepper │◄────────►│ Sessions CRUD │
|
||||||
│ StepDeskew │ │ Image Serving │
|
│ StepDeskew │ │ Image Serving │
|
||||||
│ StepDewarp │ │ Deskew/Dewarp/Columns/Rows │
|
│ StepDewarp │ SSE │ Deskew/Dewarp/Columns/Rows │
|
||||||
│ StepColumnDetection│ │ Word Recognition │
|
│ StepColumnDetection│◄────────►│ Word Recognition │
|
||||||
│ StepRowDetection │ │ Ground Truth │
|
│ StepRowDetection │ │ Correction (Spell-Checker) │
|
||||||
│ StepWordRecognition│ │ Overlay Images │
|
│ StepWordRecognition│ │ Reconstruction │
|
||||||
└────────────────────┘ └─────────────────────────────┘
|
│ StepLlmReview │ │ Ground Truth │
|
||||||
│
|
│ StepReconstruction │ └─────────────────────────────┘
|
||||||
▼
|
│ StepGroundTruth │ │
|
||||||
┌─────────────────────┐
|
└────────────────────┘ ▼
|
||||||
│ PostgreSQL │
|
┌─────────────────────┐
|
||||||
│ ocr_pipeline_sessions│
|
│ PostgreSQL │
|
||||||
│ (Images + JSONB) │
|
│ ocr_pipeline_sessions│
|
||||||
└─────────────────────┘
|
│ (Images + JSONB) │
|
||||||
|
└─────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dateistruktur
|
### Dateistruktur
|
||||||
@@ -54,7 +57,7 @@ Admin-Lehrer (Next.js) klausur-service (FastAPI :8086)
|
|||||||
klausur-service/backend/
|
klausur-service/backend/
|
||||||
├── ocr_pipeline_api.py # FastAPI Router (alle Endpoints)
|
├── ocr_pipeline_api.py # FastAPI Router (alle Endpoints)
|
||||||
├── ocr_pipeline_session_store.py # PostgreSQL Persistence
|
├── ocr_pipeline_session_store.py # PostgreSQL Persistence
|
||||||
├── cv_vocab_pipeline.py # Computer Vision Algorithmen
|
├── cv_vocab_pipeline.py # Computer Vision + NLP Algorithmen
|
||||||
└── migrations/
|
└── migrations/
|
||||||
├── 002_ocr_pipeline_sessions.sql # Basis-Schema
|
├── 002_ocr_pipeline_sessions.sql # Basis-Schema
|
||||||
├── 003_add_row_result.sql # Row-Result Spalte
|
├── 003_add_row_result.sql # Row-Result Spalte
|
||||||
@@ -66,14 +69,14 @@ admin-lehrer/
|
|||||||
│ └── types.ts # TypeScript Interfaces
|
│ └── types.ts # TypeScript Interfaces
|
||||||
└── components/ocr-pipeline/
|
└── components/ocr-pipeline/
|
||||||
├── PipelineStepper.tsx # Fortschritts-Stepper
|
├── PipelineStepper.tsx # Fortschritts-Stepper
|
||||||
├── StepDeskew.tsx # Schritt 1
|
├── StepDeskew.tsx # Schritt 1: Begradigung
|
||||||
├── StepDewarp.tsx # Schritt 2
|
├── StepDewarp.tsx # Schritt 2: Entzerrung
|
||||||
├── StepColumnDetection.tsx # Schritt 3
|
├── StepColumnDetection.tsx # Schritt 3: Spaltenerkennung
|
||||||
├── StepRowDetection.tsx # Schritt 4
|
├── StepRowDetection.tsx # Schritt 4: Zeilenerkennung
|
||||||
├── StepWordRecognition.tsx # Schritt 5
|
├── StepWordRecognition.tsx # Schritt 5: Worterkennung
|
||||||
├── StepCoordinates.tsx # Schritt 6 (Platzhalter)
|
├── StepLlmReview.tsx # Schritt 6: Korrektur (SSE-Stream)
|
||||||
├── StepReconstruction.tsx # Schritt 7 (Platzhalter)
|
├── StepReconstruction.tsx # Schritt 7: Rekonstruktion (Canvas)
|
||||||
└── StepGroundTruth.tsx # Schritt 8 (Platzhalter)
|
└── StepGroundTruth.tsx # Schritt 8: Validierung
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -145,13 +148,83 @@ Alle Endpoints unter `/api/v1/ocr-pipeline/`.
|
|||||||
| `POST` | `/sessions/{id}/ground-truth/words` | Ground Truth speichern |
|
| `POST` | `/sessions/{id}/ground-truth/words` | Ground Truth speichern |
|
||||||
| `GET` | `/sessions/{id}/ground-truth/words` | Ground Truth abrufen |
|
| `GET` | `/sessions/{id}/ground-truth/words` | Ground Truth abrufen |
|
||||||
|
|
||||||
|
### Schritt 6: Korrektur
|
||||||
|
|
||||||
|
| Methode | Pfad | Beschreibung |
|
||||||
|
|---------|------|--------------|
|
||||||
|
| `POST` | `/sessions/{id}/llm-review?stream=true` | SSE-Stream Korrektur starten |
|
||||||
|
| `POST` | `/sessions/{id}/llm-review/apply` | Ausgewaehlte Korrekturen speichern |
|
||||||
|
|
||||||
|
### Schritt 7: Rekonstruktion
|
||||||
|
|
||||||
|
| Methode | Pfad | Beschreibung |
|
||||||
|
|---------|------|--------------|
|
||||||
|
| `POST` | `/sessions/{id}/reconstruction` | Zellaenderungen speichern |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schritt 3: Spaltenerkennung (Detail)
|
||||||
|
|
||||||
|
### Algorithmus: `detect_column_geometry()`
|
||||||
|
|
||||||
|
Zweistufige Erkennung: vertikale Projektionsprofile finden Luecken, Wort-Bounding-Boxes validieren.
|
||||||
|
|
||||||
|
```
|
||||||
|
Bild → Binarisierung → Vertikalprofil → Lueckenerkennung → Wort-Validierung → ColumnGeometry
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wichtige Implementierungsdetails:**
|
||||||
|
|
||||||
|
- **Initialer Tesseract-Scan:** Laeuft auf der vollen Bildbreite `[left_x : w]` (nicht nur bis zur Content-Grenze `right_x`), damit Woerter am rechten Rand der letzten Spalte nicht uebersehen werden.
|
||||||
|
- **Letzte Spalte:** Wird immer bis zur vollen Bildbreite `w` ausgedehnt, nicht nur bis zur erkannten Content-Grenze.
|
||||||
|
- **Phantom-Spalten-Filter (Step 9):** Spalten mit Breite < 3 % der Content-Breite UND < 3 Woerter werden als Artefakte entfernt; die angrenzenden Spalten schliessen die Luecke.
|
||||||
|
- **Spaltenzuweisung:** Woerter werden anhand des groessten horizontalen Ueberlappungsbereichs einer Spalte zugeordnet.
|
||||||
|
|
||||||
|
### Konfigurierbare Parameter
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Mindestbreite fuer echte Spalten (automatisch: max(20px, 3% content_w))
|
||||||
|
min_real_col_w = max(20, int(content_w * 0.03))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schritt 4: Zeilenerkennung (Detail)
|
||||||
|
|
||||||
|
### Algorithmus: `detect_row_geometry()`
|
||||||
|
|
||||||
|
Horizontale Projektionsprofile finden Zeilen-Luecken; word-level Validierung verhindert Fehlschnitte.
|
||||||
|
|
||||||
|
**Zusaetzliche Post-Processing-Schritte:**
|
||||||
|
|
||||||
|
1. **Artefakt-Zeilen entfernen** (`_is_artifact_row`):
|
||||||
|
Zeilen, in denen alle erkannten Tokens nur 1 Zeichen lang sind (Scan-Schatten, leere Zeilen),
|
||||||
|
werden als Artefakte klassifiziert und aus dem Grid entfernt.
|
||||||
|
|
||||||
|
2. **Luecken-Heilung** (`_heal_row_gaps`):
|
||||||
|
Nach dem Entfernen leerer/Artefakt-Zeilen werden die verbleibenden Zeilen auf die Mitte
|
||||||
|
der entstehenden Luecke ausgedehnt, damit kein Zeileninhalt durch schrumpfende Grenzen
|
||||||
|
abgeschnitten wird.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _is_artifact_row(row: RowGeometry) -> bool:
|
||||||
|
"""Zeile ist Artefakt wenn alle Tokens <= 1 Zeichen."""
|
||||||
|
if row.word_count == 0: return True
|
||||||
|
return all(len(w.get('text','').strip()) <= 1 for w in row.words)
|
||||||
|
|
||||||
|
def _heal_row_gaps(rows, top_bound, bottom_bound):
|
||||||
|
"""Verbleibende Zeilen auf Mitte der Luecken ausdehnen."""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Schritt 5: Worterkennung (Detail)
|
## Schritt 5: Worterkennung (Detail)
|
||||||
|
|
||||||
### Algorithmus: `build_word_grid()`
|
### Algorithmus: `build_cell_grid()`
|
||||||
|
|
||||||
Schritt 5 nutzt die Ergebnisse von Schritt 3 (Spalten) und Schritt 4 (Zeilen), um ein Grid zu erstellen und jede Zelle per OCR auszulesen.
|
Schritt 5 nutzt die Ergebnisse von Schritt 3 (Spalten) und Schritt 4 (Zeilen), um ein Grid
|
||||||
|
zu erstellen und jede Zelle per OCR auszulesen.
|
||||||
|
|
||||||
```
|
```
|
||||||
Spalten (Step 3): column_en | column_de | column_example
|
Spalten (Step 3): column_en | column_de | column_example
|
||||||
@@ -164,69 +237,104 @@ Zeilen (Step 4): R0 │ hello │ hallo │ Hello, World!
|
|||||||
|
|
||||||
**Ablauf:**
|
**Ablauf:**
|
||||||
|
|
||||||
1. **Filterung**: Nur `content`-Zeilen (kein Header/Footer) und relevante Spalten (`column_en`, `column_de`, `column_example`)
|
1. **Initialer Scan:** Ganzes Bild einmal per Tesseract/RapidOCR → alle Wort-Bboxes
|
||||||
2. **Zell-Bildung**: Pro content-Zeile x pro relevante Spalte eine `PageRegion` berechnen
|
2. **Zuweisung:** Jedes Wort der Spalte mit groesstem horizontalem Ueberlapp zuordnen
|
||||||
3. **OCR**: `ocr_region()` mit PSM 7 (Single Line) pro Zelle aufrufen
|
3. **Zell-OCR Fallback:** Leere Zellen bekommen eigenen Crop + erneuten OCR-Aufruf (PSM 6/7)
|
||||||
4. **Sprache**: `eng` fuer EN-Spalte, `deu` fuer DE-Spalte, `eng+deu` fuer Beispiele
|
4. **Batch-Spalten-OCR:** Bei vielen leeren Zellen in einer Spalte: gesamte Spalte einmal OCR-en
|
||||||
5. **Gruppierung**: Zellen zu Vokabel-Eintraegen zusammenfuehren
|
5. **Post-Processing:** Continuation-Rows zusammenfuehren, Lautschrift erkennen, Komma-Eintraege splitten
|
||||||
|
|
||||||
### Response-Format
|
### Post-Processing Pipeline (in `build_vocab_pipeline_streaming`)
|
||||||
|
|
||||||
```json
|
| # | Schritt | Funktion | Beschreibung |
|
||||||
{
|
|---|---------|----------|--------------|
|
||||||
"entries": [
|
| 0a | Lautschrift-Fortsetzung | `_merge_phonetic_continuation_rows` | IPA-only Folgezeilen zusammenfuehren |
|
||||||
{
|
| 0b | Zeilen-Fortsetzung | `_merge_continuation_rows` | Zeilen mit Kleinbuchstaben-Anfang zusammenfuehren |
|
||||||
"row_index": 0,
|
| 2 | Lautschrift-Fix | `_fix_phonetic_brackets` | OCR-Lautschrift mit Woerterbuch-IPA ersetzen |
|
||||||
"english": "hello",
|
| 3 | Komma-Split | `_split_comma_entries` | `break, broke, broken` → 3 Eintraege |
|
||||||
"german": "hallo",
|
| 4 | Beispielsaetze | `_attach_example_sentences` | Beispielsatz-Zeilen an vorangehenden Eintrag haengen |
|
||||||
"example": "Hello, how are you?",
|
|
||||||
"confidence": 85.3,
|
!!! info "Zeichenkorrektur in Schritt 6"
|
||||||
"bbox": {"x": 5.2, "y": 12.1, "w": 90.0, "h": 2.8},
|
Die Zeichenverwirrungskorrektur (`|` → `I`, `1` → `I`, `8` → `B`) laeuft **nicht** in
|
||||||
"bbox_en": {"x": 5.2, "y": 12.1, "w": 30.0, "h": 2.8},
|
Schritt 5, sondern als erstes in Schritt 6 (Korrektur), damit die Aenderungen im UI
|
||||||
"bbox_de": {"x": 35.5, "y": 12.1, "w": 25.0, "h": 2.8},
|
sichtbar und rueckgaengig machbar sind.
|
||||||
"bbox_ex": {"x": 61.0, "y": 12.1, "w": 34.2, "h": 2.8}
|
|
||||||
}
|
---
|
||||||
],
|
|
||||||
"entry_count": 25,
|
## Schritt 6: Korrektur (Detail)
|
||||||
"image_width": 2480,
|
|
||||||
"image_height": 3508,
|
### Korrektur-Engine
|
||||||
"duration_seconds": 3.2,
|
|
||||||
"summary": {
|
Schritt 6 kombiniert zwei Korrektur-Stufen, beide als SSE-Stream:
|
||||||
"total_entries": 25,
|
|
||||||
"with_english": 24,
|
**Stufe 1 — Zeichenverwirrungskorrektur** (`_fix_character_confusion`):
|
||||||
"with_german": 22,
|
|
||||||
"low_confidence": 3
|
| OCR-Fehler | Korrektur | Regel |
|
||||||
}
|
|------------|-----------|-------|
|
||||||
|
| `\|ch` | `Ich` | `\|` am Wortanfang vor Kleinbuchstaben → `I` |
|
||||||
|
| `\| want` | `I want` | Alleinstehendes `\|` → `I` |
|
||||||
|
| `8en` | `Ben` | `8` am Wortanfang vor `en` → `B` |
|
||||||
|
| `1 want` | `I want` | Alleinstehendes `1` → `I` (NICHT vor `.` oder `,`) |
|
||||||
|
| `1. Kreuz` | unveraendert | `1.` = Listennummer, wird **nicht** korrigiert |
|
||||||
|
|
||||||
|
**Stufe 2 — Regel-basierte Rechtschreibkorrektur** (`spell_review_entries_streaming`):
|
||||||
|
|
||||||
|
Nutzt `pyspellchecker` (MIT-Lizenz) mit EN+DE-Woerterbuch. Pro Token mit verdaechtigem Zeichen
|
||||||
|
(`0`, `1`, `5`, `6`, `8`, `|`) werden Kandidaten geprueft:
|
||||||
|
|
||||||
|
```python
|
||||||
|
_SPELL_SUBS = {
|
||||||
|
'0': ['O', 'o'], '1': ['l', 'I'], '5': ['S', 's'],
|
||||||
|
'6': ['G', 'g'], '8': ['B', 'b'], '|': ['I', 'l', '1'],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "Bounding Boxes in Prozent"
|
Logik: Kandidaten werden durch Woerterbuch-Lookup validiert. Strukturregel: Verdaechtiges
|
||||||
Alle `bbox`-Werte sind Prozent (0-100) relativ zur Bildgroesse.
|
Zeichen an Position 0 + Rest klein → erstes Substitut (z.B. `8en` → `Ben`).
|
||||||
Das erleichtert die Darstellung im Frontend unabhaengig von der Bildaufloesung.
|
|
||||||
|
|
||||||
### Frontend: StepWordRecognition
|
### Umgebungsvariablen
|
||||||
|
|
||||||
Die Komponente bietet zwei Modi:
|
| Variable | Default | Beschreibung |
|
||||||
|
|----------|---------|--------------|
|
||||||
|
| `REVIEW_ENGINE` | `spell` | Korrektur-Engine: `spell` oder `llm` |
|
||||||
|
| `OLLAMA_REVIEW_MODEL` | `qwen3:0.6b` | Ollama-Modell (nur wenn `REVIEW_ENGINE=llm`) |
|
||||||
|
| `OLLAMA_REVIEW_BATCH_SIZE` | `20` | Eintraege pro LLM-Aufruf |
|
||||||
|
|
||||||
**Uebersicht-Modus:**
|
### SSE-Protokoll
|
||||||
|
|
||||||
- Zwei Bilder nebeneinander: Grid-Overlay vs. sauberes Bild
|
```
|
||||||
- Tabelle aller erkannten Eintraege mit Konfidenz-Werten
|
POST /sessions/{id}/llm-review?stream=true
|
||||||
- Klick auf Eintrag wechselt zum Labeling-Modus
|
|
||||||
|
|
||||||
**Labeling-Modus (Step-Through):**
|
Events:
|
||||||
|
data: {"type": "meta", "total_entries": 96, "to_review": 80, "skipped": 16, "model": "spell"}
|
||||||
|
data: {"type": "batch", "changes": [...], "entries_reviewed": [0,1,2,...], "progress": {...}}
|
||||||
|
data: {"type": "complete", "duration_ms": 234}
|
||||||
|
data: {"type": "error", "detail": "..."}
|
||||||
|
|
||||||
- Links (2/3): Bild mit hervorgehobenem aktiven Eintrag (gelber Rahmen)
|
Change-Format:
|
||||||
- Rechts (1/3): Zell-Ausschnitte + editierbare Felder (English, Deutsch, Example)
|
{"row_index": 5, "field": "english", "old": "| want", "new": "I want"}
|
||||||
- Tastaturkuerzel:
|
```
|
||||||
- `Enter` = Bestaetigen und weiter
|
|
||||||
- `Ctrl+Pfeil runter` = Ueberspringen
|
|
||||||
- `Ctrl+Pfeil hoch` = Zurueck
|
|
||||||
|
|
||||||
**Feedback-Loop:**
|
---
|
||||||
|
|
||||||
- "Zeilen korrigieren" springt zurueck zu Schritt 4
|
## Schritt 7: Rekonstruktion (Detail)
|
||||||
- Nach Korrektur der Zeilen kann Schritt 5 erneut ausgefuehrt werden
|
|
||||||
|
Interaktiver Canvas-Editor: Das entzerrte Originalbild wird mit 30 % Opazitaet als Hintergrund
|
||||||
|
angezeigt, alle Grid-Zellen (auch leere!) werden als editierbare Textfelder darueber gelegt.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
- Alle Zellen editierbar — auch leere Zellen (kein Filter mehr)
|
||||||
|
- Farbkodierung nach Spaltentyp (Blau=EN, Gruen=DE, Orange=Beispiel)
|
||||||
|
- Leere Pflichtfelder (EN/DE) rot gestrichelt markiert
|
||||||
|
- Undo/Redo (Ctrl+Z / Ctrl+Shift+Z)
|
||||||
|
- Tab-Navigation durch alle Zellen (inkl. leerer)
|
||||||
|
- Zoom 50–200 %
|
||||||
|
- Per-Zell-Reset-Button bei geaenderten Zellen
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /sessions/{id}/reconstruction
|
||||||
|
Body: {"cells": [{"cell_id": "r5_c2", "text": "corrected text"}]}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -251,7 +359,7 @@ CREATE TABLE ocr_pipeline_sessions (
|
|||||||
dewarp_result JSONB,
|
dewarp_result JSONB,
|
||||||
column_result JSONB,
|
column_result JSONB,
|
||||||
row_result JSONB,
|
row_result JSONB,
|
||||||
word_result JSONB,
|
word_result JSONB, -- enthaelt vocab_entries, cells, llm_review
|
||||||
|
|
||||||
-- Ground Truth + Meta
|
-- Ground Truth + Meta
|
||||||
ground_truth JSONB,
|
ground_truth JSONB,
|
||||||
@@ -261,75 +369,52 @@ CREATE TABLE ocr_pipeline_sessions (
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Migrationen
|
`word_result` JSONB-Struktur:
|
||||||
|
|
||||||
| Datei | Beschreibung |
|
```json
|
||||||
|-------|--------------|
|
{
|
||||||
| `002_ocr_pipeline_sessions.sql` | Basis-Schema (Steps 1-3) |
|
"vocab_entries": [...],
|
||||||
| `003_add_row_result.sql` | `row_result JSONB` fuer Step 4 |
|
"cells": [{"cell_id": "r0_c0", "text": "hello", "bbox_pct": {...}, ...}],
|
||||||
| `004_add_word_result.sql` | `word_result JSONB` fuer Step 5 |
|
"columns_used": [...],
|
||||||
|
"llm_review": {
|
||||||
---
|
"changes": [{"row_index": 5, "field": "english", "old": "...", "new": "..."}],
|
||||||
|
"model_used": "spell",
|
||||||
## TypeScript Interfaces
|
"duration_ms": 234
|
||||||
|
|
||||||
Die wichtigsten Typen in `types.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface WordEntry {
|
|
||||||
row_index: number
|
|
||||||
english: string
|
|
||||||
german: string
|
|
||||||
example: string
|
|
||||||
confidence: number
|
|
||||||
bbox: WordBbox // Gesamte Zeile
|
|
||||||
bbox_en: WordBbox | null // EN-Zelle
|
|
||||||
bbox_de: WordBbox | null // DE-Zelle
|
|
||||||
bbox_ex: WordBbox | null // Example-Zelle
|
|
||||||
status?: 'pending' | 'confirmed' | 'edited' | 'skipped'
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WordResult {
|
|
||||||
entries: WordEntry[]
|
|
||||||
entry_count: number
|
|
||||||
image_width: number
|
|
||||||
image_height: number
|
|
||||||
duration_seconds: number
|
|
||||||
summary: {
|
|
||||||
total_entries: number
|
|
||||||
with_english: number
|
|
||||||
with_german: number
|
|
||||||
low_confidence: number
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ground Truth System
|
## Abhaengigkeiten
|
||||||
|
|
||||||
Jeder Schritt kann mit Ground-Truth-Feedback versehen werden:
|
### Python (klausur-service)
|
||||||
|
|
||||||
```json
|
| Paket | Version | Lizenz | Zweck |
|
||||||
{
|
|-------|---------|--------|-------|
|
||||||
"is_correct": false,
|
| `pytesseract` | ≥0.3.10 | Apache-2.0 | Haupt-OCR (Schritt 3–5) |
|
||||||
"corrected_entries": [...],
|
| `opencv-python-headless` | ≥4.8.0 | Apache-2.0 | Bildverarbeitung, Projektionsprofile |
|
||||||
"notes": "Zeile 5 falsch erkannt",
|
| `Pillow` | ≥10.0.0 | HPND (MIT-kompatibel) | Bildkonvertierung |
|
||||||
"saved_at": "2026-02-28T10:30:00"
|
| `rapidocr` | latest | Apache-2.0 | Schnelles OCR (ARM64 via ONNX) |
|
||||||
}
|
| `onnxruntime` | latest | MIT | ONNX-Inferenz fuer RapidOCR |
|
||||||
```
|
| `pyspellchecker` | ≥0.8.1 | MIT | Regel-basierte OCR-Korrektur (Schritt 6) |
|
||||||
|
| `eng-to-ipa` | latest | MIT | IPA-Lautschrift-Lookup (Schritt 5) |
|
||||||
|
|
||||||
Ground-Truth-Daten werden in der `ground_truth` JSONB-Spalte gespeichert, gruppiert nach Schritt:
|
!!! info "pyspellchecker (neu seit 2026-03)"
|
||||||
|
`pyspellchecker` (MIT-Lizenz) ersetzt die LLM-basierte Korrektur als Standard-Engine.
|
||||||
|
EN+DE-Woerterbuch, ~134k Woerter. Kein Ollama notig.
|
||||||
|
Umschaltbar via `REVIEW_ENGINE=llm` fuer den LLM-Pfad.
|
||||||
|
|
||||||
```json
|
---
|
||||||
{
|
|
||||||
"deskew": { "is_correct": true, ... },
|
## Bekannte Einschraenkungen
|
||||||
"dewarp": { "is_correct": true, ... },
|
|
||||||
"columns": { "is_correct": false, ... },
|
| Problem | Ursache | Workaround |
|
||||||
"rows": { "is_correct": true, ... },
|
|---------|---------|------------|
|
||||||
"words": { "is_correct": false, ... }
|
| Schraeg gedruckte Seiten | Deskew erkennt Text-Rotation, nicht Seiten-Rotation | Manueller Winkel |
|
||||||
}
|
| Sehr kleine Schrift (< 8pt) | Tesseract PSM 7 braucht min. Zeichengroesse | Vorher zoomen |
|
||||||
```
|
| Handgeschriebene Eintraege | Tesseract/RapidOCR sind fuer Druckschrift optimiert | TrOCR-Engine (geplant) |
|
||||||
|
| Mehr als 4 Spalten | Projektionsprofil kann verschmelzen | Manuelle Spalten |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -337,35 +422,45 @@ Ground-Truth-Daten werden in der `ground_truth` JSONB-Spalte gespeichert, gruppi
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Git push
|
# 1. Git push
|
||||||
git push origin main && git push gitea main
|
git push origin main
|
||||||
|
|
||||||
# 2. Mac Mini pull + build
|
# 2. Mac Mini pull + build
|
||||||
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && git pull --no-rebase origin main"
|
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && git pull --no-rebase origin main"
|
||||||
|
|
||||||
# klausur-service (Backend)
|
# klausur-service (Backend) — bei requirements.txt Aenderungen: klausur-base neu bauen
|
||||||
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && \
|
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && \
|
||||||
/usr/local/bin/docker compose build --no-cache klausur-service && \
|
/usr/local/bin/docker compose build klausur-service && \
|
||||||
/usr/local/bin/docker compose up -d klausur-service"
|
/usr/local/bin/docker compose up -d klausur-service"
|
||||||
|
|
||||||
# admin-lehrer (Frontend)
|
# admin-lehrer (Frontend)
|
||||||
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && \
|
ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && \
|
||||||
/usr/local/bin/docker compose build --no-cache admin-lehrer && \
|
/usr/local/bin/docker compose build admin-lehrer && \
|
||||||
/usr/local/bin/docker compose up -d admin-lehrer"
|
/usr/local/bin/docker compose up -d admin-lehrer"
|
||||||
|
|
||||||
# 3. Migration ausfuehren
|
# 3. Testen unter:
|
||||||
ssh macmini "/usr/local/bin/docker exec bp-lehrer-klausur-service \
|
|
||||||
python -c \"import asyncio; from ocr_pipeline_session_store import *; asyncio.run(init_ocr_pipeline_tables())\""
|
|
||||||
|
|
||||||
# 4. Testen unter:
|
|
||||||
# https://macmini:3002/ai/ocr-pipeline
|
# https://macmini:3002/ai/ocr-pipeline
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning "Base-Image bei neuen Python-Paketen"
|
||||||
|
Wenn `requirements.txt` geaendert wird (z.B. neues Paket hinzugefuegt), muss zuerst
|
||||||
|
das Base-Image neu gebaut werden:
|
||||||
|
```bash
|
||||||
|
ssh macmini "cd ~/Projekte/breakpilot-lehrer && \
|
||||||
|
/usr/local/bin/docker build -f klausur-service/Dockerfile.base \
|
||||||
|
-t klausur-base:latest klausur-service/"
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Aenderungshistorie
|
## Aenderungshistorie
|
||||||
|
|
||||||
| Datum | Version | Aenderung |
|
| Datum | Version | Aenderung |
|
||||||
|-------|---------|----------|
|
|-------|---------|----------|
|
||||||
|
| 2026-03-03 | 2.0.0 | Schritte 6–7 implementiert; Spell-Checker, Rekonstruktions-Canvas |
|
||||||
|
| 2026-03-03 | 1.5.0 | Spaltenerkennung: volle Bildbreite fuer initialen Scan, Phantom-Filter |
|
||||||
|
| 2026-03-03 | 1.4.0 | Zeilenerkennung: Artefakt-Zeilen entfernen + Luecken-Heilung |
|
||||||
|
| 2026-03-03 | 1.3.0 | Zeichenkorrektur: `1.`/`\|.` Listenpraefixe werden nicht zu `I.` |
|
||||||
|
| 2026-03-03 | 1.2.0 | LLM-Engine durch Spell-Checker ersetzt (REVIEW_ENGINE=spell) |
|
||||||
| 2026-02-28 | 1.0.0 | Schritt 5 (Worterkennung) implementiert |
|
| 2026-02-28 | 1.0.0 | Schritt 5 (Worterkennung) implementiert |
|
||||||
| 2026-02-22 | 0.4.0 | Schritt 4 (Zeilenerkennung) implementiert |
|
| 2026-02-22 | 0.4.0 | Schritt 4 (Zeilenerkennung) implementiert |
|
||||||
| 2026-02-20 | 0.3.0 | Schritt 3 (Spaltenerkennung) mit Typ-Klassifikation |
|
| 2026-02-20 | 0.3.0 | Schritt 3 (Spaltenerkennung) mit Typ-Klassifikation |
|
||||||
|
|||||||
Reference in New Issue
Block a user