docs+tests: update OCR Pipeline docs and add overlay position tests
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 27s
CI / test-go-edu-search (push) Successful in 28s
CI / test-python-klausur (push) Failing after 2m5s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 17s

MkDocs: document row-based merge algorithm, spatial overlap dedup,
and per-word yPct/hPct rendering in OCR Pipeline docs.

Tests: add 9 vitest tests for useSlideWordPositions covering
word-box path, fallback path, and yPct/hPct contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-13 21:03:00 +01:00
parent d6f51e4418
commit c2c082d4b4
2 changed files with 230 additions and 10 deletions

View File

@@ -937,7 +937,13 @@ function usePixelWordPositions(
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`, `text`, `fontRatio`
**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
@@ -1172,16 +1178,32 @@ PaddleOCR laeuft auf dem vorverarbeiteten Bild und erkennt Woerter direkt.
5. Cells mit `ocr_engine="kombi"` taggen
6. In DB speichern
#### Merge-Algorithmus
#### 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
```mermaid
flowchart TD
A[Paddle-Wort] --> B{Tesseract-Match<br/>IoU > 0.3?}
B -->|Ja| C[Koordinaten mitteln<br/>gewichtet nach Confidence]
B -->|Nein| D[Paddle-Wort behalten]
E[Ungematchte<br/>Tesseract-Woerter] --> F{Confidence >= 40?}
F -->|Ja| G[Hinzufuegen<br/>Bullet Points, Symbole]
F -->|Nein| H[Verwerfen]
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:**
@@ -1192,21 +1214,43 @@ merged_left = (paddle_left × paddle_conf + tess_left × tess_conf) / (paddle_co
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` | `_box_iou()`, `_merge_paddle_tesseract()`, `/paddle-kombi` Endpoint |
| `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
```bash
cd klausur-service/backend && pytest tests/test_paddle_kombi.py -v
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 |