Specifica flywms_navigate - navigazione geometrica e snapshot UDC

1. Obiettivo
Realizzare uno script separato, chiamato flywms_navigate.py, che usi solo la parte detector YOLO/Ultralytics per simulare la navigazione laterale di un drone davanti a una scaffalatura.

Lo script non deve ancora fare OCR. Deve invece osservare i gaylord rilevati nel video, seguirli per alcuni frame, capire quando uno di essi si trova in posizione utile al centro della visuale, e generare un evento testuale equivalente a:
STOP
SCATTA_FOTO
ASSOCIA_POSIZIONE gaylord_N
RIPARTI_DESTRA

In questa fase la posizione reale del drone viene simulata con una sequenza logica: gaylord 1, gaylord 2, gaylord 3, ecc.

Il punto fondamentale e' separare la guida dal riconoscimento codice. Il detector lavora sul flusso video live e decide quando congelare uno snapshot. L'OCR lavorera' dopo, su uno snapshot gia' scelto, insieme alla posizione del drone registrata nel momento corretto.

2. Problema che vogliamo risolvere
Nella pipeline attuale il video continua a scorrere mentre YOLO e OCR lavorano in asincrono. Questo va bene per misurare prestazioni, ma non va bene per la geometria della navigazione: quando arriva il risultato OCR, la posizione del drone potrebbe essere gia' cambiata.

Per la navigazione non dobbiamo aspettare l'OCR per decidere dove siamo. Dobbiamo invece creare un pacchetto dati congelato nel momento in cui la geometria dice che il gaylord e' nella posizione giusta.

NavigationSnapshot
  snapshot_id
  frame_id
  timestamp_video
  simulated_position = "gaylord N"
  drone_pose_simulata
  track_id
  gaylord_bbox
  gaylord_center
  gaylord_area
  frame_image
  quality_score opzionale

Questo oggetto potra' poi essere mandato all'OCR o al backend WMS senza perdere il legame con la posizione.

3. Concetto di tracking leggero
Il tracking leggero serve a capire che un bbox rilevato nel frame corrente e' probabilmente lo stesso oggetto visto nei frame precedenti.

Non serve ancora un tracker complesso come DeepSORT. Per questa prima versione basta associare i bbox di frame consecutivi usando regole geometriche semplici:

- vicinanza del centro bbox;
- sovrapposizione tra bbox, cioe' IoU;
- continuita' della dimensione/area;
- coerenza del movimento orizzontale.

Ogni gaylord seguito nel tempo diventa una traccia, cioe' una Track.

Track
  id
  bbox corrente
  center corrente
  area corrente
  bbox_history ultimi N frame
  center_history ultimi N frame
  area_history ultimi N frame
  first_seen_frame
  last_seen_frame
  hits = quante volte e' stato visto
  missed = quanti frame consecutivi non e' stato visto
  state = entering | candidate | centered | snapshotted | exiting | lost
  already_snapshotted = true/false

Se in un frame YOLO rileva tre gaylord, il tracker mantiene tre tracce diverse. Una puo' uscire verso sinistra, una puo' essere al centro, una puo' entrare da destra.

4. Associazione detection-track
Per ogni frame:

1. YOLO produce una lista di bbox di classe gaylord.
2. Per ogni detection si cerca la Track esistente piu' compatibile.
3. La compatibilita' puo' essere calcolata con uno score:

score = 0.7 * IoU(track.bbox, detection.bbox)
      + 0.3 * center_similarity(track.center, detection.center)

4. Se lo score supera una soglia, la detection aggiorna quella Track.
5. Se nessuna Track e' compatibile, nasce una nuova Track.
6. Le Track non aggiornate aumentano missed.
7. Se missed supera una soglia, la Track diventa lost e viene rimossa.

Per una prima implementazione possiamo anche partire con una regola piu' semplice: associare al track piu' vicino se la distanza tra centri e' minore di una certa percentuale della larghezza immagine. Poi raffiniamo con IoU.

5. Come decidere quando scattare
Lo snapshot deve avvenire quando una Track rappresenta un gaylord utile, cioe' non tagliato, abbastanza grande, stabile e vicino al centro della visuale.

Criteri iniziali proposti:

- classe = gaylord;
- bbox non tagliato dai bordi immagine, con margine minimo configurabile;
- area bbox sopra una soglia minima;
- centro X bbox dentro una fascia centrale configurabile;
- centro Y dentro una fascia utile verticale configurabile;
- Track vista per almeno N frame;
- area non sta piu' crescendo in modo forte, oppure e' abbastanza stabile;
- la Track non ha gia' generato snapshot.

La fascia centrale puo' essere visualizzata nella finestra debug con due linee verticali:

center_x = frame_width / 2
tolerance = frame_width * 0.10
zona_centrale = [center_x - tolerance, center_x + tolerance]

