From ced5bb3dd312424c3809e0a00307e6e4e7e1a10d Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 12 Mar 2026 06:46:05 +0100 Subject: [PATCH] feat: Words-First Grid Builder (bottom-up alternative zu cell_grid_v2) Neuer Algorithmus in cv_words_first.py: Clustert Tesseract word_boxes direkt zu Spalten (X-Gap) und Zeilen (Y-Proximity), baut Zellen an Schnittpunkten. Kein Spalten-/Zeilenerkennung noetig. - cv_words_first.py: _cluster_columns, _cluster_rows, _build_cells, build_grid_from_words - ocr_pipeline_api.py: grid_method Parameter (v2|words_first) im /words Endpoint - StepWordRecognition.tsx: Dropdown Toggle fuer Grid-Methode - OCR-Pipeline.md: Doku v4.3.0 mit Words-First Algorithmus - 15 Unit-Tests fuer cv_words_first Co-Authored-By: Claude Opus 4.6 --- .../ocr-pipeline/StepWordRecognition.tsx | 22 +- .../services/klausur-service/OCR-Pipeline.md | 264 ++++++++++++++-- klausur-service/backend/cv_vocab_pipeline.py | 1 + klausur-service/backend/cv_words_first.py | 282 ++++++++++++++++++ klausur-service/backend/ocr_pipeline_api.py | 105 ++++++- .../backend/tests/test_cv_words_first.py | 214 +++++++++++++ 6 files changed, 854 insertions(+), 34 deletions(-) create mode 100644 klausur-service/backend/cv_words_first.py create mode 100644 klausur-service/backend/tests/test_cv_words_first.py diff --git a/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx b/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx index d213074..e74d655 100644 --- a/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx +++ b/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx @@ -63,6 +63,7 @@ export function StepWordRecognition({ sessionId, onNext, goToStep, skipHealGaps const [ocrEngine, setOcrEngine] = useState<'auto' | 'tesseract' | 'rapid'>('auto') const [usedEngine, setUsedEngine] = useState('') const [pronunciation, setPronunciation] = useState<'british' | 'american'>('british') + const [gridMethod, setGridMethod] = useState<'v2' | 'words_first'>('v2') // Streaming progress state const [streamProgress, setStreamProgress] = useState<{ current: number; total: number } | null>(null) @@ -112,7 +113,7 @@ export function StepWordRecognition({ sessionId, onNext, goToStep, skipHealGaps let res: Response | null = null for (let attempt = 0; attempt < 2; attempt++) { res = await fetch( - `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?stream=true&engine=${eng}&pronunciation=${pronunciation}${skipHealGaps ? '&skip_heal_gaps=true' : ''}`, + `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?stream=${gridMethod === 'v2' ? 'true' : 'false'}&engine=${eng}&pronunciation=${pronunciation}${skipHealGaps ? '&skip_heal_gaps=true' : ''}&grid_method=${gridMethod}`, { method: 'POST' }, ) if (res.ok) break @@ -128,6 +129,13 @@ export function StepWordRecognition({ sessionId, onNext, goToStep, skipHealGaps throw new Error(err.detail || 'Worterkennung fehlgeschlagen') } + // words_first returns plain JSON (no streaming) + if (gridMethod === 'words_first') { + const data = await res.json() as GridResult + applyGridResult(data) + return + } + const reader = res.body!.getReader() const decoder = new TextDecoder() let buffer = '' @@ -220,7 +228,7 @@ export function StepWordRecognition({ sessionId, onNext, goToStep, skipHealGaps setDetecting(false) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sessionId, ocrEngine, pronunciation]) + }, [sessionId, ocrEngine, pronunciation, gridMethod]) const handleGroundTruth = useCallback(async (isCorrect: boolean) => { if (!sessionId) return @@ -789,6 +797,16 @@ export function StepWordRecognition({ sessionId, onNext, goToStep, skipHealGaps {gridResult && (
+ {/* Grid method selector */} + + {/* OCR Engine selector */}