Acquisizione: calibrazione in tab, salvata con nome, archivio unico, caricamento con controllo risoluzione #91

Closed
opened 2026-10-24 14:02:35 +00:00 by Adriano · 1 comment
Owner

ANALISI (passo 1, claude-opus-5-5)

PROBLEMA OGGI

  • scripts/calibrazione.py Archivio: una calibrazione per sorgente (/-.json); niente nome, niente piu' calibrazioni, una calibrazione da file si salva sopra quella della sorgente.
  • scripts/calibrazione_pagina.py: calibrazione come sezione della pagina Acquisizione; «Salva» rifa' l'acquisizione (save=true) invece di salvare il calcolo visto.

DUE PIANI A FILE DISGIUNTI (contratto sotto, identico in testa ai due prompt)
A servizio: scripts/calibrazione.py (Archivio), scripts/camera_service.py (rotte), test in scripts/tests.
TEST A: migrazione (vecchio JSON+npz -> libreria + attive, idempotente, nome doppio -> "(2)"); salva_nome/NomeGiaUsato/sovrascrivi; nome non valido; elimina_nome toglie da attive; leggi/leggi_mappa risolvono l'attiva; rotte 1-8 con sorgente cartella o finta (risoluzione uguale -> 200, diversa -> 409 col messaggio, nome sconosciuto 404, calcolo_id scaduto 404); GET /camera/calibration invariato per il backend; test esistenti aggiornati solo dove usavano save=true.
B pagina: scripts/calibrazione_pagina.py; test in scripts/tests/test_pagina_tab.py (contenuto della pagina servita: tab, id, rotte nuove usate, niente alert/confirm/prompt).
NON TOCCARE: src/ (backend e frontend), vendor/, packaging/, rotte e codice #87/#89/#90 non elencati.
Comando test: uv run --python 3.13 --extra vision-worker --extra dev --with-editable vendor/visionsuite/packages/vs-camera pytest scripts/tests -q
Prova d'insieme (passo 3, Opus): TMF da sorgente su daitarn, Itala 601020 2064x1544: calcolo da file (BMP Menoncin in C:\ProgramData\Tielogic\TieMeasureFlow\camera), salvataggio con nome, caricamento su camera:601020 (risoluzione diversa -> errore, anteprima viva), migrazione delle due calibrazioni esistenti.

CONTRATTO #91 (identico per i due piani; repo TieMeasureFlow, base ramo lavoro/daitarn-tmf-dxfcal 828471d)
Richiesta Adriano 24/10/2026: acquisizione e calibrazione nella stessa pagina, due tab. Calibrazione sempre sulla sorgente selezionata nella tab Acquisizione o su un'immagine da file. Salvataggio con nome; piu' calibrazioni; archivio UNICO (non per sorgente). Caricando una calibrazione su una sorgente la risoluzione (larghezza x altezza in pixel) deve combaciare col frame della sorgente, altrimenti errore «non si puo' caricare» e l'acquisizione continua.