Quando il centro del gaylord entra in questa zona e i criteri sono soddisfatti, lo script genera:

[NAV] track=4 centered frame=1234 pos=gaylord 7
[CMD] STOP
[CMD] SCATTA_FOTO snapshot_0007.jpg
[CMD] ASSOCIA_POSIZIONE gaylord 7
[CMD] RIPARTI_DESTRA delay=1.0s

6. Gestione del migliore snapshot
Invece di scattare una sola immagine esattamente quando il centro entra nella fascia, possiamo raccogliere un piccolo gruppo di candidate snapshot per la stessa Track.

Esempio:

- quando la Track entra nella fascia centrale, si apre una finestra temporale di selezione;
- per i successivi K frame compatibili si calcola un punteggio;
- alla fine si salva solo il migliore.

Punteggio iniziale:

center_score = 1 - abs(bbox_center_x - frame_center_x) / center_tolerance
size_score = normalized_bbox_area
cut_score = 1 se bbox non tagliato, 0 se tagliato
sharpness_score = opzionale, non obbligatorio per la geometria

snapshot_score = 0.50 * center_score
               + 0.30 * size_score
               + 0.20 * cut_score

Per ora non usiamo OCR e non usiamo il filtro blur come vincolo primario, perche' il detector riesce a localizzare anche immagini leggermente sfocate. La nitidezza potra' entrare solo come criterio secondario per scegliere la foto migliore da passare all'OCR.

7. Pseudocodice generale
inizializza YOLO Ultralytics
apri video o camera
inizializza tracker vuoto
inizializza snapshot_counter = 0
inizializza simulated_position_counter = 0

per ogni frame:
    frame_id += 1
    detections = yolo.detect(frame)
    gaylord_detections = filtra detections dove classe == "gaylord"

    tracker.update(gaylord_detections, frame_id)

    per ogni track attiva:
        calcola center_x, center_y, area
        calcola se bbox e' tagliato
        calcola trend area: crescente, stabile, decrescente
        calcola distanza dal centro frame

        se track e' eleggibile per snapshot:
            aggiungi frame corrente ai candidati della track
            aggiorna miglior candidato in base a snapshot_score

        se finestra candidati della track e' completa:
            snapshot_counter += 1
            simulated_position_counter += 1
            salva miglior frame candidato su disco
            crea NavigationSnapshot
            stampa comandi:
                STOP
                SCATTA_FOTO
                ASSOCIA_POSIZIONE gaylord simulated_position_counter
                RIPARTI_DESTRA
            marca track.already_snapshotted = true

    disegna debug:
        bbox gaylord
        track id
        centro bbox
        fascia centrale
        stato track
        ultimo comando generato

    mostra finestra flywms navigate

8. Stati della Track
entering
La Track e' appena nata o sta entrando nella visuale. Area spesso crescente, centro lontano dal centro frame.

candidate
La Track e' stabile, visibile da alcuni frame, non e' tagliata e si sta avvicinando alla zona centrale.

centered
Il centro bbox e' dentro la fascia centrale. La Track puo' produrre candidati snapshot.

snapshotted
La Track ha gia' prodotto uno snapshot. Non deve produrne altri, anche se resta visibile.

exiting
Il gaylord si sta allontanando dal centro o sta uscendo dalla visuale.

lost
La Track non viene piu' rilevata da troppi frame e viene rimossa.

9. Parametri iniziali suggeriti
--center-tolerance-ratio 0.10
--usable-y-min-ratio 0.15
--usable-y-max-ratio 0.85
--min-track-hits 3
--max-track-missed 8
--min-gaylord-area-ratio 0.04
--edge-margin-ratio 0.02
--snapshot-window-frames 5
--snapshot-output-dir navigate_snapshots

10. Output atteso della prima versione
La prima versione di flywms_navigate.py deve produrre:

- una finestra video con bbox gaylord, track id e zona centrale;
- log testuali dei comandi simulati;
- immagini snapshot salvate su disco;
- un file log CSV o JSONL con frame_id, track_id, bbox, posizione simulata e nome file snapshot;
- nessuna dipendenza dall'OCR.

11. Nota sulla direzione di movimento
Per ora assumiamo movimento da sinistra verso destra del drone rispetto alla scaffalatura, come nel ragionamento iniziale. Nel video, pero', gli oggetti possono apparire con moto apparente non perfettamente regolare per via della ripresa manuale. Per questo la prima versione deve basarsi soprattutto sul centro del bbox e sulla continuita' della Track, non su un modello cinematico troppo rigido.

12. Passo successivo dopo questa specifica
Implementare flywms_navigate.py usando il detector Ultralytics gia' integrato in flywms.py, ma senza OCR. Quando la scelta degli snapshot sara' visivamente corretta, collegheremo ogni NavigationSnapshot alla pipeline OCR e poi alla logica WMS.