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