Ricetta DXF compare creata in TMF da camera, con calibrazione a scacchiera per stazione (TieVision-Core) #83

Closed
opened 2026-10-23 20:34:05 +00:00 by Adriano · 4 comments
Owner

ESITO: richiesta (analisi passo 1, Opus 5.5 claude-opus-5-5)
RIF: gitea:Adriano/TieMeasureFlow#72 (fiera A&T Vicenza 27-29/10/2026)
COSA: in TMF si crea una ricetta DXF compare da camera, senza TieVision Studio, con calibrazione a scacchiera per stazione. Tutta la visione passa da TieVision-Core (vendor/visionsuite). Pochi parametri (Adriano 23/10/2026: «semplice da gestire e con pochi parametri», «creiamo una ricetta direttamente da tmf», «non tievision studio»).

BASE: origin/V3.0.0 ae2353f, vendor/visionsuite 2e73691.

FATTI (VERIFICATO leggendo il codice):

  • CompareDxf (vs-tools/tools/dxf.py) non allinea da solo: posa = Pose(anchor_offset_row_px, anchor_offset_col_px, offset_angle_deg) + align dal localizzatore. Scala da mm_per_px o da context["calibration"] (ScaleCalibration o dict px_per_mm).
  • LocateBlob (tools/locate_ucs.py): pezzo in controluce, soglia Otsu di default, poses = delta da reference_pose. Solo uint8.
  • Session carica meta.calibration (JSON {"px_per_mm"}) dalla cartella ricetta; la calibration passata al runner la sostituisce.
  • Calibrazione: visionsuite.calibration.find_chessboard(img,(cols,rows),square_mm) + calibrate_planar(...) → CalibrationModel.pixel_size(x,y) (mm/px), rms_reprojection.
  • TMF: src/vision_worker/main.py (/run, /validate), backend _call_worker in src/backend/services/vision_service.py; execute_task non passa calibrazione (riga ~422, «nessuna fonte di calibrazione»); VisionResult.calibration_snapshot esiste e resta vuoto. Station ha camera_url/camera_source (migrazione 020). Chiavi uscita = "<tool.id>.".

