Files
breakpilot-lehrer/docs-src/services/klausur-service/OCR-Pipeline.md
Benjamin Admin 1cc69d6b5e
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Failing after 2m4s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 19s
feat: OCR pipeline step 8 — validation view with image detection & generation
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>
2026-03-05 10:40:37 +01:00

31 KiB
Raw Blame History

OCR Pipeline - Schrittweise Seitenrekonstruktion

Version: 3.0.0 Status: Produktiv (Schritte 18 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.70.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.50.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:

  1. Woerter werden nach X-Position in vertikale Spaltencluster gruppiert
  2. Pro Cluster: Lineare Regression x = a*y + ba = dx/dy = tan(shear_angle)
  3. Ensemble aus drei Methoden: Textzeilen (1.5× Gewicht), Projektionsprofil (2-Pass), Vertikalkanten
  4. 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-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.

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):

  1. Fuer jede Spalte mit width_ratio >= 0.15 und word_count >= 5:
  2. Left-Edges aller Woerter mit conf >= 30 sammeln
  3. In Alignment-Bins clustern (8px Toleranz)
  4. Linkester Bin mit >= 10% der Woerter = wahrer Spaltenanfang
  5. Woerter links davon = Sub-Spalte, wenn >= 2 und < 35% Anteil
  6. 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:

  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.

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 2040% Bildbreite, typische schmale bei 312%. Die 15%-Grenze trennt diese Gruppen sauber.

!!! note "Offener Punkt: Schwellen-Validierung" Die 15%-Schwelle wurde an Vokabeltabellen mit 35 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):

  1. Crop: Exakte Spalten- × Zeilengrenzen mit 3px internem Padding
  2. Density-Check: Ueberspringe leere Zellen (dark_ratio < 0.005)
  3. Upscaling: Kleine Crops (Hoehe < 80px) werden 3× vergroessert
  4. OCR: Engine-spezifisch (Tesseract, TrOCR, RapidOCR, LightON)
  5. Fallback: Bei leerem Ergebnis → PSM 7 (Einzelzeile) statt PSM 6
  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, 1I, 8B) 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 enB
1 want I want Alleinstehendes 1I (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.5gp.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 50200 %
  • 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 35)
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 67 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