""" Startnummer-deteksjon og OCR. Bruker EasyOCR for å lese sifre fra bib-nummerlapp. Returnerer en OcrResult med tall, konfidens og partial-flagg. """ import re from dataclasses import dataclass, field from pathlib import Path from typing import Optional import cv2 import numpy as np # EasyOCR lastes lazy for å unngå lang oppstartstid _reader = None def _get_reader(): global _reader if _reader is None: import easyocr _reader = easyocr.Reader(["en"], gpu=False, verbose=False) return _reader @dataclass class OcrResult: digits: Optional[str] # Gjenkjente sifre, f.eks. "42", None hvis ingen confidence: float # 0.0–1.0 partial: bool # True hvis nummeret trolig er delvis skjult raw_texts: list[str] = field(default_factory=list) # Alle OCR-treff for debug def _preprocess(image_path: Path) -> np.ndarray: """Forbered bilde for OCR: gråskala, kontrast, skarphet.""" img = cv2.imread(str(image_path)) if img is None: raise ValueError(f"Kunne ikke lese bilde: {image_path}") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # CLAHE for bedre kontrast i varierende lys clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) enhanced = clahe.apply(gray) # Skaler opp hvis bildet er lite (OCR er bedre på større tekst) h, w = enhanced.shape if max(h, w) < 800: scale = 800 / max(h, w) enhanced = cv2.resize( enhanced, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_CUBIC, ) return enhanced def _extract_bib_number(texts: list[tuple]) -> tuple[Optional[str], float, bool]: """ Finn beste siffersekvens blant OCR-treff. Returnerer (sifre, konfidens, partial). """ candidates = [] for (_, text, conf) in texts: # Behold kun sifre digits = re.sub(r"[^0-9]", "", text) if digits: candidates.append((digits, float(conf))) if not candidates: return None, 0.0, False # Velg kandidat med høyest konfidens best_digits, best_conf = max(candidates, key=lambda x: x[1]) # Heuristikk: 1–2 sifre kan tyde på delvis synlig nummer partial = len(best_digits) < 2 return best_digits, best_conf, partial def read_bib(image_path: Path) -> OcrResult: """ Les startnummer fra bildet. Returnerer OcrResult. Aldri exception — fallback til konfidens 0 ved feil. """ try: processed = _preprocess(image_path) reader = _get_reader() results = reader.readtext(processed, detail=1, paragraph=False) raw_texts = [text for (_, text, _) in results] digits, confidence, partial = _extract_bib_number(results) return OcrResult( digits=digits, confidence=confidence, partial=partial, raw_texts=raw_texts, ) except Exception as e: return OcrResult( digits=None, confidence=0.0, partial=False, raw_texts=[f"ERROR: {e}"], )