Files
breakpilot-lehrer/docs-src/services/klausur-service/OCR-Pipeline.md
Benjamin Admin 65059471cf
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 24s
CI / test-go-edu-search (push) Successful in 25s
CI / test-python-klausur (push) Failing after 1m55s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 15s
Update OCR Pipeline docs: Grid Editor v4.7.0 with zone merging, heading detection, IPA fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 07:05:14 +01:00

68 KiB
Raw Blame History

OCR Pipeline - Schrittweise Seitenrekonstruktion

Version: 4.7.0 Status: Produktiv (Schritte 110 + Grid Editor implementiert) URL: https://macmini:3002/ai/ocr-pipeline

Uebersicht

Die OCR Pipeline zerlegt den OCR-Prozess in 10 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 Orientierung 90/180/270° Drehungen von Scannern korrigieren Implementiert
2 Begradigung (Deskew) Scan begradigen (Hough Lines + Word Alignment) Implementiert
3 Entzerrung (Dewarp) Buchwoelbung entzerren (Vertikalkanten-Analyse) Implementiert
4 Zuschneiden (Crop) Content-basierter Crop: Buchruecken-Schatten + Ink-Projektion Implementiert
5 Spaltenerkennung Unsichtbare Spalten finden (Projektionsprofile + Wortvalidierung) Implementiert
6 Zeilenerkennung Horizontale Zeilen + Kopf-/Fusszeilen-Klassifikation + Luecken-Heilung Implementiert
7 Worterkennung Hybrid-Grid (v2) oder Words-First (bottom-up) Implementiert
8 Korrektur Zeichenverwirrung + regel-basierte Rechtschreibkorrektur (SSE-Stream) Implementiert
9 Rekonstruktion Interaktive Zellenbearbeitung auf Bildhintergrund (Fabric.js) Implementiert
10 Validierung Ground-Truth-Vergleich und Qualitaetspruefung Implementiert

!!! note "Reihenfolge-Aenderung (v4.1)" Crop wurde hinter Deskew/Dewarp verschoben. Das Bild ist dann bereits gerade, was den Content-basierten Crop deutlich zuverlaessiger macht — insbesondere bei Buchscans mit Ruecken-Schatten und weissem Scanner-Hintergrund.


Dokumenttyp-Erkennung und Pipeline-Pfade

Automatische Weiche: detect_document_type()

Nicht jedes Dokument durchlaeuft denselben Pfad. Nach den gemeinsamen Vorverarbeitungsschritten (Orientierung, Deskew, Dewarp, Crop) 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)                         │
│                                                                     │
│ Schritt 1: Orientierung (90/180/270° Drehung korrigieren)           │
│ Schritt 2: Deskew (Hough Lines + Iterative Projektion + Ensemble)   │
│ Schritt 3: Dewarp (Vertikalkanten-Drift, Ensemble Shear)            │
│ Schritt 4: Crop (Content-basiert: Schatten + Ink-Projektion)        │
└─────────────────────────────────────┬───────────────────────────────┘
                                      │
                            detect_document_type()
                                      │
                    ┌──────────────────┼──────────────────┐
                    ▼                  ▼                  ▼
            FULL-TEXT PFAD     WORDS-FIRST PFAD    CELL-FIRST PFAD
            (pipeline=         (grid_method=        (grid_method=
             'full_page')       'words_first')       'v2', default)
                    │                  │                  │
            Keine Spalten/     Tesseract Full-Page  Spaltenerkennung
            Zeilen             word_boxes            detect_column_geometry()
            analyze_layout_    _cluster_columns()    _detect_sub_columns()
            by_words()         _cluster_rows()       expand_narrow_columns()
                    │          _build_cells()         Zeilenerkennung
                    │                  │              detect_row_geometry()
                    │          build_grid_from_       │
                    │          words()           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 8: Korrektur (Spell)
                                    Schritt 9: Rekonstruktion
                                    Schritt 10: 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 (Schritte 2-10)
├── orientation_crop_api.py             # FastAPI Router (Schritte 1 + 4)
├── grid_editor_api.py                  # Grid Editor: build-grid, save-grid, grid-editor
├── cv_ocr_engines.py                   # OCR-Engines, IPA-Korrektur, Britfone-Woerterbuch
├── cv_box_detect.py                    # Box-Erkennung + Zonen-Aufteilung
├── cv_graphic_detect.py                # Grafik-/Bilderkennung (Region-basiert)
├── cv_color_detect.py                  # Farbtext-Erkennung (HSV-Analyse)
├── cv_words_first.py                   # Words-First Grid Builder (bottom-up)
├── cv_vocab_types.py                   # Datentypen: PageZone, ColumnGeometry, etc.
├── page_crop.py                        # Content-basierter Crop-Algorithmus
├── ocr_pipeline_session_store.py       # PostgreSQL Persistence
├── layout_reconstruction_service.py    # Fabric.js JSON + PDF/DOCX Export
├── tests/
│   └── test_grid_editor_api.py         # 27 Tests fuer Grid Editor + IPA
└── 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
├── app/(admin)/ai/ocr-overlay/
│   ├── page.tsx                        # OCR Overlay: 3 Modi (Pipeline/Paddle/Kombi)
│   └── types.ts                        # OVERLAY_/PADDLE_DIRECT_/KOMBI_STEPS
├── components/ocr-overlay/
│   ├── PaddleDirectStep.tsx            # Wiederverwendbar fuer Paddle Direct + Kombi
│   └── OverlayReconstruction.tsx       # Overlay-Anzeige auf Bildhintergrund
└── components/ocr-pipeline/
    ├── PipelineStepper.tsx              # Fortschritts-Stepper
    ├── StepOrientation.tsx             # Schritt 1: Orientierung
    ├── StepDeskew.tsx                   # Schritt 2: Begradigung
    ├── StepDewarp.tsx                   # Schritt 3: Entzerrung
    ├── StepCrop.tsx                     # Schritt 4: Zuschneiden
    ├── StepColumnDetection.tsx          # Schritt 5: Spaltenerkennung
    ├── StepRowDetection.tsx             # Schritt 6: Zeilenerkennung
    ├── StepWordRecognition.tsx          # Schritt 7: Worterkennung
    ├── StepStructureDetection.tsx       # Schritt 8: Strukturerkennung
    ├── StepLlmReview.tsx               # Schritt 9: Korrektur (SSE-Stream)
    ├── StepReconstruction.tsx           # Schritt 9: Rekonstruktion (Canvas + Overlay)
    ├── usePixelWordPositions.ts        # Shared Hook: Pixel-basierte Wortpositionierung
    ├── FabricReconstructionCanvas.tsx   # Fabric.js Editor
    └── StepGroundTruth.tsx             # Schritt 10: 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/oriented Orientiertes Bild
