From 45435f226fbd313ce541acee5be9526136d31f89 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sat, 28 Feb 2026 17:13:58 +0100 Subject: [PATCH] feat(ocr-pipeline): line grouping fix + RapidOCR integration Fix A: Use _group_words_into_lines() with adaptive Y-tolerance to correctly order words in multi-line cells (fixes word reordering bug). RapidOCR: Add as alternative OCR engine (PaddleOCR models on ONNX Runtime, native ARM64). Engine selectable via dropdown in UI or ?engine= query param. Auto mode prefers RapidOCR when available. Co-Authored-By: Claude Opus 4.6 --- .../app/(admin)/ai/ocr-pipeline/types.ts | 1 + .../ocr-pipeline/StepWordRecognition.tsx | 35 ++++- klausur-service/backend/cv_vocab_pipeline.py | 140 +++++++++++++++++- klausur-service/backend/ocr_pipeline_api.py | 21 ++- 4 files changed, 180 insertions(+), 17 deletions(-) diff --git a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts index a8a1f3a..3da5c8e 100644 --- a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts +++ b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts @@ -143,6 +143,7 @@ export interface WordResult { image_width: number image_height: number duration_seconds: number + ocr_engine?: string summary: { total_entries: number with_english: number diff --git a/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx b/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx index 76842c3..5c2cb59 100644 --- a/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx +++ b/admin-lehrer/components/ocr-pipeline/StepWordRecognition.tsx @@ -22,6 +22,8 @@ export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRec const [activeIndex, setActiveIndex] = useState(0) const [editedEntries, setEditedEntries] = useState([]) const [mode, setMode] = useState<'overview' | 'labeling'>('overview') + const [ocrEngine, setOcrEngine] = useState<'auto' | 'tesseract' | 'rapid'>('auto') + const [usedEngine, setUsedEngine] = useState('') const enRef = useRef(null) @@ -35,6 +37,7 @@ export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRec const info = await res.json() if (info.word_result) { setWordResult(info.word_result) + setUsedEngine(info.word_result.ocr_engine || '') initEntries(info.word_result.entries) return } @@ -54,27 +57,29 @@ export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRec setActiveIndex(0) } - const runAutoDetection = useCallback(async () => { + const runAutoDetection = useCallback(async (engine?: string) => { if (!sessionId) return + const eng = engine || ocrEngine setDetecting(true) setError(null) try { - const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words`, { + const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?engine=${eng}`, { method: 'POST', }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })) throw new Error(err.detail || 'Worterkennung fehlgeschlagen') } - const data: WordResult = await res.json() + const data = await res.json() setWordResult(data) + setUsedEngine(data.ocr_engine || eng) initEntries(data.entries) } catch (e) { setError(e instanceof Error ? e.message : 'Unbekannter Fehler') } finally { setDetecting(false) } - }, [sessionId]) + }, [sessionId, ocrEngine]) const handleGroundTruth = useCallback(async (isCorrect: boolean) => { if (!sessionId) return @@ -512,6 +517,17 @@ export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRec {wordResult && (
+ {/* OCR Engine selector */} + + + {/* Show which engine was used */} + {usedEngine && ( + + {usedEngine} + + )} +