Støtte for flere bibs per bilde, EXIF-metadata og zoom i gjennomgang
Build & Deploy / build-and-deploy (push) Successful in 46s
Build & Deploy / build-and-deploy (push) Successful in 46s
- OCR: ny read_all_bibs() returnerer alle unike startnumre (≥2 sifre) per bilde
- Ingest: oppretter én passering per bib (ikke bare beste), ingen bib → needs_review
- image_tagger.py: skriv/les bib-metadata som JSON i EXIF UserComment (piexif)
- Ingest + resolve: tagger bildefilen med bibs automatisk og ved manuell bekreftelse
- API: POST /api/passages/{id}/reanalyze — re-kjør OCR på eksisterende bilde
- API: POST /api/passages/{id}/resolve oppdaterer nå EXIF med bekreftet bib
- races: ny kolonne bib_filter_enabled (med automatisk migrering) + per-løp toggle
- ReviewPage: Re-analyser-knapp og klikk-for-zoom med scroll/drag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+58
-40
@@ -64,33 +64,11 @@ def _bbox_area(bbox) -> float:
|
||||
return (max(xs) - min(xs)) * (max(ys) - min(ys))
|
||||
|
||||
|
||||
def _extract_bib_number(texts: list[tuple]) -> tuple[Optional[str], float, bool, float]:
|
||||
def read_all_bibs(image_path: Path) -> list["OcrResult"]:
|
||||
"""
|
||||
Finn beste siffersekvens blant OCR-treff.
|
||||
Returnerer (sifre, konfidens, partial, proximity_score).
|
||||
proximity_score = areal av bounding box i piksler² (større = nærmere kamera).
|
||||
"""
|
||||
candidates = []
|
||||
for (bbox, text, conf) in texts:
|
||||
digits = re.sub(r"[^0-9]", "", text)
|
||||
if digits:
|
||||
candidates.append((digits, float(conf), _bbox_area(bbox)))
|
||||
|
||||
if not candidates:
|
||||
return None, 0.0, False, 0.0
|
||||
|
||||
# Velg kandidat med høyest konfidens
|
||||
best_digits, best_conf, best_area = max(candidates, key=lambda x: x[1])
|
||||
|
||||
partial = len(best_digits) < 2
|
||||
|
||||
return best_digits, best_conf, partial, best_area
|
||||
|
||||
|
||||
def read_bib(image_path: Path) -> OcrResult:
|
||||
"""
|
||||
Les startnummer fra bildet.
|
||||
Returnerer OcrResult. Aldri exception — fallback til konfidens 0 ved feil.
|
||||
Les alle startnumre fra bildet.
|
||||
Returnerer én OcrResult per unikt startnummer funnet (minst 2 sifre).
|
||||
Tom liste hvis ingen funnet eller ved feil.
|
||||
"""
|
||||
try:
|
||||
processed = _preprocess(image_path)
|
||||
@@ -98,19 +76,59 @@ def read_bib(image_path: Path) -> OcrResult:
|
||||
results = reader.readtext(processed, detail=1, paragraph=False)
|
||||
|
||||
raw_texts = [text for (_, text, _) in results]
|
||||
digits, confidence, partial, proximity_score = _extract_bib_number(results)
|
||||
|
||||
return OcrResult(
|
||||
digits=digits,
|
||||
confidence=confidence,
|
||||
partial=partial,
|
||||
proximity_score=proximity_score,
|
||||
raw_texts=raw_texts,
|
||||
)
|
||||
# Best konfidens + areal per unikt siffersekvens (minst 2 sifre)
|
||||
best: dict[str, tuple[float, float]] = {} # digits -> (conf, area)
|
||||
for (bbox, text, conf) in results:
|
||||
digits = re.sub(r"[^0-9]", "", text)
|
||||
if len(digits) < 2:
|
||||
continue
|
||||
area = _bbox_area(bbox)
|
||||
if digits not in best or float(conf) > best[digits][0]:
|
||||
best[digits] = (float(conf), area)
|
||||
|
||||
return [
|
||||
OcrResult(
|
||||
digits=digits,
|
||||
confidence=conf,
|
||||
partial=False,
|
||||
proximity_score=area,
|
||||
raw_texts=raw_texts,
|
||||
)
|
||||
for digits, (conf, area) in best.items()
|
||||
]
|
||||
except Exception as e:
|
||||
return OcrResult(
|
||||
digits=None,
|
||||
confidence=0.0,
|
||||
partial=False,
|
||||
raw_texts=[f"ERROR: {e}"],
|
||||
)
|
||||
return [OcrResult(digits=None, confidence=0.0, partial=False, raw_texts=[f"ERROR: {e}"])]
|
||||
|
||||
|
||||
def read_bib(image_path: Path) -> OcrResult:
|
||||
"""
|
||||
Les beste startnummer fra bildet (bakoverkompatibel).
|
||||
Returnerer OcrResult. Aldri exception — fallback til konfidens 0 ved feil.
|
||||
"""
|
||||
bibs = read_all_bibs(image_path)
|
||||
if not bibs or bibs[0].digits is None:
|
||||
# Sjekk også enkle sifre (partial) for bakoverkompatibilitet
|
||||
try:
|
||||
processed = _preprocess(image_path)
|
||||
reader = _get_reader()
|
||||
results = reader.readtext(processed, detail=1, paragraph=False)
|
||||
raw_texts = [text for (_, text, _) in results]
|
||||
candidates = []
|
||||
for (bbox, text, conf) in results:
|
||||
digits = re.sub(r"[^0-9]", "", text)
|
||||
if digits:
|
||||
candidates.append((digits, float(conf), _bbox_area(bbox)))
|
||||
if candidates:
|
||||
best_digits, best_conf, best_area = max(candidates, key=lambda x: x[1])
|
||||
return OcrResult(
|
||||
digits=best_digits,
|
||||
confidence=best_conf,
|
||||
partial=len(best_digits) < 2,
|
||||
proximity_score=best_area,
|
||||
raw_texts=raw_texts,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return OcrResult(digits=None, confidence=0.0, partial=False)
|
||||
return max(bibs, key=lambda r: r.confidence)
|
||||
|
||||
Reference in New Issue
Block a user