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:
+110
-68
@@ -21,7 +21,8 @@ from watchdog.events import FileSystemEventHandler, FileCreatedEvent
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from exif_parser import ExifError, parse_image
|
||||
from ocr import read_bib
|
||||
from image_tagger import write_bib_tags
|
||||
from ocr import read_all_bibs
|
||||
from passage_log import log_passage
|
||||
from profile_db import get_or_create_athlete, init_db
|
||||
|
||||
@@ -70,56 +71,73 @@ async def process_image(path: Path) -> None:
|
||||
return
|
||||
|
||||
# --- OCR ---
|
||||
ocr = read_bib(path)
|
||||
logger.debug("OCR: digits=%s conf=%.2f", ocr.digits, ocr.confidence)
|
||||
bibs = read_all_bibs(path)
|
||||
logger.debug("OCR: %d startnumre funnet", len(bibs))
|
||||
|
||||
# --- Flytt til processed/ ---
|
||||
dest = _destination_path(path, meta.timestamp_utc)
|
||||
shutil.move(str(path), str(dest))
|
||||
logger.info("Flyttet til: %s", dest)
|
||||
|
||||
# --- Bestem konfidens og review-flagg ---
|
||||
confidence = ocr.confidence
|
||||
needs_review = False
|
||||
review_note = None
|
||||
id_method = "bib_ocr"
|
||||
|
||||
if ocr.digits is None or confidence < MIN_AUTO_CONFIDENCE:
|
||||
needs_review = True
|
||||
review_note = "number_unreadable" if ocr.digits is None else "low_confidence"
|
||||
id_method = "bib_ocr_uncertain"
|
||||
|
||||
# --- Koble mot profil-DB ---
|
||||
profile_id = None
|
||||
bib_number = ocr.digits
|
||||
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
await init_db(db)
|
||||
|
||||
if bib_number and not needs_review:
|
||||
profile_id = await get_or_create_athlete(db, bib_number)
|
||||
if not bibs:
|
||||
# Ingen bib funnet — legg til manuell gjennomgang
|
||||
await log_passage(
|
||||
db,
|
||||
profile_id=None,
|
||||
bib_number=None,
|
||||
station=meta.station or "unknown",
|
||||
timestamp_utc=meta.timestamp_utc,
|
||||
gps_lat=meta.gps_lat,
|
||||
gps_lon=meta.gps_lon,
|
||||
gps_alt=meta.gps_alt,
|
||||
confidence=0.0,
|
||||
proximity_score=0.0,
|
||||
id_method="bib_ocr_uncertain",
|
||||
source_image=str(dest),
|
||||
needs_review=True,
|
||||
review_note="number_unreadable",
|
||||
)
|
||||
logger.info("Passering logget (ingen bib): station=%s", meta.station)
|
||||
else:
|
||||
for ocr in bibs:
|
||||
confidence = ocr.confidence
|
||||
needs_review = confidence < MIN_AUTO_CONFIDENCE
|
||||
id_method = "bib_ocr" if not needs_review else "bib_ocr_uncertain"
|
||||
review_note = "low_confidence" if needs_review else None
|
||||
|
||||
await log_passage(
|
||||
db,
|
||||
profile_id=profile_id,
|
||||
bib_number=bib_number,
|
||||
station=meta.station or "unknown",
|
||||
timestamp_utc=meta.timestamp_utc,
|
||||
gps_lat=meta.gps_lat,
|
||||
gps_lon=meta.gps_lon,
|
||||
gps_alt=meta.gps_alt,
|
||||
confidence=confidence,
|
||||
proximity_score=ocr.proximity_score,
|
||||
id_method=id_method,
|
||||
source_image=str(dest),
|
||||
needs_review=needs_review,
|
||||
review_note=review_note,
|
||||
)
|
||||
logger.info(
|
||||
"Passering logget: bib=%s station=%s needs_review=%s",
|
||||
bib_number, meta.station, needs_review,
|
||||
)
|
||||
profile_id = None
|
||||
if ocr.digits and not needs_review:
|
||||
profile_id = await get_or_create_athlete(db, ocr.digits)
|
||||
|
||||
await log_passage(
|
||||
db,
|
||||
profile_id=profile_id,
|
||||
bib_number=ocr.digits,
|
||||
station=meta.station or "unknown",
|
||||
timestamp_utc=meta.timestamp_utc,
|
||||
gps_lat=meta.gps_lat,
|
||||
gps_lon=meta.gps_lon,
|
||||
gps_alt=meta.gps_alt,
|
||||
confidence=confidence,
|
||||
proximity_score=ocr.proximity_score,
|
||||
id_method=id_method,
|
||||
source_image=str(dest),
|
||||
needs_review=needs_review,
|
||||
review_note=review_note,
|
||||
)
|
||||
logger.info(
|
||||
"Passering logget: bib=%s station=%s needs_review=%s",
|
||||
ocr.digits, meta.station, needs_review,
|
||||
)
|
||||
|
||||
# Skriv alle funne bibs til EXIF-metadata i filen
|
||||
found_bibs = [ocr.digits for ocr in bibs if ocr.digits]
|
||||
if found_bibs:
|
||||
write_bib_tags(dest, found_bibs, station=meta.station or "unknown")
|
||||
|
||||
|
||||
async def process_image_with_override(
|
||||
@@ -148,39 +166,63 @@ async def process_image_with_override(
|
||||
except ExifError:
|
||||
timestamp = datetime.now(timezone.utc)
|
||||
|
||||
ocr = read_bib(path)
|
||||
bibs = read_all_bibs(path)
|
||||
dest = _destination_path(path, timestamp)
|
||||
shutil.move(str(path), str(dest))
|
||||
|
||||
confidence = ocr.confidence
|
||||
needs_review = ocr.digits is None or confidence < MIN_AUTO_CONFIDENCE
|
||||
id_method = "bib_ocr" if not needs_review else "bib_ocr_uncertain"
|
||||
review_note = None if not needs_review else (
|
||||
"number_unreadable" if ocr.digits is None else "low_confidence"
|
||||
)
|
||||
if not bibs:
|
||||
await log_passage(
|
||||
db,
|
||||
race_id=race_id,
|
||||
profile_id=None,
|
||||
bib_number=None,
|
||||
station=station_name,
|
||||
timestamp_utc=timestamp,
|
||||
gps_lat=gps_lat or 0.0,
|
||||
gps_lon=gps_lon or 0.0,
|
||||
gps_alt=gps_alt,
|
||||
confidence=0.0,
|
||||
proximity_score=0.0,
|
||||
id_method="bib_ocr_uncertain",
|
||||
source_image=str(dest),
|
||||
needs_review=True,
|
||||
review_note="number_unreadable",
|
||||
)
|
||||
logger.info("Passering logget (ingen bib): station=%s", station_name)
|
||||
else:
|
||||
for ocr in bibs:
|
||||
confidence = ocr.confidence
|
||||
needs_review = confidence < MIN_AUTO_CONFIDENCE
|
||||
id_method = "bib_ocr" if not needs_review else "bib_ocr_uncertain"
|
||||
review_note = "low_confidence" if needs_review else None
|
||||
|
||||
profile_id = None
|
||||
if ocr.digits and not needs_review:
|
||||
profile_id = await _get_or_create(db, ocr.digits)
|
||||
profile_id = None
|
||||
if ocr.digits and not needs_review:
|
||||
profile_id = await _get_or_create(db, ocr.digits)
|
||||
|
||||
await log_passage(
|
||||
db,
|
||||
race_id=race_id,
|
||||
profile_id=profile_id,
|
||||
bib_number=ocr.digits,
|
||||
station=station_name,
|
||||
timestamp_utc=timestamp,
|
||||
gps_lat=gps_lat or 0.0,
|
||||
gps_lon=gps_lon or 0.0,
|
||||
gps_alt=gps_alt,
|
||||
confidence=confidence,
|
||||
proximity_score=ocr.proximity_score,
|
||||
id_method=id_method,
|
||||
source_image=str(dest),
|
||||
needs_review=needs_review,
|
||||
review_note=review_note,
|
||||
)
|
||||
logger.info("Passering logget: bib=%s station=%s", ocr.digits, station_name)
|
||||
await log_passage(
|
||||
db,
|
||||
race_id=race_id,
|
||||
profile_id=profile_id,
|
||||
bib_number=ocr.digits,
|
||||
station=station_name,
|
||||
timestamp_utc=timestamp,
|
||||
gps_lat=gps_lat or 0.0,
|
||||
gps_lon=gps_lon or 0.0,
|
||||
gps_alt=gps_alt,
|
||||
confidence=confidence,
|
||||
proximity_score=ocr.proximity_score,
|
||||
id_method=id_method,
|
||||
source_image=str(dest),
|
||||
needs_review=needs_review,
|
||||
review_note=review_note,
|
||||
)
|
||||
logger.info("Passering logget: bib=%s station=%s", ocr.digits, station_name)
|
||||
|
||||
# Skriv alle funne bibs til EXIF-metadata i filen
|
||||
found_bibs = [ocr.digits for ocr in bibs if ocr.digits]
|
||||
if found_bibs:
|
||||
write_bib_tags(dest, found_bibs, station=station_name, race_id=race_id)
|
||||
|
||||
|
||||
async def process_existing() -> None:
|
||||
|
||||
Reference in New Issue
Block a user