DISEGNO:

  1. Calibrazione per stazione. Parametri: angoli interni colonne × righe, lato del quadrato in mm. Immagine dalla camera della stazione o caricata. Esito: px/mm, RMS di riproiezione, variazione di scala centro↔angoli (%), immagine con gli angoli trovati. «Salva» la scrive su stations.calibration (JSON).
  2. Ricetta DXF da TMF (task dxf_compare). Parametri: stazione (camera + calibrazione), file DXF, banda ± mm (default 0,1), soglia % punti in banda (default 95), polarità (default pezzo scuro su fondo chiaro = controluce). Immagine di riferimento dalla camera. Il worker costruisce la ricetta fissa LocateBlob «pezzo» → CompareDxf «dxf»: posa del disegno sull'immagine di riferimento trovata in automatico (ICP vs-dxf, prove a 0/90/180/270°, vince la % in banda migliore), reference_pose del blob = posa trovata sull'immagine di riferimento. «Prova» mostra overlay e numeri; «Salva» scrive la ricetta sul task (come l'upload) e, se il task non ha quote di visione, crea la quota «Punti in tolleranza».
  3. Esecuzione: se la stazione del run ha calibrazione, si passa {"px_per_mm"} al worker e si scrive calibration_snapshot.

CONTRATTO HTTP (identico per i tre piani):
W1 worker POST /calibrate/chessboard multipart image, cols:int, rows:int, square_mm:float → 200 {px_per_mm, mm_per_px, rms_px, n_corners, cols, rows, square_mm, image_width, image_height, scale_spread_pct, overlay_png (base64 PNG)}; 422 {detail} se non trovata.
W2 worker POST /recipes/dxf-compare multipart image, dxf (file), px_per_mm:float, band_mm:float=0.1, min_points_pct:float=95, polarity:"dark"|"light"="dark" → 200 {graph (v2), files {"disegno.dxf": b64, "calibrazione.json": b64}, outputs {"dxf.points_in_pct":…, "dxf.distance_avg":…, "dxf.distance_min":…, "dxf.distance_max":…}, overlay [...], outcome}; 422 {detail} se pezzo non trovato o DXF illeggibile.
B1 backend POST /api/stations/{id}/calibration multipart image, cols, rows, square_mm, save:bool=false → W1 + {"saved": bool}; con save: stations.calibration = W1 senza overlay_png + calibrated_at (ISO) + calibrated_by.
B2 backend DELETE /api/stations/{id}/calibration → 204.
B3 backend POST /api/vision/tasks/{task_id}/recipe/dxf-compare (require_maker) multipart image, dxf, station_id:int|null, px_per_mm:float|null (se null: station.calibration.px_per_mm; nessuno dei due → 422 «stazione non calibrata»), band_mm, min_points_pct, polarity, save:bool=false → {result: W2 senza files, saved, recipe_dir|null, outputs (lista di /validate)|null}.
B4 StationResponse espone calibration (dict|null).

PIANI (file disgiunti, rami da ae2353f):
A worker: src/vision/costruttori.py (nuovo), src/vision_worker/main.py, test in src/vision/tests e src/vision_worker/tests. Ramo lavoro/daitarn-tmf-dxfcal-worker.
B backend: migrazione 021_station_calibration, models/orm/station.py, models/api/station.py, routers/stations.py, routers/vision.py, services/vision_service.py, test in src/backend/tests. Ramo lavoro/daitarn-tmf-dxfcal-backend.
C frontend: templates/admin/stations.html, templates/maker/task_editor.html, static/js nuovo, blueprints (maker/admin), services/api_client.py, translations, test flask. Ramo lavoro/daitarn-tmf-dxfcal-frontend.

TEST:

  • A: scacchiera sintetica (quadrati 40 px, square_mm 2) → px_per_mm 20 ±0,5%, rms < 0,5; immagine senza scacchiera → 422. DXF: vendor/visionsuite/packages/vs-dxf/tests/fixtures/sample.dxf disegnato scuro su bianco a 4 px/mm; ricetta costruita sul riferimento a 0°, poi eseguita con run_graph su scena ruotata 5° e traslata → points_in_pct > 95.
  • B: worker finto (come test_vision_station_worker.py): salva/cancella calibrazione, ricetta salvata su task (vision_json, recipe_dir, quota creata), execute passa calibration e scrive calibration_snapshot; test migrazioni.
  • C: rotte proxy con api_client finto, come i test flask esistenti.
  • Suite: uv run pytest; uv run --python 3.13 --extra vision --extra vision-worker --extra dev pytest src/vision/tests src/vision_worker/tests.

NON TOCCARE: vendor/visionsuite (submodule), upload_recipe e il suo comportamento, flusso misura esistente e il suo proxy camera, packaging Windows.
LIMITI NOTI: solo controluce (LocateBlob); pezzi simmetrici a 180° ma non nel disegno possono essere girati; DXF da ripulire a mano (cartiglio, assi).

ESITO: richiesta (analisi passo 1, Opus 5.5 claude-opus-5-5) RIF: gitea:Adriano/TieMeasureFlow#72 (fiera A&T Vicenza 27-29/10/2026) COSA: in TMF si crea una ricetta DXF compare da camera, senza TieVision Studio, con calibrazione a scacchiera per stazione. Tutta la visione passa da TieVision-Core (vendor/visionsuite). Pochi parametri (Adriano 23/10/2026: «semplice da gestire e con pochi parametri», «creiamo una ricetta direttamente da tmf», «non tievision studio»). BASE: origin/V3.0.0 ae2353f, vendor/visionsuite 2e73691. FATTI (VERIFICATO leggendo il codice): - CompareDxf (vs-tools/tools/dxf.py) non allinea da solo: posa = Pose(anchor_offset_row_px, anchor_offset_col_px, offset_angle_deg) + `align` dal localizzatore. Scala da `mm_per_px` o da context["calibration"] (ScaleCalibration o dict px_per_mm). - LocateBlob (tools/locate_ucs.py): pezzo in controluce, soglia Otsu di default, poses = delta da reference_pose. Solo uint8. - Session carica meta.calibration (JSON {"px_per_mm"}) dalla cartella ricetta; la `calibration` passata al runner la sostituisce. - Calibrazione: visionsuite.calibration.find_chessboard(img,(cols,rows),square_mm) + calibrate_planar(...) → CalibrationModel.pixel_size(x,y) (mm/px), rms_reprojection. - TMF: src/vision_worker/main.py (/run, /validate), backend _call_worker in src/backend/services/vision_service.py; execute_task non passa calibrazione (riga ~422, «nessuna fonte di calibrazione»); VisionResult.calibration_snapshot esiste e resta vuoto. Station ha camera_url/camera_source (migrazione 020). Chiavi uscita = "<tool.id>.<prop>". DISEGNO: 1. Calibrazione per stazione. Parametri: angoli interni colonne × righe, lato del quadrato in mm. Immagine dalla camera della stazione o caricata. Esito: px/mm, RMS di riproiezione, variazione di scala centro↔angoli (%), immagine con gli angoli trovati. «Salva» la scrive su stations.calibration (JSON). 2. Ricetta DXF da TMF (task dxf_compare). Parametri: stazione (camera + calibrazione), file DXF, banda ± mm (default 0,1), soglia % punti in banda (default 95), polarità (default pezzo scuro su fondo chiaro = controluce). Immagine di riferimento dalla camera. Il worker costruisce la ricetta fissa LocateBlob «pezzo» → CompareDxf «dxf»: posa del disegno sull'immagine di riferimento trovata in automatico (ICP vs-dxf, prove a 0/90/180/270°, vince la % in banda migliore), reference_pose del blob = posa trovata sull'immagine di riferimento. «Prova» mostra overlay e numeri; «Salva» scrive la ricetta sul task (come l'upload) e, se il task non ha quote di visione, crea la quota «Punti in tolleranza». 3. Esecuzione: se la stazione del run ha calibrazione, si passa {"px_per_mm"} al worker e si scrive calibration_snapshot. CONTRATTO HTTP (identico per i tre piani): W1 worker POST /calibrate/chessboard multipart image, cols:int, rows:int, square_mm:float → 200 {px_per_mm, mm_per_px, rms_px, n_corners, cols, rows, square_mm, image_width, image_height, scale_spread_pct, overlay_png (base64 PNG)}; 422 {detail} se non trovata. W2 worker POST /recipes/dxf-compare multipart image, dxf (file), px_per_mm:float, band_mm:float=0.1, min_points_pct:float=95, polarity:"dark"|"light"="dark" → 200 {graph (v2), files {"disegno.dxf": b64, "calibrazione.json": b64}, outputs {"dxf.points_in_pct":…, "dxf.distance_avg":…, "dxf.distance_min":…, "dxf.distance_max":…}, overlay [...], outcome}; 422 {detail} se pezzo non trovato o DXF illeggibile. B1 backend POST /api/stations/{id}/calibration multipart image, cols, rows, square_mm, save:bool=false → W1 + {"saved": bool}; con save: stations.calibration = W1 senza overlay_png + calibrated_at (ISO) + calibrated_by. B2 backend DELETE /api/stations/{id}/calibration → 204. B3 backend POST /api/vision/tasks/{task_id}/recipe/dxf-compare (require_maker) multipart image, dxf, station_id:int|null, px_per_mm:float|null (se null: station.calibration.px_per_mm; nessuno dei due → 422 «stazione non calibrata»), band_mm, min_points_pct, polarity, save:bool=false → {result: W2 senza files, saved, recipe_dir|null, outputs (lista di /validate)|null}. B4 StationResponse espone calibration (dict|null). PIANI (file disgiunti, rami da ae2353f): A worker: src/vision/costruttori.py (nuovo), src/vision_worker/main.py, test in src/vision/tests e src/vision_worker/tests. Ramo lavoro/daitarn-tmf-dxfcal-worker. B backend: migrazione 021_station_calibration, models/orm/station.py, models/api/station.py, routers/stations.py, routers/vision.py, services/vision_service.py, test in src/backend/tests. Ramo lavoro/daitarn-tmf-dxfcal-backend. C frontend: templates/admin/stations.html, templates/maker/task_editor.html, static/js nuovo, blueprints (maker/admin), services/api_client.py, translations, test flask. Ramo lavoro/daitarn-tmf-dxfcal-frontend. TEST: - A: scacchiera sintetica (quadrati 40 px, square_mm 2) → px_per_mm 20 ±0,5%, rms < 0,5; immagine senza scacchiera → 422. DXF: vendor/visionsuite/packages/vs-dxf/tests/fixtures/sample.dxf disegnato scuro su bianco a 4 px/mm; ricetta costruita sul riferimento a 0°, poi eseguita con run_graph su scena ruotata 5° e traslata → points_in_pct > 95. - B: worker finto (come test_vision_station_worker.py): salva/cancella calibrazione, ricetta salvata su task (vision_json, recipe_dir, quota creata), execute passa calibration e scrive calibration_snapshot; test migrazioni. - C: rotte proxy con api_client finto, come i test flask esistenti. - Suite: uv run pytest; uv run --python 3.13 --extra vision --extra vision-worker --extra dev pytest src/vision/tests src/vision_worker/tests. NON TOCCARE: vendor/visionsuite (submodule), upload_recipe e il suo comportamento, flusso misura esistente e il suo proxy camera, packaging Windows. LIMITI NOTI: solo controluce (LocateBlob); pezzi simmetrici a 180° ma non nel disegno possono essere girati; DXF da ripulire a mano (cartiglio, assi).
Adriano added the sviluppoassegnata:daitarn labels 2026-10-23 20:34:43 +00:00
Author
Owner

🔵 daitarn — preso in carico 2026-10-23T20:34:41Z
ramo: lavoro/daitarn · capacità: build-windows, gpu

🔵 daitarn — **preso in carico** 2026-10-23T20:34:41Z ramo: `lavoro/daitarn` · capacità: build-windows, gpu
Author
Owner

ESITO: richiesta (cambio contratto)
RIF: gitea:Adriano/TieMeasureFlow#83
COSA: Adriano 23/10/2026: «l'acquisizione potrebbe essere gestita con un modulo esterno configurabile e su tmf ci sarebbe solo link all'api dell'acquisitore», «acquisitore che ha modulo di calibrazione».
NUOVO CONTRATTO:

  • Acquisitore = scripts/camera_service.py (TieVision vs-camera). Nuove rotte: C1 GET /camera/calibration?source= → {source, px_per_mm, mm_per_px, rms_px, n_corners, cols, rows, square_mm, image_width, image_height, scale_spread_pct, calibrated_at} | 404; C2 POST /camera/calibrate (source, cols, rows, square_mm, image opzionale = grab, save) → C1 + overlay_png + saved; C3 DELETE /camera/calibration?source=; C4 GET /calibrazione pagina offline. Persistenza JSON per sorgente in --config-dir.
  • TOLTI: W1 nel vision worker, migrazione 021, stations.calibration, B1, B2, B4.
  • Backend: get_station_calibration(station) legge C1 da camera_url/camera_source; B3 e execute_task la usano (snapshot in VisionResult.calibration_snapshot).
  • Frontend: pagina stazioni mostra stato via proxy C1 e link «Calibra» a {camera_url}/calibrazione; niente pannello di calibrazione in TMF.
  • W2 e B3 invariati.
    PROSSIMO: i tre agenti (worker/acquisitore, backend, frontend) hanno ricevuto il cambio; revisione Opus a fine piani.
ESITO: richiesta (cambio contratto) RIF: gitea:Adriano/TieMeasureFlow#83 COSA: Adriano 23/10/2026: «l'acquisizione potrebbe essere gestita con un modulo esterno configurabile e su tmf ci sarebbe solo link all'api dell'acquisitore», «acquisitore che ha modulo di calibrazione». NUOVO CONTRATTO: - Acquisitore = scripts/camera_service.py (TieVision vs-camera). Nuove rotte: C1 GET /camera/calibration?source= → {source, px_per_mm, mm_per_px, rms_px, n_corners, cols, rows, square_mm, image_width, image_height, scale_spread_pct, calibrated_at} | 404; C2 POST /camera/calibrate (source, cols, rows, square_mm, image opzionale = grab, save) → C1 + overlay_png + saved; C3 DELETE /camera/calibration?source=; C4 GET /calibrazione pagina offline. Persistenza JSON per sorgente in --config-dir. - TOLTI: W1 nel vision worker, migrazione 021, stations.calibration, B1, B2, B4. - Backend: get_station_calibration(station) legge C1 da camera_url/camera_source; B3 e execute_task la usano (snapshot in VisionResult.calibration_snapshot). - Frontend: pagina stazioni mostra stato via proxy C1 e link «Calibra» a {camera_url}/calibrazione; niente pannello di calibrazione in TMF. - W2 e B3 invariati. PROSSIMO: i tre agenti (worker/acquisitore, backend, frontend) hanno ricevuto il cambio; revisione Opus a fine piani.
Adriano removed the assegnata:daitarn label 2026-10-24 02:44:25 +00:00
Author
Owner

🤖 goldrake — liberata nessun segno di daitarn da 6.2 h — 2026-10-24T02:44:25Z

🤖 goldrake — **liberata** nessun segno di daitarn da 6.2 h — 2026-10-24T02:44:25Z
Author
Owner

Soluzione

ESITO: fatto (chiusa su richiesta di Adriano del 06/11/2026)
RIF: gitea:Adriano/TieMeasureFlow#83; contratto finale = commento del 23/10 20:37 (cambio contratto), non la prima analisi
COSA: ricetta DXF compare creata in TMF da camera, con la calibrazione presa dall'acquisitore esterno della stazione.
CONTRATTO FINALE IN CODICE (VERIFICATO su origin/V3.0.0 8c361c1):

  • acquisitore scripts/camera_service.py con il modulo di calibrazione (C1 GET /camera/calibration, C2 POST /camera/calibrate, C3 DELETE, C4 pagina scripts/calibrazione_pagina.py); W1, migrazione 021 stations.calibration, B1, B2, B4 tolti come deciso il 23/10;
  • worker: POST /recipes/dxf-compare (src/vision_worker/main.py:242), costruttore src/vision/costruttori.py (LocateBlob → CompareDxf); test src/vision_worker/tests/test_dxf_compare.py;
  • backend: get_station_calibration (src/backend/services/vision_service.py:317) legge C1 da camera_url/camera_source della stazione; B3 ed execute_task la usano;
  • in V3.0.0 dal ramo lavoro/daitarn-tmf-dxfcal (5897a9e), portato su V3.0.0 con #126.
    PROVA: in fiera, 28/10, ricetta FIERA-DXF-COPIA-2 con /api/vision/check → 100 % dei punti in banda sull'immagine di riferimento (scheda AI-OS TieMeasureFlow, voce 28/10).
    LIMITI NOTI (invariati): solo controluce (LocateBlob), simmetrie a 180°, DXF da ripulire a mano.
**Soluzione** ESITO: fatto (chiusa su richiesta di Adriano del 06/11/2026) RIF: gitea:Adriano/TieMeasureFlow#83; contratto finale = commento del 23/10 20:37 (cambio contratto), non la prima analisi COSA: ricetta DXF compare creata in TMF da camera, con la calibrazione presa dall'acquisitore esterno della stazione. CONTRATTO FINALE IN CODICE (VERIFICATO su origin/V3.0.0 8c361c1): - acquisitore scripts/camera_service.py con il modulo di calibrazione (C1 GET /camera/calibration, C2 POST /camera/calibrate, C3 DELETE, C4 pagina scripts/calibrazione_pagina.py); W1, migrazione 021 stations.calibration, B1, B2, B4 tolti come deciso il 23/10; - worker: POST /recipes/dxf-compare (src/vision_worker/main.py:242), costruttore src/vision/costruttori.py (LocateBlob → CompareDxf); test src/vision_worker/tests/test_dxf_compare.py; - backend: get_station_calibration (src/backend/services/vision_service.py:317) legge C1 da camera_url/camera_source della stazione; B3 ed execute_task la usano; - in V3.0.0 dal ramo lavoro/daitarn-tmf-dxfcal (5897a9e), portato su V3.0.0 con #126. PROVA: in fiera, 28/10, ricetta FIERA-DXF-COPIA-2 con /api/vision/check → 100 % dei punti in banda sull'immagine di riferimento (scheda AI-OS TieMeasureFlow, voce 28/10). LIMITI NOTI (invariati): solo controluce (LocateBlob), simmetrie a 180°, DXF da ripulire a mano.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/TieMeasureFlow#83