acquisitore: rotte per calibrazioni con nome, caricamento con controllo risoluzione (Refs #91)
/camera/calibrate non salva piu' (calcolo_id in memoria, ultimi 8); nuove rotte GET/POST/DELETE /camera/calibrazioni e PUT /camera/calibrazione-attiva (409 se la risoluzione non combacia col frame della sorgente); DELETE /camera/calibration ora scollega. Test: rotte nuove e test esistenti aggiornati (save=true -> salva con nome). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jbf7qKuxsnAVjxthRy7dnB
This commit is contained in:
@@ -82,12 +82,24 @@ def client(cartella, tmp_path):
|
||||
|
||||
|
||||
CAMPI = {"cols": "9", "rows": "6", "square_mm": "2"}
|
||||
CHIAVI_C1 = {"source", "px_per_mm", "mm_per_px", "rms_px", "n_corners", "cols", "rows",
|
||||
CHIAVI_C1 = {"px_per_mm", "mm_per_px", "rms_px", "n_corners", "cols", "rows",
|
||||
"square_mm", "image_width", "image_height", "scale_spread_pct", "calibrated_at"}
|
||||
# #84: campi in piu' per metodo planare (C2 con cols/rows) e per la densa
|
||||
CHIAVI_PLANARE = CHIAVI_C1 | {"metodo", "distorsione"}
|
||||
CHIAVI_PLANARE = CHIAVI_C1 | {"metodo", "distorsione", "origine"} # #91: + origine ("camera:X" | "file:Y")
|
||||
CHIAVI_DENSA = CHIAVI_PLANARE | {"residuo_rms_px", "residuo_max_px", "copertura_pct"}
|
||||
SORGENTE = "camera:folder"
|
||||
# #91: GET /camera/calibration = voce di libreria + "nome" + "source" (la sorgente che la usa)
|
||||
CHIAVI_ATTIVA = {"source", "nome"}
|
||||
|
||||
|
||||
def salva_con_nome(client, calcolo: dict, nome: str, **extra):
|
||||
"""#91: POST /camera/calibrazioni sul calcolo_id di una risposta di /camera/calibrate."""
|
||||
return client.post("/camera/calibrazioni", json={"calcolo_id": calcolo["calcolo_id"], "nome": nome, **extra})
|
||||
|
||||
|
||||
def carica_su(client, sorgente: str, nome: str):
|
||||
"""#91: PUT /camera/calibrazione-attiva."""
|
||||
return client.put("/camera/calibrazione-attiva", json={"source": sorgente, "nome": nome})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -157,34 +169,49 @@ def test_c2_immagine_caricata_senza_salvare(client):
|
||||
files={"image": ("s.png", _png(una_scacchiera()), "image/png")})
|
||||
assert r.status_code == 200, r.text
|
||||
corpo = r.json()
|
||||
assert CHIAVI_PLANARE | {"overlay_png", "saved"} == set(corpo)
|
||||
assert CHIAVI_PLANARE | {"overlay_png", "calcolo_id"} == set(corpo) # #91: niente "saved", niente "source"
|
||||
assert corpo["metodo"] == "planare" and corpo["distorsione"] is False # #84: con cols/rows nulla cambia
|
||||
assert corpo["saved"] is False
|
||||
assert corpo["origine"] == "file:s.png"
|
||||
assert corpo["px_per_mm"] == pytest.approx(20.0, rel=0.005)
|
||||
# senza save non si scrive nulla
|
||||
# il calcolo non salva mai
|
||||
assert client.get("/camera/calibration", params={"source": "camera:folder"}).status_code == 404
|
||||
assert not client.config.exists() or not list(client.config.glob("*.json"))
|
||||
assert not client.config.exists() or not list(client.config.iterdir())
|
||||
|
||||
|
||||
def test_c2_save_true_viene_ignorato(client):
|
||||
# #91: il vecchio campo save non salva piu' nulla
|
||||
r = client.post("/camera/calibrate", data={"source": "camera:folder", "save": "true", **CAMPI})
|
||||
assert r.status_code == 200, r.text
|
||||
assert client.get("/camera/calibration", params={"source": "camera:folder"}).status_code == 404
|
||||
assert client.get("/camera/calibrazioni").json()["calibrazioni"] == []
|
||||
assert not client.config.exists() or not list(client.config.iterdir())
|
||||
|
||||
|
||||
def test_c2_c1_c3_ciclo_con_grab_dalla_sorgente(client):
|
||||
# nessun file: grab da camera:folder (la scacchiera in cartella), con save
|
||||
r = client.post("/camera/calibrate",
|
||||
data={"source": "camera:folder", "save": "true", **CAMPI})
|
||||
# nessun file: grab da camera:folder (la scacchiera in cartella)
|
||||
r = client.post("/camera/calibrate", data={"source": "camera:folder", **CAMPI})
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["saved"] is True
|
||||
assert r.json()["origine"] == "camera:folder"
|
||||
assert salva_con_nome(client, r.json(), "Banco").status_code == 201 # #91: si salva con nome, dopo
|
||||
|
||||
# salvata ma non ancora caricata sulla sorgente
|
||||
assert client.get("/camera/calibration", params={"source": "camera:folder"}).status_code == 404
|
||||
assert carica_su(client, "camera:folder", "Banco").status_code == 200
|
||||
|
||||
r1 = client.get("/camera/calibration", params={"source": "camera:folder"})
|
||||
assert r1.status_code == 200
|
||||
assert set(r1.json()) == CHIAVI_PLANARE # nessun overlay nel file salvato
|
||||
assert set(r1.json()) == CHIAVI_PLANARE | CHIAVI_ATTIVA # nessun overlay nel file salvato
|
||||
assert r1.json()["px_per_mm"] == pytest.approx(20.0, rel=0.005)
|
||||
assert r1.json()["source"] == "camera:folder"
|
||||
file_salvati = list(client.config.glob("*.json"))
|
||||
assert r1.json()["source"] == "camera:folder" and r1.json()["nome"] == "Banco"
|
||||
file_salvati = list((client.config / "calibrazioni").glob("*.json"))
|
||||
assert len(file_salvati) == 1
|
||||
assert json.loads(file_salvati[0].read_text(encoding="utf-8"))["cols"] == 9
|
||||
|
||||
# DELETE /camera/calibration scollega: la libreria resta
|
||||
assert client.delete("/camera/calibration", params={"source": "camera:folder"}).status_code == 204
|
||||
assert client.get("/camera/calibration", params={"source": "camera:folder"}).status_code == 404
|
||||
# cancellare di nuovo e' idempotente
|
||||
assert len(list((client.config / "calibrazioni").glob("*.json"))) == 1
|
||||
# scollegare di nuovo e' idempotente
|
||||
assert client.delete("/camera/calibration", params={"source": "camera:folder"}).status_code == 204
|
||||
|
||||
|
||||
@@ -214,12 +241,14 @@ def test_c2_errori_camera_come_grab(client):
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_due_sorgenti_hanno_file_diversi(client):
|
||||
for nome in ("camera:a b", "camera:a_b"):
|
||||
r = client.post("/camera/calibrate", data={"source": nome, "save": "true", **CAMPI},
|
||||
files={"image": ("s.png", _png(una_scacchiera()), "image/png")})
|
||||
assert r.status_code == 200
|
||||
assert len(list(client.config.glob("*.json"))) == 2
|
||||
def test_due_nomi_simili_hanno_file_diversi(client):
|
||||
# #91: i nomi si ripuliscono come prima (hash sul nome originale): due nomi diversi non si pestano
|
||||
r = client.post("/camera/calibrate", data={"source": "camera:folder", **CAMPI},
|
||||
files={"image": ("s.png", _png(una_scacchiera()), "image/png")})
|
||||
assert r.status_code == 200
|
||||
for nome in ("banco a b", "banco a_b"):
|
||||
assert salva_con_nome(client, r.json(), nome).status_code == 201
|
||||
assert len(list((client.config / "calibrazioni").glob("*.json"))) == 2
|
||||
|
||||
|
||||
def test_c4_pagina_html(client):
|
||||
@@ -308,11 +337,10 @@ def test_c2_senza_cols_rows_e_densa(client_denso):
|
||||
r = client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2"})
|
||||
assert r.status_code == 200, r.text
|
||||
corpo = r.json()
|
||||
assert CHIAVI_DENSA | {"overlay_png", "saved"} == set(corpo)
|
||||
assert CHIAVI_DENSA | {"overlay_png", "calcolo_id"} == set(corpo)
|
||||
assert corpo["metodo"] == "densa" and corpo["distorsione"] is True
|
||||
assert (corpo["cols"], corpo["rows"]) == (15, 11)
|
||||
assert corpo["px_per_mm"] == pytest.approx(1.0 / corpo["mm_per_px"])
|
||||
assert corpo["saved"] is False
|
||||
assert not client_denso.config.exists() or not list(client_denso.config.iterdir())
|
||||
|
||||
|
||||
@@ -327,14 +355,17 @@ def test_grab_raddrizzato_se_calibrata_e_grezzo_a_richiesta(client_denso, cartel
|
||||
assert prima.headers["x-raddrizzato"] == "no" # non calibrata: frame com'e'
|
||||
assert np.array_equal(_grigio(prima), originale)
|
||||
|
||||
r = client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2", "save": "true"})
|
||||
r = client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2"})
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["saved"] is True
|
||||
json_salvati, mappe = list(client_denso.config.glob("*.json")), list(client_denso.config.glob("*.npz"))
|
||||
assert salva_con_nome(client_denso, r.json(), "Densa").status_code == 201 # #91: salvata con nome
|
||||
libreria = client_denso.config / "calibrazioni"
|
||||
json_salvati, mappe = list(libreria.glob("*.json")), list(libreria.glob("*.npz"))
|
||||
assert len(json_salvati) == 1 and len(mappe) == 1
|
||||
assert mappe[0].stem == json_salvati[0].stem # stesso nome del JSON
|
||||
assert client_denso.post("/camera/grab", json={"source": SORGENTE}).headers["x-raddrizzato"] == "no" # salvata != attiva
|
||||
assert carica_su(client_denso, SORGENTE, "Densa").status_code == 200
|
||||
saved = client_denso.get("/camera/calibration", params={"source": SORGENTE}).json()
|
||||
assert set(saved) == CHIAVI_DENSA and saved["metodo"] == "densa"
|
||||
assert set(saved) == CHIAVI_DENSA | CHIAVI_ATTIVA and saved["metodo"] == "densa"
|
||||
|
||||
dopo = client_denso.post("/camera/grab", json={"source": SORGENTE})
|
||||
assert dopo.status_code == 200 and dopo.headers["x-raddrizzato"] == "si"
|
||||
@@ -359,19 +390,33 @@ def test_grab_grezzo_non_booleano_400(client_denso):
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_delete_toglie_anche_la_mappa(client_denso):
|
||||
client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2", "save": "true"})
|
||||
assert list(client_denso.config.glob("*.npz"))
|
||||
def test_scollega_lascia_la_libreria_ed_elimina_toglie_la_mappa(client_denso):
|
||||
r = client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2"})
|
||||
salva_con_nome(client_denso, r.json(), "Densa")
|
||||
carica_su(client_denso, SORGENTE, "Densa")
|
||||
libreria = client_denso.config / "calibrazioni"
|
||||
assert list(libreria.glob("*.npz"))
|
||||
# scollegare: il frame torna com'e', ma la voce (e la mappa) restano in libreria
|
||||
assert client_denso.delete("/camera/calibration", params={"source": SORGENTE}).status_code == 204
|
||||
assert not list(client_denso.config.glob("*.npz")) and not list(client_denso.config.glob("*.json"))
|
||||
assert client_denso.post("/camera/grab", json={"source": SORGENTE}).headers["x-raddrizzato"] == "no"
|
||||
assert list(libreria.glob("*.npz")) and list(libreria.glob("*.json"))
|
||||
# eliminare la voce toglie JSON e mappa
|
||||
carica_su(client_denso, SORGENTE, "Densa")
|
||||
assert client_denso.delete("/camera/calibrazioni", params={"nome": "Densa"}).status_code == 204
|
||||
assert not list(libreria.glob("*.npz")) and not list(libreria.glob("*.json"))
|
||||
assert client_denso.post("/camera/grab", json={"source": SORGENTE}).headers["x-raddrizzato"] == "no"
|
||||
|
||||
|
||||
def test_ricalibrare_planare_toglie_la_mappa_densa(client_denso):
|
||||
client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2", "save": "true"})
|
||||
assert list(client_denso.config.glob("*.npz"))
|
||||
planare = client_denso.post("/camera/calibrate", data={"source": SORGENTE, "save": "true", **CAMPI},
|
||||
densa = client_denso.post("/camera/calibrate", data={"source": SORGENTE, "square_mm": "2"})
|
||||
salva_con_nome(client_denso, densa.json(), "Cal")
|
||||
carica_su(client_denso, SORGENTE, "Cal")
|
||||
libreria = client_denso.config / "calibrazioni"
|
||||
assert list(libreria.glob("*.npz"))
|
||||
planare = client_denso.post("/camera/calibrate", data={"source": SORGENTE, **CAMPI},
|
||||
files={"image": ("s.png", _png(una_scacchiera()), "image/png")})
|
||||
assert planare.status_code == 200 and planare.json()["metodo"] == "planare"
|
||||
assert not list(client_denso.config.glob("*.npz")) # niente mappa vecchia che raddrizza a vuoto
|
||||
assert salva_con_nome(client_denso, planare.json(), "Cal").status_code == 409 # nome gia' preso
|
||||
assert salva_con_nome(client_denso, planare.json(), "Cal", sovrascrivi=True).status_code == 201
|
||||
assert not list(libreria.glob("*.npz")) # niente mappa vecchia che raddrizza a vuoto
|
||||
assert client_denso.post("/camera/grab", json={"source": SORGENTE}).headers["x-raddrizzato"] == "no"
|
||||
|
||||
Reference in New Issue
Block a user