GET /sessions/{id}/image/deskewed Begradigtes Bild
GET /sessions/{id}/image/dewarped Entzerrtes Bild
GET /sessions/{id}/image/cropped Zugeschnittenes 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: Orientierung

Methode Pfad Beschreibung
POST /sessions/{id}/orientation 90/180/270° Drehung erkennen und korrigieren

Schritt 2: 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 3: Entzerrung

Methode Pfad Beschreibung
POST /sessions/{id}/dewarp Automatische Entzerrung
POST /sessions/{id}/dewarp/manual Manueller Scherbungswinkel
POST /sessions/{id}/adjust-combined Kombinierte Rotation + Shear Feinabstimmung
POST /sessions/{id}/ground-truth/dewarp Ground Truth speichern

Schritt 4: Zuschneiden

Methode Pfad Beschreibung
POST /sessions/{id}/crop Automatischer Content-Crop
POST /sessions/{id}/crop/manual Manueller Crop (Prozent-Koordinaten)
POST /sessions/{id}/crop/skip Crop ueberspringen

Schritt 5: 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 6: 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 7: Worterkennung

Methode Pfad Beschreibung
POST /sessions/{id}/words Wort-Grid erstellen
POST /sessions/{id}/ground-truth/words Ground Truth speichern
GET /sessions/{id}/ground-truth/words Ground Truth abrufen

Query-Parameter fuer /sessions/{id}/words:

Parameter Default Beschreibung
engine auto OCR-Engine: auto, tesseract, rapid, paddle
pronunciation british IPA-Woerterbuch: british oder american
stream false SSE-Streaming (nur bei grid_method=v2)
skip_heal_gaps false Zeilen-Luecken nicht heilen (Overlay-Modus)
grid_method v2 Grid-Strategie: v2 (top-down) oder words_first (bottom-up)

Schritt 8: Strukturerkennung

Methode Pfad Beschreibung
POST /sessions/{id}/detect-structure Boxen, Zonen, Farben und Grafiken erkennen
GET /sessions/{id}/image/structure-overlay Overlay mit allen Strukturelementen

Schritt 9: 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 10: 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 10)
GET /sessions/{id}/reconstruction/validation Validierungsdaten abrufen

Grid Editor (Excel-aehnlich)

Methode Pfad Beschreibung
POST /sessions/{id}/build-grid Strukturiertes Grid aus Kombi-Wortdaten erstellen
POST /sessions/{id}/save-grid Bearbeitetes Grid speichern
GET /sessions/{id}/grid-editor Grid-Editor-Daten abrufen

Schritt 4: Zuschneiden/Crop (Detail)

Warum Crop nach Deskew/Dewarp?

In frueheren Versionen lief Crop als Schritt 2 (vor Deskew). Das fuehrte zu Problemen:

  • Schiefes Bild: boundingRect einer schiefen Seite schliesst viel Scanner-Hintergrund ein
  • Buchscans: Otsu-Binarisierung versagt bei weiss-auf-weiss (Seite auf weissem Scanner)
  • Buchruecken: Gradueller Schatten-Uebergang wird nicht als Kante erkannt

Loesung (v4.1): Crop laeuft jetzt nach Dewarp — das Bild ist dann gerade.

Algorithmus: Content-basierte 4-Kanten-Erkennung

Datei: page_crop.py

Input: Entzerrtes BGR-Bild
  │
  ├─ Adaptive Threshold (Gauss, blockSize=51)
  │   → binary (Text=255, Hintergrund=0)
  │
  ├─ Linker Rand (Buchruecken-Schatten):
  │   1. Grauwert-Spaltenmittel in linken 25%
  │   2. Glaetten mit Boxcar-Kernel
  │   3. Transition hell→dunkel finden (> 60% des Helligkeitsbereichs)
  │   4. Fallback: Binaere Vertikal-Projektion
  │
  ├─ Rechter Rand: Binaere Vertikal-Projektion (letzte Ink-Spalte)
  │
  ├─ Oben/Unten: Binaere Horizontal-Projektion (erste/letzte Ink-Zeile)
  │
  ├─ Rausch-Filter: Runs < 0.5% der Dimension ignorieren
  │
  ├─ Sanity-Checks:
  │   - Mindestens eine Kante > 2% Border
  │   - Crop-Flaeche >= 40% des Originals
  │
  └─ Crop + konfigurierbarer Rand (default 1%)

Vergleich alt vs. neu

Eigenschaft Alt (Otsu + Kontur) Neu (Content-basiert)
Binarisierung Otsu (global) Adaptive Threshold
Methode Groesste Kontur → boundingRect 4-Kanten Ink-Projektion
Buchruecken Nicht erkannt Schatten-Gradient-Erkennung
Weiss-auf-weiss Versagt Funktioniert (adaptive)
Format-Matching A4/Letter erzwungen Kein Format-Matching (Content-Bounds)
Position in Pipeline Vor Deskew (Schritt 2) Nach Dewarp (Schritt 4)

Schritt 3: 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

Feinabstimmung (Combined Adjust)

Der Endpoint POST /sessions/{id}/adjust-combined erlaubt die kombinierte Feinabstimmung von Rotation und Shear in einem Schritt. Im Frontend stehen 7 Schieberegler zur Verfuegung:

Rotation (3 Paesse):

