diff --git a/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx b/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx index 340555d..981cd76 100644 --- a/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx +++ b/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx @@ -49,7 +49,7 @@ export default function OcrPipelinePage() { const renderStep = () => { switch (currentStep) { case 0: - return + return case 1: return case 2: diff --git a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts index a727661..0ffe7ff 100644 --- a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts +++ b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts @@ -13,6 +13,8 @@ export interface SessionInfo { image_width: number image_height: number original_image_url: string + deskew_result?: DeskewResult + dewarp_result?: DewarpResult } export interface DeskewResult { diff --git a/admin-lehrer/components/ocr-pipeline/ImageCompareView.tsx b/admin-lehrer/components/ocr-pipeline/ImageCompareView.tsx index 774da28..2d448a7 100644 --- a/admin-lehrer/components/ocr-pipeline/ImageCompareView.tsx +++ b/admin-lehrer/components/ocr-pipeline/ImageCompareView.tsx @@ -9,6 +9,7 @@ interface ImageCompareViewProps { originalUrl: string | null deskewedUrl: string | null showGrid: boolean + showGridLeft?: boolean showBinarized: boolean binarizedUrl: string | null leftLabel?: string @@ -77,6 +78,7 @@ export function ImageCompareView({ originalUrl, deskewedUrl, showGrid, + showGridLeft, showBinarized, binarizedUrl, leftLabel, @@ -95,12 +97,15 @@ export function ImageCompareView({
{originalUrl && !leftError ? ( - Original Scan setLeftError(true)} - /> + <> + Original Scan setLeftError(true)} + /> + {showGridLeft && } + ) : (
{leftError ? 'Fehler beim Laden' : 'Noch kein Bild'} diff --git a/admin-lehrer/components/ocr-pipeline/StepDeskew.tsx b/admin-lehrer/components/ocr-pipeline/StepDeskew.tsx index cb3bb9b..14786ce 100644 --- a/admin-lehrer/components/ocr-pipeline/StepDeskew.tsx +++ b/admin-lehrer/components/ocr-pipeline/StepDeskew.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import type { DeskewGroundTruth, DeskewResult, SessionInfo } from '@/app/(admin)/ai/ocr-pipeline/types' import { DeskewControls } from './DeskewControls' import { ImageCompareView } from './ImageCompareView' @@ -8,10 +8,11 @@ import { ImageCompareView } from './ImageCompareView' const KLAUSUR_API = '/klausur-api' interface StepDeskewProps { + sessionId?: string | null onNext: (sessionId: string) => void } -export function StepDeskew({ onNext }: StepDeskewProps) { +export function StepDeskew({ sessionId: existingSessionId, onNext }: StepDeskewProps) { const [session, setSession] = useState(null) const [deskewResult, setDeskewResult] = useState(null) const [uploading, setUploading] = useState(false) @@ -22,6 +23,42 @@ export function StepDeskew({ onNext }: StepDeskewProps) { const [error, setError] = useState(null) const [dragOver, setDragOver] = useState(false) + // Reload session data when navigating back from a later step + useEffect(() => { + if (!existingSessionId || session) return + + const loadSession = async () => { + try { + const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${existingSessionId}`) + if (!res.ok) return + const data = await res.json() + + const sessionInfo: SessionInfo = { + session_id: data.session_id, + filename: data.filename, + image_width: data.image_width, + image_height: data.image_height, + original_image_url: `${KLAUSUR_API}${data.original_image_url}`, + } + setSession(sessionInfo) + + // Reconstruct deskew result from session data + if (data.deskew_result) { + const dr: DeskewResult = { + ...data.deskew_result, + deskewed_image_url: `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${existingSessionId}/image/deskewed`, + binarized_image_url: `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${existingSessionId}/image/binarized`, + } + setDeskewResult(dr) + } + } catch (e) { + console.error('Failed to reload session:', e) + } + } + + loadSession() + }, [existingSessionId, session]) + const handleUpload = useCallback(async (file: File) => { setUploading(true) setError(null) diff --git a/admin-lehrer/components/ocr-pipeline/StepDewarp.tsx b/admin-lehrer/components/ocr-pipeline/StepDewarp.tsx index daccf50..54fc7de 100644 --- a/admin-lehrer/components/ocr-pipeline/StepDewarp.tsx +++ b/admin-lehrer/components/ocr-pipeline/StepDewarp.tsx @@ -123,9 +123,10 @@ export function StepDewarp({ sessionId, onNext }: StepDewarpProps) { originalUrl={deskewedUrl} deskewedUrl={dewarpedUrl} showGrid={showGrid} + showGridLeft={showGrid} showBinarized={false} binarizedUrl={null} - leftLabel="Begradigt (nach Deskew)" + leftLabel={`Begradigt (nach Deskew)${showGrid ? ' + Raster' : ''}`} rightLabel={`Entzerrt${showGrid ? ' + Raster (mm)' : ''}`} /> diff --git a/klausur-service/backend/cv_vocab_pipeline.py b/klausur-service/backend/cv_vocab_pipeline.py index 708a6b4..6b05018 100644 --- a/klausur-service/backend/cv_vocab_pipeline.py +++ b/klausur-service/backend/cv_vocab_pipeline.py @@ -627,12 +627,24 @@ def dewarp_image(img: np.ndarray) -> Tuple[np.ndarray, Dict[str, Any]]: f"curv={result_b['curvature_px']:.1f}px " f"({duration:.2f}s)") - # Pick method with higher confidence - if result_a["confidence"] >= result_b["confidence"]: + # Pick best method: prefer significant curvature over high confidence + # If one method found real curvature (>5px) and the other didn't (<3px), + # prefer the one with real curvature regardless of confidence. + a_has_curvature = result_a["curvature_px"] >= 5.0 and result_a["displacement_map"] is not None + b_has_curvature = result_b["curvature_px"] >= 5.0 and result_b["displacement_map"] is not None + + if a_has_curvature and not b_has_curvature: + best = result_a + elif b_has_curvature and not a_has_curvature: + best = result_b + elif result_a["confidence"] >= result_b["confidence"]: best = result_a else: best = result_b + logger.info(f"dewarp: selected {best['method']} " + f"(curv={best['curvature_px']:.1f}px, conf={best['confidence']:.2f})") + if best["displacement_map"] is None or best["curvature_px"] < 2.0: return img, no_correction diff --git a/klausur-service/backend/ocr_pipeline_api.py b/klausur-service/backend/ocr_pipeline_api.py index 4eee01a..54eebac 100644 --- a/klausur-service/backend/ocr_pipeline_api.py +++ b/klausur-service/backend/ocr_pipeline_api.py @@ -149,6 +149,32 @@ async def create_session(file: UploadFile = File(...)): } +@router.get("/sessions/{session_id}") +async def get_session_info(session_id: str): + """Get session info including deskew/dewarp results for step navigation.""" + session = _get_session(session_id) + img_bgr = session["original_bgr"] + + result = { + "session_id": session["id"], + "filename": session["filename"], + "image_width": img_bgr.shape[1], + "image_height": img_bgr.shape[0], + "original_image_url": f"/api/v1/ocr-pipeline/sessions/{session_id}/image/original", + "current_step": session.get("current_step", 1), + } + + # Include deskew result if available + if session.get("deskew_result"): + result["deskew_result"] = session["deskew_result"] + + # Include dewarp result if available + if session.get("dewarp_result"): + result["dewarp_result"] = session["dewarp_result"] + + return result + + @router.post("/sessions/{session_id}/deskew") async def auto_deskew(session_id: str): """Run both deskew methods and pick the best one."""