Replaces the stub StepGroundTruth with a full side-by-side Original vs Reconstruction view. Adds VLM-based image region detection (qwen2.5vl), mflux image generation proxy, sync scroll/zoom, manual region drawing, and score/notes persistence. New backend endpoints: detect-images, generate-image, validate, get validation. New standalone mflux-service (scripts/mflux-service.py) for Metal GPU generation. Dockerfile.base: adds fonts-liberation (Apache-2.0). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 KiB
OCR Pipeline - Schrittweise Seitenrekonstruktion
Version: 3.0.0 Status: Produktiv (Schritte 1–8 implementiert) URL: https://macmini:3002/ai/ocr-pipeline
Uebersicht
Die OCR Pipeline zerlegt den OCR-Prozess in 8 einzelne Schritte, um eingescannte Seiten 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.
Pipeline-Schritte
| Schritt | Name | Beschreibung | Status |
|---|---|---|---|
| 1 | Begradigung (Deskew) | Scan begradigen (Hough Lines + Word Alignment) | Implementiert |
| 2 | Entzerrung (Dewarp) | Buchwoelbung entzerren (Vertikalkanten-Analyse) | Implementiert |
| 3 | Spaltenerkennung | Unsichtbare Spalten finden (Projektionsprofile + Wortvalidierung) | Implementiert |
| 4 | Zeilenerkennung | Horizontale Zeilen + Kopf-/Fusszeilen-Klassifikation + Luecken-Heilung | Implementiert |
| 5 | Worterkennung | Hybrid-Grid: Breite Spalten full-page, schmale cell-crop | Implementiert |
| 6 | Korrektur | Zeichenverwirrung + regel-basierte Rechtschreibkorrektur (SSE-Stream) | Implementiert |
| 7 | Rekonstruktion | Interaktive Zellenbearbeitung auf Bildhintergrund (Fabric.js) | Implementiert |
| 8 | Validierung | Ground-Truth-Vergleich und Qualitaetspruefung | Implementiert |
Dokumenttyp-Erkennung und Pipeline-Pfade
Automatische Weiche: detect_document_type()
Nicht jedes Dokument durchlaeuft denselben Pfad. Nach den gemeinsamen Vorverarbeitungsschritten
(Deskew, Dewarp, Binarisierung) analysiert detect_document_type() die Seitenstruktur
ohne OCR — rein ueber Projektionsprofile und Textdichte-Analyse (< 2 Sekunden).
detect_document_type(ocr_img, img_bgr) → DocumentTypeResult
Entscheidungslogik
flowchart TD
A[Bild-Input] --> B[Vertikales Projektionsprofil]
B --> C{Interne Spalten-Gaps >= 2?}
C -->|Ja| D{Zeilen-Gaps >= 5?}
D -->|Ja| E["vocab_table<br/>pipeline = cell_first<br/>confidence 0.7–0.95"]
D -->|Nein| F{Zeilen-Gaps >= 3?}
C -->|Nein| G{Interne Spalten-Gaps >= 1?}
G -->|Ja| F
G -->|Nein| H["full_text<br/>pipeline = full_page<br/>skip: columns, rows"]
F -->|Ja| I["generic_table<br/>pipeline = cell_first<br/>confidence 0.5–0.85"]
F -->|Nein| H
| Dokumenttyp | Spalten-Gaps | Zeilen-Gaps | Pipeline | Beispiel |
|---|---|---|---|---|
vocab_table |
≥ 2 | ≥ 5 | cell_first |
3-spaltige Schulbuch-Vokabeltabelle |
generic_table |
≥ 1 | ≥ 3 | cell_first |
2-spaltiges Glossar |
full_text |
0 | egal | full_page |
Fliesstext, Aufsatz, Buchseite |
Komplett-Flussdiagramm
┌─────────────────────────────────────────────────────────────────────┐
│ GEMEINSAME VORVERARBEITUNG (alle Dokumente) │
│ │
│ Stage 1: Render (432 DPI, 3× Zoom) │
│ Stage 2: Deskew (Hough Lines + Ensemble) │
│ Stage 3: Dewarp (Vertikalkanten-Drift, Ensemble Shear) │
│ Stage 4: Dual-Bild (ocr_img = binarisiert, layout_img = CLAHE) │
└─────────────────────────────────────┬───────────────────────────────┘
│
detect_document_type()
│
┌─────────────────┴──────────────────┐
▼ ▼
FULL-TEXT PFAD CELL-FIRST PFAD
(pipeline='full_page') (pipeline='cell_first')
│ │
Keine Spalten/Zeilen Spaltenerkennung
analyze_layout_by_words() detect_column_geometry()
Lese-Reihenfolge _detect_sub_columns()
│ expand_narrow_columns()
│ Zeilenerkennung
│ detect_row_geometry()
│ │
│ build_cell_grid_v2()
│ │
│ ┌─────────┴──────────┐
│ ▼ ▼
│ Breite Spalten Schmale Spalten
│ (>= 15% Breite) (< 15% Breite)
│ Full-Page Words Cell-Crop OCR
│ word_lookup cell_crop_v2
│ │ │
└───────────────────────────┴────────────────────┘
│
Post-Processing Pipeline
(Lautschrift, Komma-Split, etc.)
│
Schritt 6: Korrektur (Spell)
Schritt 7: Rekonstruktion
Schritt 8: Validierung
Architektur
Admin-Lehrer (Next.js) klausur-service (FastAPI :8086)
┌────────────────────┐ ┌─────────────────────────────┐
│ /ai/ocr-pipeline │ │ /api/v1/ocr-pipeline/ │
│ │ REST │ │
│ PipelineStepper │◄────────►│ Sessions CRUD │
│ StepDeskew │ │ Image Serving │
│ StepDewarp │ SSE │ Deskew/Dewarp/Columns/Rows │
│ StepColumnDetection│◄────────►│ Word Recognition │
│ StepRowDetection │ │ Correction (Spell-Checker) │
│ StepWordRecognition│ │ Reconstruction │
│ StepLlmReview │ │ Ground Truth │
│ StepReconstruction │ └─────────────────────────────┘
│ StepGroundTruth │ │
└────────────────────┘ ▼
┌─────────────────────┐
│ PostgreSQL │
│ ocr_pipeline_sessions│
│ (Images + JSONB) │
└─────────────────────┘
Dateistruktur
klausur-service/backend/
├── services/
│ └── cv_vocab_pipeline.py # Computer Vision + NLP Algorithmen
├── ocr_pipeline_api.py # FastAPI Router (alle Endpoints)
├── ocr_pipeline_session_store.py # PostgreSQL Persistence
├── layout_reconstruction_service.py # Fabric.js JSON + PDF/DOCX Export
└── migrations/
├── 002_ocr_pipeline_sessions.sql # Basis-Schema
├── 003_add_row_result.sql # Row-Result Spalte
└── 004_add_word_result.sql # Word-Result Spalte
admin-lehrer/
├── app/(admin)/ai/ocr-pipeline/
│ ├── page.tsx # Haupt-Page mit Session-Management
│ └── types.ts # TypeScript Interfaces
└── components/ocr-pipeline/
├── PipelineStepper.tsx # Fortschritts-Stepper
├── StepDeskew.tsx # Schritt 1: Begradigung
├── StepDewarp.tsx # Schritt 2: Entzerrung
├── StepColumnDetection.tsx # Schritt 3: Spaltenerkennung
├── StepRowDetection.tsx # Schritt 4: Zeilenerkennung
├── StepWordRecognition.tsx # Schritt 5: Worterkennung
├── StepLlmReview.tsx # Schritt 6: Korrektur (SSE-Stream)
├── StepReconstruction.tsx # Schritt 7: Rekonstruktion (Canvas)
├── FabricReconstructionCanvas.tsx # Fabric.js Editor
└── StepGroundTruth.tsx # Schritt 8: Validierung
API-Referenz
Alle Endpoints unter /api/v1/ocr-pipeline/.
Sessions
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/sessions |
Neue Session erstellen (Bild hochladen) |
GET |
/sessions |
Alle Sessions auflisten |
GET |
/sessions/{id} |
Session-Info mit allen Step-Results |
PUT |
/sessions/{id} |
Session umbenennen |
DELETE |
/sessions/{id} |
Session loeschen |
POST |
/sessions/{id}/detect-type |
Dokumenttyp erkennen |
Bilder
| Methode | Pfad | Beschreibung |
|---|---|---|
GET |
/sessions/{id}/image/original |
Originalbild |
GET |
/sessions/{id}/image/deskewed |
Begradigtes Bild |
GET |
/sessions/{id}/image/dewarped |
Entzerrtes Bild |
GET |
/sessions/{id}/image/binarized |
Binarisiertes Bild |
GET |
/sessions/{id}/image/columns-overlay |
Spalten-Overlay |
GET |
/sessions/{id}/image/rows-overlay |
Zeilen-Overlay |
GET |
/sessions/{id}/image/words-overlay |
Wort-Grid-Overlay |
Schritt 1: Begradigung
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/sessions/{id}/deskew |
Automatische Begradigung |
POST |
/sessions/{id}/deskew/manual |
Manuelle Winkelkorrektur |
POST |
/sessions/{id}/ground-truth/deskew |
Ground Truth speichern |
Schritt 2: Entzerrung
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/sessions/{id}/dewarp |
Automatische Entzerrung |
POST |
/sessions/{id}/dewarp/manual |
Manueller Scherbungswinkel |
POST |
/sessions/{id}/ground-truth/dewarp |
Ground Truth speichern |
Schritt 3: Spalten
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/sessions/{id}/columns |
Automatische Spaltenerkennung |
POST |
/sessions/{id}/columns/manual |
Manuelle Spalten-Definition |
POST |
/sessions/{id}/ground-truth/columns |
Ground Truth speichern |
Schritt 4: Zeilen
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/sessions/{id}/rows |
Automatische Zeilenerkennung |
POST |
/sessions/{id}/rows/manual |
Manuelle Zeilen-Definition |
POST |
/sessions/{id}/ground-truth/rows |
Ground Truth speichern |
GET |
/sessions/{id}/ground-truth/rows |
Ground Truth abrufen |
Schritt 5: Worterkennung
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/sessions/{id}/words |
Wort-Grid aus Spalten x Zeilen erstellen |
POST |
/sessions/{id}/ground-truth/words |
Ground Truth speichern |
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 |
GET |
/sessions/{id}/reconstruction/fabric-json |
Fabric.js Canvas-Daten |
GET |
/sessions/{id}/reconstruction/export/pdf |
PDF-Export (reportlab) |
GET |
/sessions/{id}/reconstruction/export/docx |
DOCX-Export (python-docx) |
POST |
/sessions/{id}/reconstruction/detect-images |
Bildbereiche per VLM erkennen |
POST |
/sessions/{id}/reconstruction/generate-image |
Bild per mflux generieren |
POST |
/sessions/{id}/reconstruction/validate |
Validierung speichern (Step 8) |
GET |
/sessions/{id}/reconstruction/validation |
Validierungsdaten abrufen |
Schritt 2: Entzerrung/Dewarp (Detail)
Algorithmus: Vertikalkanten-Drift
Die Dewarp-Erkennung misst die vertikale Spaltenkippung (dx/dy) statt Textzeilen-Neigung:
- Woerter werden nach X-Position in vertikale Spaltencluster gruppiert
- Pro Cluster: Lineare Regression
x = a*y + b→a = dx/dy = tan(shear_angle) - Ensemble aus drei Methoden: Textzeilen (1.5× Gewicht), Projektionsprofil (2-Pass), Vertikalkanten
- Qualitaetspruefung: Horizontale Projektionsvarianz vor/nach Korrektur
Schwellenwerte:
| Parameter | Wert | Beschreibung |
|---|---|---|
| Min. Korrekturwinkel | 0.08° | Unter 0.08° wird nicht korrigiert |
| Ensemble Min-Confidence | 0.35 | Mindest-Konfidenz fuer Korrektur |
| Quality-Gate Skip | < 0.5° | Kleine Korrekturen ueberspringen Quality-Gate |
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-Grenzeright_x), damit Woerter am rechten Rand der letzten Spalte nicht uebersehen werden. - Letzte Spalte: Wird immer bis zur vollen Bildbreite
wausgedehnt, 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.
Sub-Spalten-Erkennung: _detect_sub_columns()
Erkennt versteckte Sub-Spalten innerhalb breiter Spalten (z.B. Seitenzahl-Spalte links neben EN-Vokabeln).
Algorithmus (Left-Edge Alignment Clustering):
- Fuer jede Spalte mit
width_ratio >= 0.15undword_count >= 5: - Left-Edges aller Woerter mit
conf >= 30sammeln - In Alignment-Bins clustern (8px Toleranz)
- Linkester Bin mit >= 10% der Woerter = wahrer Spaltenanfang
- Woerter links davon = Sub-Spalte, wenn >= 2 und < 35% Anteil
- Neue ColumnGeometry-Objekte mit korrekten Indizes erzeugen
Koordinatensystem: Word left-Werte sind relativ zum Content-ROI (left_x), ColumnGeometry.x ist absolut. left_x wird als Parameter durchgereicht.
Spalten-Erweiterung: expand_narrow_columns()
Laeuft nach _detect_sub_columns(). Erweitert sehr schmale Spalten (< 10% Content-Breite,
z.B. page_ref, marker) in den Weissraum zum Nachbar-Spalte hinein, aber nie ueber die
naechsten Woerter im Nachbarn hinaus (4px Sicherheitsabstand).
Spaltentyp-Klassifikation: classify_column_types()
| Spaltentyp | Beschreibung | Erkennung |
|---|---|---|
column_en |
Englische Vokabeln | EN-Funktionswoerter (the, a, is...) |
column_de |
Deutsche Uebersetzung | DE-Funktionswoerter (der, die, das...) |
column_example |
Beispielsaetze | Abkuerzungen, Grammatik-Marker |
page_ref |
Seitenzahlen | Schmal (< 20% Breite), wenige Woerter |
column_marker |
Dekorative Markierungen | Sehr schmal, spezielle Zeichen |
column_text |
Generischer Text | Fallback |
Konfigurierbare Parameter
# 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:
-
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. -
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.
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 — Hybrid-Grid (Detail)
Algorithmus: build_cell_grid_v2()
Schritt 5 nutzt eine Hybrid-Strategie: Breite Spalten verwenden die Full-Page-Tesseract-Woerter, schmale Spalten werden isoliert per Cell-Crop OCR verarbeitet.
!!! success "Warum Hybrid?" Full-Page OCR liefert gute Ergebnisse fuer breite Spalten (Saetze, IPA-Klammern, Interpunktion). Aber bei schmalen Spalten (Seitenzahlen, Marker) „bluten" Woerter aus Nachbar-Spalten ein. Cell-Crop isoliert jede Zelle und verhindert dieses Bleeding.
Broad vs. Narrow — Die 15%-Schwelle
_NARROW_COL_THRESHOLD_PCT = 15.0 # cv_vocab_pipeline.py
| Eigenschaft | Breite Spalten (>= 15%) | Schmale Spalten (< 15%) |
|---|---|---|
| OCR-Quelle | Full-Page Tesseract (vorher gelaufen) | Isolierter Cell-Crop |
| Wort-Zuweisung | _assign_row_words_to_columns() |
Direktes Zell-OCR |
| Confidence-Filter | conf >= 30 |
conf >= 30 |
| Text-Bereinigung | _clean_cell_text() (mittel) |
_clean_cell_text_lite() (aggressiv) |
| Neighbour-Bleeding | Risiko vorhanden | Verhindert (isoliert) |
| Parallelisierung | Sequentiell | Parallel (max_workers=4) |
| OCR-Engine Label | word_lookup |
cell_crop_v2 |
| Typische Spalten | EN-Vokabeln, DE-Uebersetzung, Beispielsaetze | Seitenzahlen, Marker |
Empirische Grundlage: Typische breite Spalten liegen bei 20–40% Bildbreite, typische schmale bei 3–12%. Die 15%-Grenze trennt diese Gruppen sauber.
!!! note "Offener Punkt: Schwellen-Validierung" Die 15%-Schwelle wurde an Vokabeltabellen mit 3–5 Spalten validiert. Fuer eine breitere Validierung werden diverse Schulbuchseiten mit unterschiedlichen Layouts (2-, 3-, 4-, 5-spaltig, verschiedene Verlage) benoetigt. Aktuell gibt es in der Datenbank nur Sessions mit demselben Arbeitsblatt-Typ.
Cell-Crop OCR: _ocr_cell_crop()
Isolierte OCR einer einzelnen Zelle (Spalte × Zeile Schnittflaeche):
- Crop: Exakte Spalten- × Zeilengrenzen mit 3px internem Padding
- Density-Check: Ueberspringe leere Zellen (
dark_ratio < 0.005) - Upscaling: Kleine Crops (Hoehe < 80px) werden 3× vergroessert
- OCR: Engine-spezifisch (Tesseract, TrOCR, RapidOCR, LightON)
- Fallback: Bei leerem Ergebnis → PSM 7 (Einzelzeile) statt PSM 6
- Bereinigung:
_clean_cell_text_lite()(aggressives Noise-Filtering)
Ablauf von build_cell_grid_v2()
Eingabe: ocr_img, column_regions, row_geometries
│
┌───────────┴───────────┐
│ Filter │
│ • Phantom-Zeilen │
│ • Artefakt-Zeilen │
│ • Irrelevante Spalten │
│ (header, footer, │
│ margin, ignore) │
└───────────┬───────────┘
│
┌───────────┴───────────┐
│ Klassifizierung │
│ Spalte.width / img_w │
│ >= 15% → broad │
│ < 15% → narrow │
└───────────┬───────────┘
│
┌───────────┴────────────────┐
│ │
Phase 1: Broad Phase 2: Narrow
(sequentiell) (parallel, max_workers=4)
│ │
Pro (row, col): Pro (row, col):
1. Words aus Full-Page 1. _ocr_cell_crop()
2. Filter conf >= 30 2. Isoliertes Zell-Bild
3. _words_to_reading_order 3. Upscale wenn noetig
4. _clean_cell_text() 4. _clean_cell_text_lite()
│ │
└───────────┬────────────────┘
│
Merge + Sortierung
(row_index, col_index)
Leere Zeilen entfernen
│
Ausgabe: cells[], columns_meta[]
Post-Processing Pipeline (in build_vocab_pipeline_streaming)
| # | Schritt | Funktion | Beschreibung |
|---|---|---|---|
| 0a | Lautschrift-Fortsetzung | _merge_phonetic_continuation_rows |
IPA-only Folgezeilen zusammenfuehren |
| 0b | Zeilen-Fortsetzung | _merge_continuation_rows |
Zeilen mit Kleinbuchstaben-Anfang zusammenfuehren |
| 2 | Lautschrift-Fix | _fix_phonetic_brackets |
OCR-Lautschrift mit Woerterbuch-IPA ersetzen |
| 3 | Komma-Split | _split_comma_entries |
break, broke, broken → 3 Eintraege |
| 4 | Beispielsaetze | _attach_example_sentences |
Beispielsatz-Zeilen an vorangehenden Eintrag haengen |
!!! info "Zeichenkorrektur in Schritt 6"
Die Zeichenverwirrungskorrektur (| → I, 1 → I, 8 → B) laeuft nicht in
Schritt 5, sondern als erstes in Schritt 6 (Korrektur), damit die Aenderungen im UI
sichtbar und rueckgaengig machbar sind.
Schritt 6: Korrektur (Detail)
Korrektur-Engine
Schritt 6 kombiniert drei Korrektur-Stufen, alle als SSE-Stream:
Stufe 1 — Zeichenverwirrungskorrektur (_fix_character_confusion):
| 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:
_SPELL_SUBS = {
'0': ['O', 'o'], '1': ['l', 'I'], '5': ['S', 's'],
'6': ['G', 'g'], '8': ['B', 'b'], '|': ['I', 'l', '1'],
}
Stufe 3 — Seitenzahl-Korrektur (page_ref-Felder):
Korrigiert haeufige OCR-Fehler in Seitenverweisen (z.B. p.5g → p.59).
Umgebungsvariablen
| 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 |
SSE-Protokoll
POST /sessions/{id}/llm-review?stream=true
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": "..."}
Change-Format:
{"row_index": 5, "field": "english", "old": "| want", "new": "I want"}
Schritt 7: Rekonstruktion (Detail)
Zwei Modi verfuegbar:
Einfacher Modus
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
Fabric.js Editor
Erweiterter Canvas-Editor (FabricReconstructionCanvas.tsx):
- Drag & Drop fuer Zellen
- Freie Positionierung auf dem Canvas
- Export als PDF (reportlab) oder DOCX (python-docx)
POST /sessions/{id}/reconstruction
Body: {"cells": [{"cell_id": "r5_c2", "text": "corrected text"}]}
Wichtige Konstanten
| Konstante | Wert | Datei | Beschreibung |
|---|---|---|---|
_NARROW_COL_THRESHOLD_PCT |
15.0% | cv_vocab_pipeline.py | Schwelle breit/schmal fuer Hybrid-OCR |
_NARROW_THRESHOLD_PCT |
10.0% | cv_vocab_pipeline.py | Schwelle fuer Spalten-Erweiterung |
_MIN_WORD_CONF |
30 | cv_vocab_pipeline.py | Mindest-Confidence fuer OCR-Woerter |
_PAD |
3px | cv_vocab_pipeline.py | Internes Padding bei Cell-Crop |
PDF_ZOOM |
3.0 | cv_vocab_pipeline.py | PDF-Rendering (= 432 DPI) |
_MIN_WORD_MARGIN |
4px | cv_vocab_pipeline.py | Sicherheitsabstand bei Spalten-Erweiterung |
Datenbank-Schema
CREATE TABLE ocr_pipeline_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255),
filename VARCHAR(255),
status VARCHAR(50) DEFAULT 'active',
current_step INT DEFAULT 1,
-- Dokumenttyp-Erkennung
doc_type VARCHAR(50), -- 'vocab_table', 'generic_table', 'full_text'
doc_type_result JSONB, -- Vollstaendiges DetectionResult
-- Bilder (BYTEA)
original_png BYTEA,
deskewed_png BYTEA,
binarized_png BYTEA,
dewarped_png BYTEA,
-- Step-Results (JSONB)
deskew_result JSONB,
dewarp_result JSONB,
column_result JSONB,
row_result JSONB,
word_result JSONB, -- enthaelt vocab_entries, cells, llm_review
-- Ground Truth + Meta
ground_truth JSONB,
auto_shear_degrees REAL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
word_result JSONB-Struktur:
{
"vocab_entries": [...],
"cells": [{"cell_id": "r0_c0", "text": "hello", "bbox_pct": {...}, "ocr_engine": "word_lookup", ...}],
"columns_used": [...],
"llm_review": {
"changes": [{"row_index": 5, "field": "english", "old": "...", "new": "..."}],
"model_used": "spell",
"duration_ms": 234
}
}
Abhaengigkeiten
Python (klausur-service)
| Paket | Version | Lizenz | Zweck |
|---|---|---|---|
pytesseract |
≥0.3.10 | Apache-2.0 | Haupt-OCR (Schritt 3–5) |
opencv-python-headless |
≥4.8.0 | Apache-2.0 | Bildverarbeitung, Projektionsprofile |
Pillow |
≥10.0.0 | HPND (MIT-kompatibel) | Bildkonvertierung |
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) |
reportlab |
latest | BSD | PDF-Export (Schritt 7) |
python-docx |
≥1.1.0 | MIT | DOCX-Export (Schritt 7) |
fabric (JS) |
^6 | MIT | Canvas-Editor (Frontend) |
!!! info "pyspellchecker (neu seit 2026-03)"
pyspellchecker (MIT-Lizenz) ersetzt die LLM-basierte Korrektur als Standard-Engine.
EN+DE-Woerterbuch, ~134k Woerter. Kein Ollama noetig.
Umschaltbar via REVIEW_ENGINE=llm fuer den LLM-Pfad.
Bekannte Einschraenkungen
| Problem | Ursache | Workaround |
|---|---|---|
| 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 |
| Mehr als 4 Spalten | Projektionsprofil kann verschmelzen | Manuelle Spalten |
| Farbige Marker (rot/blau) | HSV-Erkennung erzeugt False Positives | Manuell im Rekonstruktions-Editor |
| 15%-Schwelle nicht breit validiert | Nur an einem Arbeitsblatt-Typ getestet | Diverse Schulbuchseiten testen |
Deployment
# 1. Git push
git push origin main
# 2. Mac Mini pull + build
ssh macmini "git -C /Users/benjaminadmin/Projekte/breakpilot-lehrer pull --no-rebase origin main"
# klausur-service (Backend)
ssh macmini "/usr/local/bin/docker compose -f /Users/benjaminadmin/Projekte/breakpilot-lehrer/docker-compose.yml build klausur-service"
ssh macmini "/usr/local/bin/docker compose -f /Users/benjaminadmin/Projekte/breakpilot-lehrer/docker-compose.yml up -d klausur-service"
# admin-lehrer (Frontend)
ssh macmini "/usr/local/bin/docker compose -f /Users/benjaminadmin/Projekte/breakpilot-lehrer/docker-compose.yml build admin-lehrer"
ssh macmini "/usr/local/bin/docker compose -f /Users/benjaminadmin/Projekte/breakpilot-lehrer/docker-compose.yml up -d admin-lehrer"
# 3. Testen unter:
# 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 "/usr/local/bin/docker build -f /Users/benjaminadmin/Projekte/breakpilot-lehrer/klausur-service/Dockerfile.base \ -t klausur-base:latest /Users/benjaminadmin/Projekte/breakpilot-lehrer/klausur-service/"
Aenderungshistorie
| Datum | Version | Aenderung |
|---|---|---|
| 2026-03-05 | 3.0.0 | Doku-Update: Dokumenttyp-Erkennung, Hybrid-Grid, Sub-Column-Detection, Pipeline-Pfade |
| 2026-03-04 | 2.2.0 | Dewarp: Vertikalkanten-Drift statt Textzeilen-Neigung, Schwellenwerte gesenkt |
| 2026-03-04 | 2.1.0 | Sub-Column-Detection, expand_narrow_columns, Fabric.js Editor, PDF/DOCX-Export |
| 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-22 | 0.4.0 | Schritt 4 (Zeilenerkennung) implementiert |
| 2026-02-20 | 0.3.0 | Schritt 3 (Spaltenerkennung) mit Typ-Klassifikation |
| 2026-02-15 | 0.2.0 | Schritt 2 (Entzerrung/Dewarp) |
| 2026-02-12 | 0.1.0 | Schritt 1 (Begradigung/Deskew) + Session-Management |