Slider Bereich Beschreibung
P1 Iterative ±5° Erster Deskew-Pass (Hough Lines)
P2 Word-Alignment ±3° Zweiter Pass (Wort-Ausrichtung)
P3 Textline ±3° Dritter Pass (Textzeilen-Regression)

Die Summe aller drei ergibt den finalen Rotationswinkel.

Shear (4 Methoden, Radio-Auswahl):

Slider Bereich Beschreibung
A: Textline Drift ±5° Textzeilen-Drift
B: Projection Profile ±5° 2-Pass Projektionsprofil
C: Vertical Edges ±5° Vertikalkanten-Analyse
D: Ensemble ±5° Gewichteter Ensemble-Wert

Nur der per Radio-Button ausgewaehlte Shear-Wert wird verwendet.

POST /sessions/{id}/adjust-combined
Body: {"rotation_degrees": 1.23, "shear_degrees": -0.45}
Response: {"method_used": "manual_combined", "shear_degrees": -0.45, "dewarped_image_url": "..."}

Schritt 5: Spaltenerkennung (Detail)

Algorithmus: detect_column_geometry()

Mehrstufige Erkennung: Seite segmentieren, vertikale Projektionsprofile finden Luecken, Wort-Bounding-Boxes validieren.

Bild → Binarisierung → Seiten-Segmentierung → 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.

Seiten-Segmentierung an Sub-Headern

