fix: Overlay-Zellen ohne _heal_row_gaps positionieren (skip_heal_gaps)
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 36s
CI / test-go-edu-search (push) Successful in 35s
CI / test-python-klausur (push) Failing after 2m12s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 21s

_heal_row_gaps verschiebt Zell-Positionen nach Entfernung von Artefakt-Zeilen,
was im Overlay zu sichtbarem Versatz fuehrt (z.B. 23px bei "badge").
Neuer skip_heal_gaps Parameter in build_cell_grid_v2 und words-Endpoint
behaelt die exakten Zeilen-Positionen bei.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-11 08:59:50 +01:00
parent e3ee1de790
commit 8a60f4bf30
4 changed files with 17 additions and 4 deletions

View File

@@ -218,7 +218,7 @@ export default function OcrOverlayPage() {
case 4: case 4:
return <StepRowDetection sessionId={sessionId} onNext={handleNext} /> return <StepRowDetection sessionId={sessionId} onNext={handleNext} />
case 5: case 5:
return <StepWordRecognition sessionId={sessionId} onNext={handleNext} goToStep={goToStep} /> return <StepWordRecognition sessionId={sessionId} onNext={handleNext} goToStep={goToStep} skipHealGaps />
case 6: case 6:
return <OverlayReconstruction sessionId={sessionId} onNext={handleNext} /> return <OverlayReconstruction sessionId={sessionId} onNext={handleNext} />
default: default:

View File

@@ -44,9 +44,11 @@ interface StepWordRecognitionProps {
sessionId: string | null sessionId: string | null
onNext: () => void onNext: () => void
goToStep: (step: number) => void goToStep: (step: number) => void
/** Skip _heal_row_gaps in cell grid (better overlay positioning) */
skipHealGaps?: boolean
} }
export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRecognitionProps) { export function StepWordRecognition({ sessionId, onNext, goToStep, skipHealGaps = false }: StepWordRecognitionProps) {
const [gridResult, setGridResult] = useState<GridResult | null>(null) const [gridResult, setGridResult] = useState<GridResult | null>(null)
const [detecting, setDetecting] = useState(false) const [detecting, setDetecting] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@@ -110,7 +112,7 @@ export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRec
let res: Response | null = null let res: Response | null = null
for (let attempt = 0; attempt < 2; attempt++) { for (let attempt = 0; attempt < 2; attempt++) {
res = await fetch( res = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?stream=true&engine=${eng}&pronunciation=${pronunciation}`, `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?stream=true&engine=${eng}&pronunciation=${pronunciation}${skipHealGaps ? '&skip_heal_gaps=true' : ''}`,
{ method: 'POST' }, { method: 'POST' },
) )
if (res.ok) break if (res.ok) break

View File

@@ -264,6 +264,7 @@ def build_cell_grid_v2(
lang: str = "eng+deu", lang: str = "eng+deu",
ocr_engine: str = "auto", ocr_engine: str = "auto",
img_bgr: Optional[np.ndarray] = None, img_bgr: Optional[np.ndarray] = None,
skip_heal_gaps: bool = False,
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
"""Hybrid Grid: full-page OCR for broad columns, cell-crop for narrow ones. """Hybrid Grid: full-page OCR for broad columns, cell-crop for narrow ones.
@@ -330,7 +331,12 @@ def build_cell_grid_v2(
else: else:
bottom_bound = content_rows[-1].y + content_rows[-1].height bottom_bound = content_rows[-1].y + content_rows[-1].height
_heal_row_gaps(content_rows, top_bound=top_bound, bottom_bound=bottom_bound) # skip_heal_gaps: When True, keep cell positions at their exact row geometry
# positions without expanding to fill gaps from removed rows. Useful for
# overlay rendering where pixel-precise positioning matters more than
# full-coverage OCR crops.
if not skip_heal_gaps:
_heal_row_gaps(content_rows, top_bound=top_bound, bottom_bound=bottom_bound)
relevant_cols.sort(key=lambda c: c.x) relevant_cols.sort(key=lambda c: c.x)

View File

@@ -1857,6 +1857,7 @@ async def detect_words(
engine: str = "auto", engine: str = "auto",
pronunciation: str = "british", pronunciation: str = "british",
stream: bool = False, stream: bool = False,
skip_heal_gaps: bool = False,
): ):
"""Build word grid from columns × rows, OCR each cell. """Build word grid from columns × rows, OCR each cell.
@@ -1864,6 +1865,8 @@ async def detect_words(
engine: 'auto' (default), 'tesseract', or 'rapid' engine: 'auto' (default), 'tesseract', or 'rapid'
pronunciation: 'british' (default) or 'american' — for IPA dictionary lookup pronunciation: 'british' (default) or 'american' — for IPA dictionary lookup
stream: false (default) for JSON response, true for SSE streaming stream: false (default) for JSON response, true for SSE streaming
skip_heal_gaps: false (default). When true, cells keep exact row geometry
positions without gap-healing expansion. Better for overlay rendering.
""" """
if session_id not in _cache: if session_id not in _cache:
logger.info("detect_words: session %s not in cache, loading from DB", session_id) logger.info("detect_words: session %s not in cache, loading from DB", session_id)
@@ -2007,6 +2010,7 @@ async def detect_words(
cells, columns_meta = build_cell_grid_v2( cells, columns_meta = build_cell_grid_v2(
ocr_img, col_regions, row_geoms, img_w, img_h, ocr_img, col_regions, row_geoms, img_w, img_h,
ocr_engine=engine, img_bgr=dewarped_bgr, ocr_engine=engine, img_bgr=dewarped_bgr,
skip_heal_gaps=skip_heal_gaps,
) )
duration = time.time() - t0 duration = time.time() - t0
@@ -2136,6 +2140,7 @@ async def _word_batch_stream_generator(
lambda: build_cell_grid_v2( lambda: build_cell_grid_v2(
ocr_img, col_regions, row_geoms, img_w, img_h, ocr_img, col_regions, row_geoms, img_w, img_h,
ocr_engine=engine, img_bgr=dewarped_bgr, ocr_engine=engine, img_bgr=dewarped_bgr,
skip_heal_gaps=skip_heal_gaps,
), ),
) )