Files
timing/spec.md
T
steinhelge 330ba7a93d Initial commit: MVP tidtakingssystem
- Backend: FastAPI, EXIF-parser, EasyOCR, SQLite
- Frontend: React admin (startliste, passeringer, gjennomgang, resultater)
- Docker: docker-compose med depot/processed/data-volumer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 15:01:33 +01:00

271 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bildedrevet tidtakingssystem — teknisk spesifikasjon
## Oversikt
Et tidtakingssystem for løp uten elektroniske brikker. Utøvere identifiseres ved hjelp av bilder tatt ved start, passeringspunkter og mål. Bildene inneholder nøyaktig tidsstempel og GPS-posisjon i EXIF-metadata, noe som gjør det mulig å rekonstruere hver utøvers rute og passeringstidspunkter langs løypa.
Identifikasjon skjer ved kombinasjon av to metoder:
1. OCR på startnummer
2. Biometrisk persongjenkjenning (ansikt/kroppsform)
De to metodene forsterker hverandre — et delvis skjult startnummer kan disambigueres av persongjenkjenning, og omvendt.
---
## Kamerastasjoner
Kamera settes opp ved:
- **Start** — alle utøvere fotograferes ved avgang
- **Passeringspunkter** — vilkårlig antall stasjoner langs løypa (N ≥ 0)
- **Mål** — alle utøvere fotograferes ved ankomst
Hvert kamera må:
- Produsere JPEG/PNG med korrekt EXIF-data (se nedenfor)
- Ha burst-modus eller høy framerate for å håndtere bevegelsesuskarphet
- Synkroniseres mot NTP (nøyaktig klokkeslett er kritisk)
### EXIF-krav per bilde
Alle bilder MÅ ha følgende EXIF-felter satt:
| EXIF-felt | Innhold | Merknad |
|---|---|---|
| `DateTimeOriginal` | ISO 8601 UTC | Eks: `2024-06-15T10:23:45.123Z` |
| `SubSecTimeOriginal` | Millisekunder | For sub-sekund presisjon |
| `GPSLatitude` + `GPSLatitudeRef` | Desimalgrader | Stasjonens posisjon |
| `GPSLongitude` + `GPSLongitudeRef` | Desimalgrader | Stasjonens posisjon |
| `GPSAltitude` | Meter over havet | Valgfritt men anbefalt |
| `CameraLabel` / `ImageDescription` | Stasjonsnavn | Eks: `"start"`, `"cp1"`, `"finish"` |
EXIF-posisjonen representerer stasjonens faste posisjon (ikke utøverens bevegelse). For mobile stasjoner settes GPS per bilde fra kamerakontrollerens GPS-mottaker.
---
## Dataflyt og pipeline
```
Bilde (JPEG + EXIF)
[1] Inntak og preprocessing
- Valider EXIF (tid + GPS påkrevd, avvis ellers)
- Kø-basert mottak fra alle stasjoner
- Generer utsnitt av utøver (bounding box)
- Kvalitetsfiltrering: skarphet, lysforhold, utøver synlig
├──────────────────────────────────┐
▼ ▼
[2a] Startnummer-OCR [2b] Persongjenkjenning
- Detekter nummerlapp - Generer embedding fra
- Les sifre ansikt og/eller kropp
- Returner: sifre[], konfidens, - Match mot profil-DB
andel synlig (0.01.0) - Returner: profil_id,
konfidens, match-type
│ │
└──────────────┬───────────────────┘
[3] Konfidensbasert fusjonsmotor
- Kombiner OCR-resultat og personmatch
- Beslutningstre (se nedenfor)
- Output: identifikasjon + konfidensscore
├── Høy konfidens ──► [4a] Logg passering direkte
└── Lav konfidens ──► [4b] Flagg for manuell gjennomgang
[5] Resultatdatabase
- Utøver-ID, stasjon, tidsstempel (fra EXIF), GPS-pos
- Beregn split-tider og løyperute
```
---
## Fusjonslogikk
Prioriteringsrekkefølge ved konflikt:
```
1. Startnummer tydelig (konfidens > 0.90)
→ Bruk startnummer, ignorer person-match hvis konflikt
2. Startnummer delvis synlig (konfidens 0.500.90) + person-match
→ Kombiner: filtrer kandidater fra startnummer, disambiguer med person
→ Logg hvis entydig, flagg hvis flere kandidater gjenstår
3. Startnummer uleselig (konfidens < 0.50) + person-match (konfidens > 0.70)
→ Bruk person-match alene
→ Logg med merknad "number_unreadable"
4. Ingen av delene gir tilstrekkelig konfidens
→ Flagg for manuell gjennomgang med bilde og metadata
```
Minste akseptable konfidens for automatisk logging: konfigurbart, anbefalt startverdi `0.75`.
---
## Profildatabase (utøver-DB)
Utøverprofiler bygges opp suksessivt:
- **Bootstrapping:** Startkameraet genererer en profil for hver unik person som passerer. Profil-ID tildeles automatisk.
- **Kobling til startliste:** Valgfritt — kan mates inn med navn + startnummer + registreringsbilde. Kobles til profil ved første gjenkjenning.
- **Uten startliste:** Systemet fungerer fullt ut; utøvere får anonyme ID-er. Startliste kan legges til i etterkant for å sette navn.
### Skjema (forenklet)
```json
{
"profile_id": "uuid",
"bib_number": "42",
"name": "Ola Nordmann",
"embeddings": [ /* array av vektorer fra registreringsbilder */ ],
"registration_images": [ "path/to/img1.jpg" ],
"created_at": "2024-06-15T09:00:00Z"
}
```
---
## Passeringslogg
En passering lagres når en utøver identifiseres ved en stasjon:
```json
{
"passage_id": "uuid",
"profile_id": "uuid",
"bib_number": "42",
"station": "cp1",
"timestamp_utc": "2024-06-15T10:23:45.123Z",
"gps_lat": 61.1234,
"gps_lon": 10.5678,
"gps_alt": 910,
"confidence": 0.92,
"id_method": "bib_ocr+person",
"source_image": "path/to/image.jpg",
"needs_review": false
}
```
Fra passeringsloggen beregnes:
- Split-tider mellom stasjoner
- Totaltid start→mål
- Løyperute (GPS-spor av stasjonspasseringer)
- Estimert posisjon i løypa på ethvert tidspunkt (interpolasjon)
---
## Komponenter som skal implementeres
### Backend (Python anbefalt)
| Modul | Ansvar |
|---|---|
| `ingest.py` | Ta imot bilder, valider EXIF, kø til pipeline |
| `exif_parser.py` | Parse og normaliser EXIF-metadata |
| `ocr.py` | Startnummer-deteksjon og OCR |
| `recognition.py` | Persongjenkjenning, embedding, match mot profil-DB |
| `fusion.py` | Kombiner OCR + person, beslutningslogikk |
| `profile_db.py` | CRUD for utøverprofiler og embeddings |
| `passage_log.py` | Skriv og query passeringslogg |
| `results.py` | Beregn split-tider, totalresultat, løyperute |
| `review_queue.py` | Håndter manuelle gjennomganger |
### Foreslåtte biblioteker
- **OCR:** `easyocr` eller `paddleocr` (god på korte numre i varierende lysforhold)
- **Persongjenkjenning:** `deepface` eller `insightface` (lokalt, ingen sky-avhengighet)
- **EXIF-parsing:** `piexif` eller `exifread`
- **Database:** SQLite for enkel lokal drift, PostgreSQL for produksjon
- **Bildeprosessering:** `opencv-python`, `Pillow`
- **Kø:** `redis` + `rq` for asynkron pipeline, eller enkel filkø for MVP
### Frontend (valgfritt, React anbefalt)
- Live-visning av passeringer
- Leaflet-kart med GPS-posisjoner til stasjoner og utøveres rute
- Resultatliste med split-tider
- Manuell gjennomgangskø med bildevisning
---
## Kanttilfeller og håndtering
| Situasjon | Håndtering |
|---|---|
| Utøvere overlapper i bildet | Generer separate bounding boxes per utøver |
| Startnummeret er delvis bak arm/klesplagg | OCR returnerer synlige sifre + `partial: true` |
| Samme utøver fanges i burst-sekvens | Deduplisering: behold beste bilde per utøver per stasjon (±2 sek vindu) |
| Refleks/solblending gjør bildet ubrukelig | Kvalitetsfilter forkaster bildet; neste i burst brukes |
| EXIF mangler tid eller GPS | Bildet avvises med feillogg; manuell stasjon-tid kan angis som fallback |
| Ukjent person, uleselig nummer | Flagges med bilde til manuell gjennomgang |
| To utøvere med like startnummer (feil) | Flagges som konflikt; begge profiler merkes |
---
## Personvern
- Biometrisk gjenkjenning krever samtykke (GDPR artikkel 9 — særlig kategori)
- Anbefalt løsning: opt-in ved påmelding, registreringsbilde lastes opp i forkant
- Embeddings lagres, ikke råbilder, etter at passering er bekreftet (konfigurerbart)
- Råbilder bør slettes etter løpet med mindre deltaker har samtykket til lagring
- Systemet bør kunne kjøres i "bib-only"-modus uten persongjenkjenning for arrangement som ikke innhenter biometrisk samtykke
---
## Infrastruktur
### Container-drift
Hele systemet kjøres i Docker (docker-compose):
- **backend** — Python/FastAPI, pipeline og API
- **frontend** — React admin-grensesnitt (Nginx)
- **db** — SQLite via volum (PostgreSQL i produksjon)
Volumes:
- `./depot` — inntakskatalog for nye bilder (kamera-drop)
- `./processed` — bearbeidede bilder med unike filnavn
- `./data` — SQLite-database
### Bildehåndtering
1. Kamera/bruker legger bilder i `depot/`
2. `ingest.py` oppdager nye filer (inotify / polling)
3. EXIF valideres; ved godkjenning:
- Metadata lagres i DB
- Filen flyttes til `processed/<år>/<måned>/<uuid>_<originalfilnavn>`
- Depot-filen slettes
4. Ved ugyldig EXIF: filen flyttes til `depot/rejected/` med feillogg
---
## Admin-webgrensesnitt (React)
Tilgjengelig via nettleser, kommuniserer med backend via REST API.
| Skjerm | Funksjonalitet |
|---|---|
| Startliste | Last opp CSV, vis/rediger utøverliste |
| Live passeringer | Strøm av nylige passeringer fra alle stasjoner |
| Manuell gjennomgang | Vis bilder med lav konfidens, bekreft/korriger ID |
| Resultater | Split-tider og totalresultat per utøver |
| Stasjoner | Oversikt over kamerastasjoner og siste aktivitet |
---
## MVP-avgrensning (fase 1)
For å komme raskt i gang, begrens til:
1. EXIF-parsing og validering
2. Startnummer-OCR (ingen persongjenkjenning ennå)
3. Manuell startliste som CSV (bib_number → navn)
4. Passeringslogg til SQLite
5. REST API (FastAPI) for frontend
6. React admin-grensesnitt: startliste-import og manuell gjennomgang
7. Docker-oppsett med depot/processed-volumer
Persongjenkjenning og live Leaflet-kart legges til i fase 2.