Farbige Zwischenueberschriften (z.B. „Unit 4: Bonnie Scotland" mit blauem Hintergrund) erzeugen nach Binarisierung Tinte ueber die gesamte Seitenbreite. Diese Baender fuellen Spaltenluecken im vertikalen Projektionsprofil auf und fuehren zu fragmentierten Spalten (z.B. 11 statt 5).

Loesung: Horizontale Gap-Segmentierung (Step 2b)

  1. Horizontales Projektionsprofil berechnen: Zeilensummen ueber den Content-Bereich
  2. Leere Zeilen erkennen: Zeilen mit < 2% Tinten-Dichte (H_GAP_THRESH = 0.02)
  3. Gaps sammeln: Zusammenhaengende leere Zeilen zu Gaps buendeln (Mindestlaenge: max(5, h/200))
  4. Grosse Gaps identifizieren: Gaps > 1.8× Median-Gap-Hoehe = Sub-Header-Trennungen
  5. Segmente bilden: Seite an grossen Gaps aufteilen
  6. Groesstes Segment waehlen: Das hoechste Segment wird fuer die vertikale Projektion verwendet
┌─────────────────────────────────┐
│  Header / Titel                 │  ─── grosser Gap ───
├─────────────────────────────────┤
│  EN  │  DE  │ Example │ Page   │  ← Segment 1 (groesster)
│  ... │  ... │ ...     │ ...    │
├─────────────────────────────────┤
│  Unit 4: Bonnie Scotland        │  ─── grosser Gap ───
├─────────────────────────────────┤
│  EN  │  DE  │ Example │ Page   │  ← Segment 2
│  ... │  ... │ ...     │ ...    │
└─────────────────────────────────┘

Segment-gefilterte Wort-Validierung:

Die Wort-Validierung (Step 5) nutzt nur Tesseract-Woerter innerhalb des gewaehlten Segments. Woerter aus Sub-Header-Bereichen (die die volle Breite einnehmen) werden so ausgeschlossen und koennen die Spaltenluecken-Validierung nicht verfaelschen.

Word-Coverage Gap Detection (Fallback)

Wenn die pixel-basierte Projektion keine ausreichenden Spaltenluecken findet (z.B. bei Seiten mit Illustrationen, die Spaltenluecken teilweise verdecken), greift ein Fallback auf Basis der Tesseract-Wort-Bounding-Boxes:

  1. X-Achse in 2px-Bins aufteilen
  2. Pro Bin zaehlen, wie viele Segment-Woerter ihn ueberdecken
  3. Zusammenhaengende Bins mit 0 Woertern = Gap-Kandidaten
  4. Nur Gaps im inneren 90%-Bereich beruecksichtigen (Raender ignorieren)
  5. Gaps mit Mindestbreite (max(8px, content_w * 0.5%)) werden als Spaltenluecken akzeptiert

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 6: 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.

  3. Box-Boundary-Schutz (box_ranges_inner, neu in v4.2): Bei Seiten mit Box-Zonen (Sub-Sessions) werden Zeilen am Box-Rand nicht faelschlich ausgeschlossen. Das Problem: Die letzte Textzeile ueber einer Box ueberlappt haeufig mit dem Box-Rahmen. Loesung: Die Exclusion-Zone wird um max(border_thickness, 5px) geschrumpft, sodass nur Zeilen innerhalb der Box ausgeschlossen werden.

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."""
    ...

Box-Zonen und Content-Strips (Detail)

Seiten mit Box-Bereichen (z.B. Grammatik-Tipps, Uebungsboxen) werden in Zonen aufgeteilt:

┌──────────────────────────┐
│ Content Zone 0 (Zeilen)  │ ← Vokabeltabelle oben
├──────────────────────────┤
│ ███ Box Zone (border) ███│ ← Sub-Session mit eigener OCR
├──────────────────────────┤
│ Content Zone 2 (Zeilen)  │ ← Vokabeltabelle unten
└──────────────────────────┘

Content-Strip-Verfahren (detect_rows in ocr_pipeline_api.py):

  1. Box-Zonen identifizieren, box_ranges_inner berechnen (geschrumpft um Border-Dicke)
  2. Content-Strips = Seitenbereiche ohne Box-Inneres, vertikal gestapelt
  3. Zeilenerkennung auf gestapeltem Bild, Y-Koordinaten zurueckgemappt
  4. Wort-Filterung: Woerter in Box-Innerem werden ausgeschlossen

Wichtig: box_ranges_inner (nicht box_ranges) wird verwendet, damit Zeilen am Box-Rand nicht abgeschnitten werden. Minimum 5px Margin.


Schritt 7: Worterkennung (Detail)

Schritt 7 bietet zwei Grid-Strategien, auswaehlbar per grid_method-Parameter:

Strategie Parameter Ansatz Benoetigt Spalten/Zeilen?
Hybrid-Grid v2 grid_method=v2 (Default) Top-down: Spalten → Zeilen → Zellen → OCR Ja (Schritte 5+6)
Words-First grid_method=words_first Bottom-up: Woerter → Spalten clustern → Zeilen clustern → Zellen Nein

Words-First Grid Builder: build_grid_from_words()

Datei: cv_words_first.py

Der Words-First Builder arbeitet bottom-up: Er nimmt die pixelgenauen word_boxes aus einem Tesseract Full-Page-Lauf und clustert sie direkt zu Spalten und Zeilen — ohne die vorherige Spalten-/Zeilenerkennung (Schritte 5+6) zu benoetigen.

Algorithmus

Eingabe: word_dicts (flat list), img_w, img_h
                    │
        ┌───────────┴───────────┐
        │ 1. Confidence-Filter  │
        │    conf >= 30         │
        │    Whitespace entf.   │
        └───────────┬───────────┘
                    │
        ┌───────────┴───────────┐
        │ 2. _cluster_columns() │
        │    X-Gap-Analyse      │
        │    Schwelle: median_h │
        │    × 3 (min 3% Breite)│
        └───────────┬───────────┘
                    │
        ┌───────────┴───────────┐
        │ 3. _cluster_rows()    │
        │    Y-Proximity-Grupp. │
        │    Toleranz: median_h │
        │    / 2                │
        └───────────┬───────────┘
                    │
        ┌───────────┴───────────┐
        │ 4. _build_cells()     │
        │    Wort → (col, row)  │
        │    Text + bbox + conf │
        │    word_boxes pro Zelle│
        └───────────┬───────────┘
                    │
            Ausgabe: cells[], columns_meta[]
            (identisch zu build_cell_grid_v2)

Spalten-Clustering

  1. Alle Woerter nach X-Mitte sortieren
  2. Aufeinanderfolgende X-Gaps berechnen
  3. Adaptiver Schwellwert: median_word_height × 3 (min 3% Bildbreite)
  4. Gaps > Schwellwert = Spaltengrenzen
  5. Kein Gap gefunden → 1 Spalte (column_text)
  6. Spaltentypen: column_1, column_2, ... (generisch, positionsbasiert)

Zeilen-Clustering

  1. Woerter zu visuellen Zeilen gruppieren (Y-Toleranz: halbe Worthoehe)
  2. Jede visuelle Zeile = eine Zeile im Grid
  3. Sortiert von oben nach unten

Edge Cases

Fall Behandlung
Einzelne Spalte (Fliesstext) Kein X-Gap → 1 Spalte column_text
Keine Woerter erkannt Leeres Ergebnis ([], [])
Ueberschriften (grosse Schrift) Eigene Zeile durch Y-Gap
Bilder/Grafiken Keine Woerter → automatisch leerer Bereich
Schmale Spalten (Seitenzahlen) Eigene Spalte durch X-Gap

Vergleich v2 vs. Words-First

Kriterium v2 (Top-Down) Words-First (Bottom-Up)
Abhaengigkeiten Spalten + Zeilen noetig Nur Tesseract-Woerter
Spaltentypen Semantisch (EN, DE, ...) Positionsbasiert (1, 2, ...)
OCR Hybrid (full-page + cell-crop) Nur full-page Tesseract
Robustheit Abhaengig von Spalten-/Zeilenerkennung Direkt aus Wortpositionen
Geschwindigkeit Langsamer (cell-crop pro Zelle) Schneller (kein OCR-Lauf)
Genauigkeit Besser bei schmalen Spalten Besser bei ungewoehnlichen Layouts

Hybrid-Grid v2: build_cell_grid_v2()

Schritt 7 nutzt im Default 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, PaddleOCR)
  5. Fallback: Bei leerem Ergebnis → PSM 7 (Einzelzeile) statt PSM 6
  6. Bereinigung: _clean_cell_text_lite() (aggressives Noise-Filtering)

PaddleOCR Remote-Engine (engine=paddle)

PaddleOCR (PP-OCRv5 Latin) laeuft als eigenstaendiger Microservice auf einem Hetzner x86_64 Server, da PaddlePaddle nicht auf ARM64 (Mac Mini) laeuft.

Mac Mini (klausur-service)          Hetzner (paddleocr-service)
       │   HTTPS POST + Bild           │
       │ ──────────────────────────▶    │  PP-OCRv5 Latin
       │                                │  FastAPI (Port 8095)
       │   JSON word_boxes              │  API-Key Auth
       │ ◀──────────────────────────    │

Besonderheiten:

  • Erzwingt automatisch grid_method=words_first (full-page OCR, kein cell-crop)
  • Async HTTP-Client (paddleocr_remote.py) mit 30s Timeout
  • Koordinaten sind bereits absolut (kein content_bounds Offset noetig)
  • API-Key Authentifizierung ueber X-API-Key Header
  • Dateien: paddleocr-service/main.py, services/paddleocr_remote.py, cv_ocr_engines.py:ocr_region_paddle()

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 8: 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 8: Strukturerkennung (Detail)

Erkennt Boxen, Zonen, Farbregionen und grafische Elemente auf der Seite. Laeuft nach der Worterkennung (Schritt 7), damit OCR-Wortpositionen fuer die Unterscheidung von Text vs. Grafik zur Verfuegung stehen.

Teilschritte

  1. Box-Erkennung (cv_box_detect.py): Linien-Rahmen und farbige Hintergruende
  2. Zonen-Aufteilung (split_page_into_zones): Seite in Box- und Content-Zonen aufteilen
  3. Farb-Analyse (cv_color_detect.py): HSV-basierte Erkennung farbiger Textbereiche
  4. Grafik-Erkennung (cv_graphic_detect.py): Nicht-Text-Grafiken identifizieren

Grafik-Erkennung: Region-basierter Ansatz

Zwei Paesse trennen farbige Grafiken von farbigem Text und erkennen schwarze Illustrationen:

Pass 1 — Farbige Bildregionen:

  1. HSV-Saturation-Kanal extrahieren (Schwelle > 40)
    • Schwarzer Text hat Saettigung ≈ 0 → unsichtbar auf diesem Kanal
  2. Starke Dilation (25×25 Ellipse) verschmilzt nahe Farbpixel zu Regionen
  3. Fuer jede Region: Wort-Ueberlappung pruefen
    • > 50 % Ueberlappung mit OCR-Woertern → farbiger Text → ueberspringen
    • ≤ 50 % → farbige Grafik/Bild → behalten
  4. Minimum 200 Farbpixel erforderlich (kein Rauschen)
  5. Regionen > 50 % der Bildbreite oder -hoehe → Seitenumfassend → ueberspringen

Pass 2 — Schwarze Illustrationen:

  1. Otsu-Binarisierung fuer Tinten-Maske
  2. Ausschlusszonen: OCR-Woerter (5 px Padding) + erkannte Boxen (8 px Inset)
  3. Farbige Pixel aus Pass 1 ebenfalls ausschliessen
  4. Nur Konturen mit Flaeche > 5000 px und min(Breite, Hoehe) > 40 px

Deduplizierung: Ueberlappende Elemente (> 50 % IoU der kleineren Bounding-Box) werden zusammengefasst. Ergebnis nach Flaeche absteigend sortiert.

Response-Format

{
  "boxes": [
    {"x": 50, "y": 300, "w": 1100, "h": 200, "confidence": 0.85,
     "border_thickness": 3, "bg_color_name": "blue", "bg_color_hex": "#2563eb"}
  ],
  "zones": [
    {"index": 0, "zone_type": "content", "x": 50, "y": 50, "w": 1100, "h": 250},
    {"index": 1, "zone_type": "box", "x": 50, "y": 300, "w": 1100, "h": 200}
  ],
  "graphics": [
    {"x": 100, "y": 500, "w": 150, "h": 120, "area": 8500,
     "shape": "image", "color_name": "red", "color_hex": "#dc2626",
     "confidence": 0.72}
  ],
  "color_pixel_counts": {"red": 1234, "blue": 5678},
  "has_words": true,
  "word_count": 96,
  "duration_seconds": 0.45
}

Grafik-Shape-Typen

Shape Quelle Beschreibung
image Pass 1 Farbige Grafik/Bild (Ballons, Pfeile, Icons)
illustration Pass 2 Grosse schwarze Zeichnung/Illustration

Erkannte Farben

red, orange, yellow, green, blue, purple, black — basierend auf dem Median-Hue der saturierten Pixel in der Region.

Frontend-Anzeige

StepStructureDetection.tsx zeigt:

  • Boxen-Liste mit Position, Hintergrundfarbe und Confidence
  • Zonen-Uebersicht (Content vs. Box)
  • Farb-Zusammenfassung (Pixel-Counts)
  • Grafik-Liste mit Shape, Abmessungen, Farbe und Confidence

Schritt 9: Rekonstruktion (Detail)

Drei 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

Overlay-Modus (neu in v4.2)

Ganzseitige Tabellenrekonstruktion mit Pixel-basierter Wortpositionierung. Nur verfuegbar bei Parent-Sessions mit Sub-Sessions (Box-Bereiche).

Funktionsweise:

  1. Sub-Session-Merging: Zellen aus Sub-Sessions werden koordinaten-konvertiert und in die Parent-Session eingefuegt. Die Umrechnung laeuft ueber die Box-Zone:

    parentCellX = boxXPct + (subCell.bbox_pct.x / 100) * boxWPct
    parentCellY = boxYPct + (subCell.bbox_pct.y / 100) * boxHPct
    
  2. 180°-Rotation: Bei Parent-Sessions mit Boxen wird das Bild standardmaessig 180° gedreht, da der Scan haeufig kopfueber vorliegt. Die Pixel-Analyse arbeitet auf dem rotierten Bild:

    • Canvas: ctx.translate(W, H); ctx.rotate(Math.PI)
    • Zell-Koordinaten: (100 - x - w, 100 - y - h) fuer rotiertes Space
    • Cluster-Ruecktransformation: start → cw-1-end, danach reverse()
  3. Pixel-Wortpositionierung: Der usePixelWordPositions Hook analysiert dunkle Pixel per vertikaler Projektion, findet Wortgruppen-Cluster und berechnet die exakte horizontale Position + Auto-Schriftgroesse.

Layout: 50/50 Grid (links Originalbild, rechts Rekonstruktion)

Toolbar:

  • Schriftgroessen-Slider (30120%)
  • Bold-Toggle
  • 180°-Rotations-Toggle
  • Speichern-Button

Visuelle Elemente:

  • Spaltenlinien (aus column_result.columns)
  • Zeilenlinien (aus row_result.rows)
  • Box-Zonen-Markierung (blau, halbtransparent)
  • Editierbare Inputs an Pixel-Positionen

Shared Hook: usePixelWordPositions

Extrahierter Hook fuer die Pixel-basierte Wortpositionierung, genutzt in StepLlmReview (Schritt 8) und StepReconstruction (Schritt 9).

function usePixelWordPositions(
  imageUrl: string,
  cells: GridCell[],
  active: boolean,
  rotation: 0 | 180 = 0,
): Map<string, WordPosition[]>

Algorithmus:

  1. Bild in offscreen Canvas laden (optional 180° gedreht)
  2. Pro Zelle: getImageData() → vertikale Projektion (dunkle Pixel pro Spalte)
  3. Cluster-Erkennung (Schwelle: 3% der Zellhoehe, Gap: 2% der Zellbreite)
  4. Bei Rotation: Cluster zurueck ins Original-Koordinatensystem spiegeln
  5. Text-Gruppen (split bei 3+ Leerzeichen) auf Cluster matchen
  6. Auto-Schriftgroesse per measureText() + fontRatio
  7. Mode-Normalisierung: Haeufigste fontRatio (gerundet auf 0.02) auf alle anwenden

Rueckgabe: Map<cell_id, WordPosition[]> mit xPct, wPct, yPct, hPct, text, fontRatio

!!! note "Per-Word Y-Positionierung (v4.3.1)" WordPosition enthaelt seit v4.3.1 auch yPct und hPct. Dadurch rendert jedes Wort an seiner tatsaechlichen vertikalen Position, statt alle Woerter einer Zelle auf der Zell-Mitte zu stapeln. Bei Zellen ohne word_boxes (Fallback) werden yPct/hPct aus cell.bbox_pct uebernommen.

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"}]}

Grid Editor (Detail)

Der Grid Editor baut aus den Kombi-Wortdaten (PaddleOCR + Tesseract) ein strukturiertes, Excel-aehnliches Grid mit Zonen, Spalten, Zeilen und Zellen. Er ersetzt die manuelle Rekonstruktion fuer Vokabelseiten mit komplexen Layouts (Bilder, Ueberschriften, IPA-Lautschrift).

Dateien:

Datei Beschreibung
grid_editor_api.py _build_grid_core() Pipeline, alle Steps
cv_ocr_engines.py IPA-Korrektur, Britfone-Woerterbuch, Garbled-IPA-Erkennung
cv_vocab_types.py PageZone (mit image_overlays), ColumnGeometry
tests/test_grid_editor_api.py 27 Tests

_build_grid_core() Pipeline

Die Grid-Erstellung laeuft in mehreren Steps:

Kombi-Wortdaten
  │
  ├─ Zone Merging: Content-Zonen durch Bilder zusammenfuegen
  │   → _merge_content_zones_across_boxes()
  │
  ├─ Step 1: Zonen-Grid-Berechnung
  │   → Spalten (Column Union), Zeilen, Zellen pro Zone
  │
  ├─ Step 2: Header-Zeilen erkennen + mergen
  │
  ├─ Step 3: Ghost-Filter
  │   → _filter_border_ghosts(): Einzel-Char-Artefakte an Box-Raendern
  │
  ├─ Step 4: Farb-Annotation
  │   → detect_word_colors(): HSV-Farbanalyse aller word_boxes
  │
  ├─ Step 4c: Oversized Word Box Removal
  │   → word_boxes > 3x Median entfernen (Grafik-Artefakte)
  │
  ├─ Step 5: Overlay-Wort-Filter
  │   → Woerter innerhalb image_overlays entfernen
  │
  ├─ Step 5a: Heading Detection by Color + Height
  │   → _detect_heading_rows_by_color(): Farbige Ueberschriften erkennen
  │
  ├─ Step 5b: Fix Unmatched Parentheses
  │
  ├─ Step 5c: IPA Phonetic Correction
  │   → fix_cell_phonetics(): Garbled OCR-IPA durch Britfone ersetzen
  │
  └─ Step 5d: IPA Continuation Detection
      → fix_ipa_continuation_cell(): Reine Klammer-Zellen als IPA-Fortsetzung

Zone Merging Across Images

_merge_content_zones_across_boxes() erkennt das Muster [content, box, content] und fuegt die Content-Zonen zu einer einzigen Zone zusammen. Die Box-Zonen werden als image_overlays auf der gemergten Zone gespeichert.

Vorher:  Zone 1 (content) → Zone 2 (box/Bild) → Zone 3 (content)
Nachher: Zone 1 (content, mit image_overlays=[Zone 2])

PageZone.image_overlays:

[{"y": 120, "height": 85, "x": 50, "width": 400, "box": {...}}]

Box-Zonen die NICHT zwischen Content-Zonen liegen (z.B. "VOCABULARY" Header-Box) bleiben als eigenstaendige Zone erhalten.

Heading Detection by Color + Height

_detect_heading_rows_by_color() erkennt farbige Zwischenueberschriften (z.B. "Unit 4: Bonnie Scotland") nach der Farb-Annotation:

  1. Farbkriterium: ALLE word_boxes der Zeile haben color_name != 'black' (typisch: blau)
  2. Hoehenkriterium: Mittlere Worthoehe > 1.2× Median aller Woerter in der Zone

Erkannte Headings werden zu einer Spanning-Cell zusammengefuegt (col_type: 'heading').

Ghost-Filter

_filter_border_ghosts() entfernt einzelne Zeichen (a-z, A-Z, 0-9) die nahe an Box-Raendern erkannt werden — typische OCR-Artefakte von Rahmenlinien.

Ausnahme: Borderless Boxes (border_thickness = 0) ueberspringen den Ghost-Filter, da dort keine Rahmen-Artefakte entstehen.

IPA Phonetic Correction (Step 5c)

PaddleOCR erkennt IPA-Symbole als ASCII-Zeichen:

IPA-Symbol OCR liest
ʒ 3
ʃ f
ə 9
ˈ '
ɪ i oder 1

fix_cell_phonetics() ersetzt garbled OCR-IPA durch korrekte Lautschrift aus dem Britfone-Woerterbuch (British English). Nur Zellen mit col_type == 'column_en' werden verarbeitet.

_text_has_garbled_ipa() erkennt garbled IPA anhand von:

  • Typischen OCR-IPA-Zeichen: ' 3 9 @ : ;
  • Bracket-Notation ohne echte IPA-Symbole: [n, nn], [1uedtX,1]

IPA Continuation Detection (Step 5d)

Vokabelseiten haben manchmal Zeilen, die nur Lautschrift enthalten (Fortsetzung des Headwords der vorherigen Zeile). Diese werden von PaddleOCR als garbled Text erkannt.

fix_ipa_continuation_cell():

  1. Prueft ob die Zelle komplett in Klammern steht ([...]) — Zellen wie "employee [im'ploi:]" werden NICHT ueberschrieben
  2. Sucht das Headword in der Zeile darueber
  3. Stripped Grammatik-Annotationen: [sth.], (adj.) etc.
  4. Schlaegt IPA im Britfone-Woerterbuch nach
  5. Beruecksichtigt alle Wortteile (z.B. "close sth. down" → [klˈəʊz dˈaʊn])

en_col_type Erkennung

Die Erkennung der Englisch-Headword-Spalte nutzt Bracket-IPA-Pattern-Count statt "laengster Durchschnittstext":

  1. Fuer jede column_*-Spalte: Zaehle Zellen mit [ im Text
  2. Spalte mit den meisten Klammer-Mustern = en_col_type
  3. Fallback: Laengster Durchschnittstext (wenn keine Klammern vorhanden)

Dies verhindert, dass Beispielsatz-Spalten (laenger aber ohne IPA) faelschlicherweise als Headword-Spalte erkannt werden.

Oversized Word Box Removal (Step 4c)

Entfernt word_boxes deren Flaeche > 3× der Median-Flaeche aller Woerter in der Zone. Diese sind typischerweise Grafik-Artefakte (z.B. ein einzelnes "N" das eine ganze Illustration abdeckt).

Tests

cd klausur-service/backend && pytest tests/test_grid_editor_api.py -v  # 27 Tests
Klasse Tests Beschreibung
TestZoneMerging 4 Content-Zone-Merging ueber Box-Zonen
TestHeadingDetection 3 Farb- + Hoehenbasierte Heading-Erkennung
TestGhostFilter 4 Border-Ghost-Filterung inkl. Borderless
TestOversizedWordBoxes 2 Grafik-Artefakt-Entfernung
TestGarbledIpaDetection 8 Bracket-IPA, Continuation, en_col_type
TestColumnUnion 3 Spalten-Vereinigung ueber Zonen
TestHeaderMerging 3 Header-Zeilen zusammenfuegen

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 5 Spalten Projektionsprofil kann verschmelzen (Segmentierung hilft) 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/"


OCR Overlay — Alternative Pipelines

URL: https://macmini:3002/ai/ocr-overlay

Neben der vollen 10-Schritt-Pipeline gibt es die OCR Overlay-Seite mit vereinfachten Pfaden fuer schnelle Ergebnisse. Alle drei Modi teilen die gleichen Vorverarbeitungsschritte (Orient → Deskew → Dewarp → Crop).

Modus-Uebersicht

Modus Schritte Engine Endpoint Beschreibung
Pipeline 7 Tesseract /words (SSE) Volle Pipeline: Zeilen + Woerter + Overlay
Paddle Direct 5 PaddleOCR /paddle-direct PaddleOCR ersetzt Zeilen + Woerter + Overlay
Kombi 5 PaddleOCR + Tesseract /paddle-kombi Beide Engines, Ergebnisse gemittelt

Flussdiagramm

┌──────────────────────────────────────────────────────────────┐
│ GEMEINSAME VORVERARBEITUNG (alle 3 Modi)                      │
│                                                                │
│ Schritt 1: Orientierung                                        │
│ Schritt 2: Deskew                                              │
│ Schritt 3: Dewarp                                              │
│ Schritt 4: Crop                                                │
└──────────────────┬────────────────────┬───────────────────────┘
                   │                    │
       ┌───────────┼────────────────────┼────────────────┐
       ▼           ▼                    ▼                ▼
   PIPELINE     PADDLE DIRECT       KOMBI-MODUS
   (7 Schritte) (5 Schritte)       (5 Schritte)
       │           │                    │
   Zeilen-     PaddleOCR           PaddleOCR
   erkennung   word_boxes          + Tesseract
       │           │                parallel
   Woerter-    build_grid_             │
   erkennung   from_words()       _merge_paddle_
       │           │               tesseract()
   Overlay     Overlay                 │
       │           │              build_grid_
       ▼           ▼              from_words()
   Ergebnis    Ergebnis                │
                                  Overlay
                                      │
                                  Ergebnis

Paddle Direct

PaddleOCR laeuft auf dem vorverarbeiteten Bild und erkennt Woerter direkt.

Endpoint: POST /api/v1/ocr-pipeline/sessions/{id}/paddle-direct

Ablauf:

  1. Cropped/dewarped Bild laden (Prioritaet: cropped > dewarped > original)
  2. ocr_region_paddle(img_bgr, region=None) aufrufen
  3. build_grid_from_words(word_dicts, img_w, img_h) fuer Grid-Erstellung
  4. Cells mit ocr_engine="paddle_direct" taggen
  5. In DB speichern (current_step=8)

Frontend: PaddleDirectStep.tsx — wiederverwendbare Komponente mit konfigurierbaren Props.

Kombi-Modus (PaddleOCR + Tesseract)

!!! info "Motivation" PaddleOCR liefert gute Texterkennung, positioniert Woerter aber manchmal falsch (z.B. !Betonung als ein Wort, Bullet Points nicht erkannt). Tesseract erkennt Sonderzeichen besser und liefert feinere Word-Level-Boxen. Der Kombi-Modus nutzt beide Engines und mittelt die Koordinaten.

Endpoint: POST /api/v1/ocr-pipeline/sessions/{id}/paddle-kombi

Ablauf:

  1. Cropped/dewarped Bild laden
  2. Parallel beide Engines aufrufen:
    • ocr_region_paddle(img_bgr, region=None)paddle_words
    • pytesseract.image_to_data(pil_img, lang='eng+deu', config='--psm 6 --oem 3')tess_words
  3. Merge: _merge_paddle_tesseract(paddle_words, tess_words)
  4. build_grid_from_words(merged_words, img_w, img_h) fuer Grid
  5. Cells mit ocr_engine="kombi" taggen
  6. In DB speichern

Merge-Algorithmus (v2: Row-Based Sequence Alignment)

!!! info "Rewrite (v4.3)" Der Merge wurde von IoU-basiertem Matching auf Row-Based Sequence Alignment umgestellt. Multi-Word Paddle-Boxen werden vor dem Merge in Einzelwoerter aufgeteilt (_split_paddle_multi_words).

Ablauf:

  1. Row Grouping: Woerter beider Engines nach Y-Position in Zeilen gruppieren (12px Toleranz)
  2. Row Matching: Paddle- und Tesseract-Zeilen ueber vertikale Naehe zuordnen
  3. Sequence Alignment: Innerhalb jeder gematchten Zeile links-nach-rechts durchlaufen:
    • Gleicher Text oder Substring-Match: Zusammenfuehren (Paddle-Text, gemittelte Koordinaten)
    • Raeumlicher Overlap >= 50%: Auch bei unterschiedlichem Text als Duplikat behandeln
    • Nur bei einer Engine: Wort beibehalten (falls Confidence >= 30)
  4. Ungematchte Zeilen: Paddle-Zeilen behalten, Tesseract-Zeilen nur mit Confidence >= 40
flowchart TD
    A[Beide Engines] --> B[Row Grouping<br/>Y-Toleranz 12px]
    B --> C[Row Matching<br/>vertikale Naehe]
    C --> D{Gleicher Text<br/>oder Overlap >= 50%?}
    D -->|Ja| E[Deduplizieren:<br/>Paddle-Text + gemittelte Coords]
    D -->|Nein| F{Wort nur bei<br/>einer Engine?}
    F -->|Ja| G[Beibehalten<br/>falls conf >= 30]
    F -->|Nein| H[Beide behalten<br/>verschiedene Positionen]

Koordinaten-Mittelung:

merged_left = (paddle_left × paddle_conf + tess_left × tess_conf) / (paddle_conf + tess_conf)

Gleiches Prinzip fuer top, width, height. Der Text kommt immer von PaddleOCR (bessere Texterkennung).

Raeumlicher Overlap-Check (v4.3.1):

Wenn zwei Woerter >= 50% horizontal ueberlappen, werden sie als dasselbe physische Wort behandelt — unabhaengig davon, ob die OCR-Texte unterschiedlich sind (z.B. "hello" vs "helo"). Dies verhindert, dass leicht unterschiedliche Erkennungen als separate Woerter uebereinander im Overlay erscheinen.

Dateien

Datei Aenderung
ocr_pipeline_api.py _split_paddle_multi_words(), _group_words_into_rows(), _merge_row_sequences(), _merge_paddle_tesseract(), /paddle-kombi Endpoint
admin-lehrer/.../ocr-overlay/types.ts KOMBI_STEPS Konstante
admin-lehrer/.../ocr-overlay/useSlideWordPositions.ts Slide-Positionierung mit yPct/hPct
admin-lehrer/.../ocr-overlay/usePixelWordPositions.ts Pixel-Cluster-Positionierung mit yPct/hPct
admin-lehrer/.../ocr-overlay/OverlayReconstruction.tsx Rendering mit per-Word Y-Positionen
admin-lehrer/.../PaddleDirectStep.tsx Wiederverwendbar mit endpoint/engineKey Props
admin-lehrer/.../ocr-overlay/page.tsx 3er-Toggle: Pipeline / Paddle Direct / Kombi

Tests

cd klausur-service/backend && pytest tests/test_paddle_kombi.py -v  # 36 Tests

Test-Klassen:

Klasse Tests Beschreibung
TestSplitPaddleMultiWords 7 Multi-Word-Box-Splitting
TestGroupWordsIntoRows 5 Y-Position Row Clustering
TestMergeRowSequences 10 Sequence Alignment innerhalb einer Zeile
TestMergePaddleTesseract 8 Vollstaendiger Merge mit Row-Matching
TestMergeRealWorldRegression 1 Regression mit Echtdaten
TestSpatialOverlapDedup 4 Raeumliche Overlap-Deduplizierung
TestSplitThenMerge 1 Split + Merge End-to-End
Testklasse Tests Beschreibung
TestBoxIoU 6 IoU-Berechnung: identisch, kein Overlap, teilweise, enthalten, Kante, Null-Flaeche
TestMergePaddleTesseract 10 Merge: Match-Averaging, kein Match, Low-Conf-Drop, leer, IoU-Schwelle, Text-Praeferenz, Zero-Conf
TestMergePaddleTesseractBulletPoints 2 Bullet-Points und Sonderzeichen von Tesseract

Aenderungshistorie

Datum Version Aenderung
2026-03-20 4.7.0 Grid Editor: Zone Merging ueber Bilder (image_overlays), Heading Detection (Farbe + Hoehe), Ghost-Filter (borderless-aware), Oversized Word Box Removal, IPA Phonetic Correction (Britfone), IPA Continuation Detection, en_col_type via Bracket-Count. 27 Tests.
2026-03-16 4.6.0 Strukturerkennung (Schritt 8): Region-basierte Grafikerkennung (cv_graphic_detect.py) mit Zwei-Pass-Verfahren (Farbregionen + schwarze Illustrationen), Wort-Ueberlappungs-Filter, Box/Zonen/Farb-Analyse. Schritt laeuft nach Worterkennung.
2026-03-12 4.5.0 Kombi-Modus (PaddleOCR + Tesseract): Beide Engines laufen parallel, Koordinaten werden IoU-basiert gematcht und confidence-gewichtet gemittelt. Ungematchte Tesseract-Woerter (Bullets, Symbole) werden hinzugefuegt. 3er-Toggle in OCR Overlay.
2026-03-12 4.4.0 PaddleOCR Remote-Engine (engine=paddle): PP-OCRv5 Latin auf Hetzner x86_64. Neuer Microservice (paddleocr-service/), HTTP-Client (paddleocr_remote.py), Frontend-Dropdown-Option. Nutzt words_first Grid-Methode.
2026-03-12 4.3.0 Words-First Grid Builder (cv_words_first.py): Bottom-up-Algorithmus clustert Tesseract word_boxes direkt zu Spalten/Zeilen/Zellen. Neuer grid_method Parameter im /words Endpoint. Frontend-Toggle in StepWordRecognition.
2026-03-10 4.2.0 Rekonstruktion: Overlay-Modus mit Pixel-Wortpositionierung, 180°-Rotation, Sub-Session-Merging, usePixelWordPositions Hook, Box-Boundary-Schutz (box_ranges_inner)
2026-03-05 3.1.0 Spalten: Seiten-Segmentierung an Sub-Headern, Word-Coverage Fallback, Segment-gefilterte Validierung
2026-03-05 3.0.1 Dewarp: Feinabstimmung mit 7 Schiebereglern (3 Rotation + 4 Shear), Combined-Adjust-Endpoint
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