ARCHIVIO (scripts/calibrazione.py, classe Archivio, radice = cartella attuale <config_dir>, es. C:\ProgramData\Tielogic\TieMeasureFlow\acquisitore)

  • Libreria: /calibrazioni/-<sha1(nome.casefold())[:8]>.json (+ .npz se densa), stesso schema di ripulitura di Archivio._file.
    JSON = campi del calcolo attuali (px_per_mm, mm_per_px, rms_px, ..., image_width, image_height, metodo, square_mm, ...) + "nome", "origine" ("camera:601020" | "file:"), "calibrated_at".
  • Attive: /attive.json = {"": ""}; scrittura atomica (tmp + os.replace) come oggi.
  • Migrazione automatica e idempotente (al costruttore o al primo accesso): ogni /*.json di primo livello con campo "source" (formato vecchio, una per sorgente) diventa voce di libreria con nome = valore di "source" (se gia' preso: " (2)", "(3)"...), si sposta anche il .npz, e attive[source] = nome. La cartella settaggi/ non si tocca.
  • Nome: strip, 1-60 caratteri, niente caratteri di controllo; unico senza distinzione maiuscole/minuscole.
  • API: elenco() -> list[dict metadati]; leggi_nome(nome) -> dict|None; salva_nome(nome, dati, mappa=None, sovrascrivi=False) (NomeGiaUsato se esiste e non sovrascrivi; NomeNonValido); elimina_nome(nome) (toglie anche da attive.json); attiva(sorgente, nome); scollega(sorgente); attiva_di(sorgente) -> nome|None.
    leggi(sorgente) e leggi_mappa(sorgente) restano con la stessa firma e ora risolvono la calibrazione ATTIVA della sorgente (leggi aggiunge "nome" e "source"=sorgente). Cache delle mappe come oggi (per percorso + mtime).

ROTTE (scripts/camera_service.py)

  1. POST /camera/calibrate (multipart): source (obbligatorio solo senza image), square_mm, cols/rows opzionali, image opzionale. Il campo save sparisce (ignorato se arriva). Non salva mai.
    -> 200 {...risultato, "calibrated_at", "origine", "calcolo_id": "", "overlay_png"}. Il servizio tiene in memoria gli ultimi 8 calcoli (dati + mappa) per calcolo_id.
  2. GET /camera/calibrazioni[?source=S] -> 200 {"calibrazioni": [{nome, origine, image_width, image_height, px_per_mm, mm_per_px, metodo, square_mm, calibrated_at, attiva_su: [sorgenti]}], "attiva": nome|null} (attiva solo se c'e' source; elenco per calibrated_at decrescente)
  3. POST /camera/calibrazioni json {calcolo_id, nome, sovrascrivi=false} -> 201 metadati della voce.
    404 «calcolo non piu' disponibile: ricalcolare»; 400 nome non valido; 409 «esiste gia' una calibrazione «X»» se esiste e sovrascrivi=false.
  4. DELETE /camera/calibrazioni?nome=X -> 204; 404 se non esiste. Toglie X da tutte le sorgenti su cui era attiva.
  5. PUT /camera/calibrazione-attiva json {source, nome}: legge un frame dalla sorgente (stesso lock e stessi errori di /camera/grab: 404/503/504) e confronta (larghezza, altezza) con image_width/image_height.
    Diverse -> 409 «la calibrazione «X» e' per immagini W×H, la sorgente da' w×h: non si puo' caricare». Senza image_width/height -> 409 «la calibrazione «X» non ha la risoluzione registrata: non si puo' caricare». Nome sconosciuto -> 404. Ok -> 200 = stessa forma di GET /camera/calibration.
  6. GET /camera/calibration?source=S: contratto invariato (lo legge il backend, src/backend/services/vision_service.py:244) = calibrazione attiva + "nome"; 404 «sorgente non calibrata» se nessuna attiva.
  7. DELETE /camera/calibration?source=S: ora SCOLLEGA la calibrazione attiva (204), la libreria resta.
  8. /camera/grab e /camera/anteprima: raddrizzano con la mappa della calibrazione attiva (gia' cosi' passando da leggi_mappa).
    Lingua dei messaggi: italiano. Il ciclo anteprima non dipende da queste rotte: un errore di caricamento non lo ferma.

PAGINA (scripts/calibrazione_pagina.py, costante PAGINA, offline)

  • Due tab in alto: «Acquisizione» | «Calibrazione» (pulsanti role=tab, aria-selected; tab scelta ricordata con memoria()).
  • Colonna sinistra (Anteprima con indicatori) visibile in ENTRAMBE le tab; la colonna destra cambia: Acquisizione = Sorgente + Parametri camera; Calibrazione = pannello calibrazione.
  • Pannello calibrazione: riga «Sorgente: (si cambia nella tab Acquisizione)»; origine immagine: radio «Sorgente selezionata» / «Immagine da file» (+ Scegli immagine...); lato scacco; «Calcola». Risultato: tabella, avvisi, overlay. Poi campo «Nome» + «Salva»; su 409 il pulsante diventa «Sovrascrivi «X»? Clic ancora» (secondo clic entro 4 s -> sovrascrivi=true), come l'attuale conferma di Elimina.
  • «Calibrazioni salvate»: tabella nome, risoluzione W×H, px/mm, metodo, data, origine; segno «attiva» su quella attiva della sorgente selezionata; «risoluzione diversa» se la risoluzione nota del frame (X-Larghezza/X-Altezza dell'ultima anteprima) e' diversa; per riga «Carica» (PUT /camera/calibrazione-attiva) ed «Elimina» (doppio clic). «Scollega» sulla riga attiva (DELETE /camera/calibration?source=).
  • Errore di caricamento: messaggio rosso nel pannello, anteprima che continua. Dopo un caricamento riuscito: stato e indicatore «raddrizzato» aggiornati.
  • Calcolo da sorgente: l'anteprima si sospende e riprende come oggi. Niente alert/confirm/prompt.
ANALISI (passo 1, claude-opus-5-5) PROBLEMA OGGI - scripts/calibrazione.py Archivio: una calibrazione per sorgente (<radice>/<sorgente>-<sha8>.json); niente nome, niente piu' calibrazioni, una calibrazione da file si salva sopra quella della sorgente. - scripts/calibrazione_pagina.py: calibrazione come sezione della pagina Acquisizione; «Salva» rifa' l'acquisizione (save=true) invece di salvare il calcolo visto. DUE PIANI A FILE DISGIUNTI (contratto sotto, identico in testa ai due prompt) A servizio: scripts/calibrazione.py (Archivio), scripts/camera_service.py (rotte), test in scripts/tests. TEST A: migrazione (vecchio JSON+npz -> libreria + attive, idempotente, nome doppio -> "(2)"); salva_nome/NomeGiaUsato/sovrascrivi; nome non valido; elimina_nome toglie da attive; leggi/leggi_mappa risolvono l'attiva; rotte 1-8 con sorgente cartella o finta (risoluzione uguale -> 200, diversa -> 409 col messaggio, nome sconosciuto 404, calcolo_id scaduto 404); GET /camera/calibration invariato per il backend; test esistenti aggiornati solo dove usavano save=true. B pagina: scripts/calibrazione_pagina.py; test in scripts/tests/test_pagina_tab.py (contenuto della pagina servita: tab, id, rotte nuove usate, niente alert/confirm/prompt). NON TOCCARE: src/ (backend e frontend), vendor/, packaging/, rotte e codice #87/#89/#90 non elencati. Comando test: uv run --python 3.13 --extra vision-worker --extra dev --with-editable vendor/visionsuite/packages/vs-camera pytest scripts/tests -q Prova d'insieme (passo 3, Opus): TMF da sorgente su daitarn, Itala 601020 2064x1544: calcolo da file (BMP Menoncin in C:\ProgramData\Tielogic\TieMeasureFlow\camera), salvataggio con nome, caricamento su camera:601020 (risoluzione diversa -> errore, anteprima viva), migrazione delle due calibrazioni esistenti. CONTRATTO #91 (identico per i due piani; repo TieMeasureFlow, base ramo lavoro/daitarn-tmf-dxfcal 828471d) Richiesta Adriano 24/10/2026: acquisizione e calibrazione nella stessa pagina, due tab. Calibrazione sempre sulla sorgente selezionata nella tab Acquisizione o su un'immagine da file. Salvataggio con nome; piu' calibrazioni; archivio UNICO (non per sorgente). Caricando una calibrazione su una sorgente la risoluzione (larghezza x altezza in pixel) deve combaciare col frame della sorgente, altrimenti errore «non si puo' caricare» e l'acquisizione continua. ARCHIVIO (scripts/calibrazione.py, classe Archivio, radice = cartella attuale <config_dir>, es. C:\ProgramData\Tielogic\TieMeasureFlow\acquisitore) - Libreria: <radice>/calibrazioni/<nome ripulito>-<sha1(nome.casefold())[:8]>.json (+ .npz se densa), stesso schema di ripulitura di Archivio._file. JSON = campi del calcolo attuali (px_per_mm, mm_per_px, rms_px, ..., image_width, image_height, metodo, square_mm, ...) + "nome", "origine" ("camera:601020" | "file:<nome file>"), "calibrated_at". - Attive: <radice>/attive.json = {"<sorgente>": "<nome>"}; scrittura atomica (tmp + os.replace) come oggi. - Migrazione automatica e idempotente (al costruttore o al primo accesso): ogni <radice>/*.json di primo livello con campo "source" (formato vecchio, una per sorgente) diventa voce di libreria con nome = valore di "source" (se gia' preso: "<source> (2)", "(3)"...), si sposta anche il .npz, e attive[source] = nome. La cartella settaggi/ non si tocca. - Nome: strip, 1-60 caratteri, niente caratteri di controllo; unico senza distinzione maiuscole/minuscole. - API: elenco() -> list[dict metadati]; leggi_nome(nome) -> dict|None; salva_nome(nome, dati, mappa=None, sovrascrivi=False) (NomeGiaUsato se esiste e non sovrascrivi; NomeNonValido); elimina_nome(nome) (toglie anche da attive.json); attiva(sorgente, nome); scollega(sorgente); attiva_di(sorgente) -> nome|None. leggi(sorgente) e leggi_mappa(sorgente) restano con la stessa firma e ora risolvono la calibrazione ATTIVA della sorgente (leggi aggiunge "nome" e "source"=sorgente). Cache delle mappe come oggi (per percorso + mtime). ROTTE (scripts/camera_service.py) 1. POST /camera/calibrate (multipart): source (obbligatorio solo senza image), square_mm, cols/rows opzionali, image opzionale. Il campo save sparisce (ignorato se arriva). Non salva mai. -> 200 {...risultato, "calibrated_at", "origine", "calcolo_id": "<hex>", "overlay_png"}. Il servizio tiene in memoria gli ultimi 8 calcoli (dati + mappa) per calcolo_id. 2. GET /camera/calibrazioni[?source=S] -> 200 {"calibrazioni": [{nome, origine, image_width, image_height, px_per_mm, mm_per_px, metodo, square_mm, calibrated_at, attiva_su: [sorgenti]}], "attiva": nome|null} (attiva solo se c'e' source; elenco per calibrated_at decrescente) 3. POST /camera/calibrazioni json {calcolo_id, nome, sovrascrivi=false} -> 201 metadati della voce. 404 «calcolo non piu' disponibile: ricalcolare»; 400 nome non valido; 409 «esiste gia' una calibrazione «X»» se esiste e sovrascrivi=false. 4. DELETE /camera/calibrazioni?nome=X -> 204; 404 se non esiste. Toglie X da tutte le sorgenti su cui era attiva. 5. PUT /camera/calibrazione-attiva json {source, nome}: legge un frame dalla sorgente (stesso lock e stessi errori di /camera/grab: 404/503/504) e confronta (larghezza, altezza) con image_width/image_height. Diverse -> 409 «la calibrazione «X» e' per immagini W×H, la sorgente da' w×h: non si puo' caricare». Senza image_width/height -> 409 «la calibrazione «X» non ha la risoluzione registrata: non si puo' caricare». Nome sconosciuto -> 404. Ok -> 200 = stessa forma di GET /camera/calibration. 6. GET /camera/calibration?source=S: contratto invariato (lo legge il backend, src/backend/services/vision_service.py:244) = calibrazione attiva + "nome"; 404 «sorgente non calibrata» se nessuna attiva. 7. DELETE /camera/calibration?source=S: ora SCOLLEGA la calibrazione attiva (204), la libreria resta. 8. /camera/grab e /camera/anteprima: raddrizzano con la mappa della calibrazione attiva (gia' cosi' passando da leggi_mappa). Lingua dei messaggi: italiano. Il ciclo anteprima non dipende da queste rotte: un errore di caricamento non lo ferma. PAGINA (scripts/calibrazione_pagina.py, costante PAGINA, offline) - Due tab in alto: «Acquisizione» | «Calibrazione» (pulsanti role=tab, aria-selected; tab scelta ricordata con memoria()). - Colonna sinistra (Anteprima con indicatori) visibile in ENTRAMBE le tab; la colonna destra cambia: Acquisizione = Sorgente + Parametri camera; Calibrazione = pannello calibrazione. - Pannello calibrazione: riga «Sorgente: <sorgente selezionata> (si cambia nella tab Acquisizione)»; origine immagine: radio «Sorgente selezionata» / «Immagine da file» (+ Scegli immagine...); lato scacco; «Calcola». Risultato: tabella, avvisi, overlay. Poi campo «Nome» + «Salva»; su 409 il pulsante diventa «Sovrascrivi «X»? Clic ancora» (secondo clic entro 4 s -> sovrascrivi=true), come l'attuale conferma di Elimina. - «Calibrazioni salvate»: tabella nome, risoluzione W×H, px/mm, metodo, data, origine; segno «attiva» su quella attiva della sorgente selezionata; «risoluzione diversa» se la risoluzione nota del frame (X-Larghezza/X-Altezza dell'ultima anteprima) e' diversa; per riga «Carica» (PUT /camera/calibrazione-attiva) ed «Elimina» (doppio clic). «Scollega» sulla riga attiva (DELETE /camera/calibration?source=). - Errore di caricamento: messaggio rosso nel pannello, anteprima che continua. Dopo un caricamento riuscito: stato e indicatore «raddrizzato» aggiornati. - Calcolo da sorgente: l'anteprima si sospende e riprende come oggi. Niente alert/confirm/prompt.
Adriano added the sviluppo label 2026-10-24 14:02:35 +00:00
Author
Owner

Soluzione

ESITO: fatto
COSA: pagina Acquisizione a due tab (Acquisizione | Calibrazione, anteprima in entrambe); calibrazione da sorgente selezionata o da file; salvataggio con nome in archivio unico (calibrazioni/ + attive.json), sovrascrittura con secondo clic; caricamento su sorgente solo con risoluzione uguale (409 altrimenti, anteprima viva); migrazione automatica delle calibrazioni per sorgente.
FILE: scripts/calibrazione.py (Archivio), scripts/camera_service.py (rotte calibrate, calibrazioni GET/POST/DELETE, calibrazione-attiva PUT; GET /camera/calibration invariato per il backend), scripts/calibrazione_pagina.py, scripts/tests/test_calibrazioni_archivio.py, test_calibrazioni_rotte.py, test_pagina_tab.py
COMMIT: e7d5489 a5e8472 (servizio), d6cee49 8eb80e2 (pagina), merge 3c5c5f9 b2f82fe su lavoro/daitarn-tmf-dxfcal
PROVA: scripts/tests → 124 passed; TMF da sorgente su daitarn: migrazione reale camera:601654 e camera:folder → attive invariate; Playwright Edge 1400 e 400 px: calcolo da file calibration_135_1.bmp (71,111 px/mm), salva, 409 → sovrascrivi, Carica su Itala 601020 2064×1544 → 409 «non si può caricare», 57 anteprime in 6 s durante l'errore; PUT attiva su camera:folder (2464×2056) → 200 e anteprima X-Raddrizzato si, poi ripristinata.
MANCA: PC fiera con la prossima build daltanius (la migrazione girerà al primo avvio: backup consigliato di acquisitore/).

**Soluzione** ESITO: fatto COSA: pagina Acquisizione a due tab (Acquisizione | Calibrazione, anteprima in entrambe); calibrazione da sorgente selezionata o da file; salvataggio con nome in archivio unico (calibrazioni/ + attive.json), sovrascrittura con secondo clic; caricamento su sorgente solo con risoluzione uguale (409 altrimenti, anteprima viva); migrazione automatica delle calibrazioni per sorgente. FILE: scripts/calibrazione.py (Archivio), scripts/camera_service.py (rotte calibrate, calibrazioni GET/POST/DELETE, calibrazione-attiva PUT; GET /camera/calibration invariato per il backend), scripts/calibrazione_pagina.py, scripts/tests/test_calibrazioni_archivio.py, test_calibrazioni_rotte.py, test_pagina_tab.py COMMIT: e7d5489 a5e8472 (servizio), d6cee49 8eb80e2 (pagina), merge 3c5c5f9 b2f82fe su lavoro/daitarn-tmf-dxfcal PROVA: scripts/tests → 124 passed; TMF da sorgente su daitarn: migrazione reale camera:601654 e camera:folder → attive invariate; Playwright Edge 1400 e 400 px: calcolo da file calibration_135_1.bmp (71,111 px/mm), salva, 409 → sovrascrivi, Carica su Itala 601020 2064×1544 → 409 «non si può caricare», 57 anteprime in 6 s durante l'errore; PUT attiva su camera:folder (2464×2056) → 200 e anteprima X-Raddrizzato si, poi ripristinata. MANCA: PC fiera con la prossima build daltanius (la migrazione girerà al primo avvio: backup consigliato di acquisitore/).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/TieMeasureFlow#91