552f96bb47
POST /camera/calibrazioni/manuale salva una voce senza mappa (metodo manuale); anteprima/calibrata senza mappa risponde il frame grezzo (X-Raddrizzato: no); scheda «Calibrazione manuale» nella tab Calibrazione. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
149 lines
7.0 KiB
Python
149 lines
7.0 KiB
Python
"""Test di #95: calibrazione manuale (mm/px impostato a mano) in scripts/camera_service.py e nella PAGINA.
|
||
|
||
Uso: uv run --python 3.13 --extra vision-worker --extra dev \
|
||
--with-editable vendor/visionsuite/packages/vs-camera pytest scripts/tests -q
|
||
Sorgenti = immagini caricate in tmp_path: niente camera vera, niente ProgramData.
|
||
"""
|
||
import re
|
||
|
||
import numpy as np
|
||
import pytest
|
||
|
||
from test_calibrazione import _png, cartella, client # fixture della calibrazione
|
||
from test_immagini_affiancate import _anteprima, _calibrata, _carica
|
||
from test_pagina_acquisizione import PAGINA
|
||
|
||
__all__ = ["cartella", "client"] # le fixture importate restano usate
|
||
|
||
SRC = "banco.png" # 64x48
|
||
ALTRA = "grande.png" # 80x60, risoluzione diversa
|
||
|
||
|
||
@pytest.fixture
|
||
def sorgenti(client):
|
||
assert _carica(client, SRC, np.full((48, 64), 120, np.uint8)).status_code == 201
|
||
assert _carica(client, ALTRA, np.full((60, 80), 120, np.uint8)).status_code == 201
|
||
return client
|
||
|
||
|
||
def _manuale(c, nome="Manuale", mm=0.05, source=SRC, **extra):
|
||
return c.post("/camera/calibrazioni/manuale",
|
||
json={"source": source, "nome": nome, "mm_per_px": mm, **extra})
|
||
|
||
|
||
# ---- servizio
|
||
|
||
def test_manuale_201_con_scala_e_risoluzione_del_frame(sorgenti):
|
||
r = _manuale(sorgenti, mm=0.05)
|
||
assert r.status_code == 201, r.text
|
||
m = r.json()
|
||
assert m["nome"] == "Manuale" and m["metodo"] == "manuale"
|
||
assert m["px_per_mm"] == pytest.approx(20.0) and m["mm_per_px"] == pytest.approx(0.05)
|
||
assert (m["image_width"], m["image_height"]) == (64, 48)
|
||
assert m["origine"] == SRC and m["square_mm"] is None and m["attiva_su"] == [] # non la attiva
|
||
voci = sorgenti.get("/camera/calibrazioni").json()["calibrazioni"]
|
||
assert [v["nome"] for v in voci] == ["Manuale"]
|
||
|
||
|
||
def test_manuale_usa_il_frame_tenuto_se_c_e(sorgenti):
|
||
assert _anteprima(sorgenti, SRC).status_code == 200 # tiene il grezzo 64x48
|
||
r = _manuale(sorgenti)
|
||
assert r.status_code == 201 and (r.json()["image_width"], r.json()["image_height"]) == (64, 48)
|
||
|
||
|
||
@pytest.mark.parametrize("valore", [0, -1, 0.000001, 100.5, "abc", None, True])
|
||
def test_manuale_mm_non_valido_400(sorgenti, valore):
|
||
r = _manuale(sorgenti, mm=valore)
|
||
assert r.status_code == 400
|
||
assert "mm/px deve essere un numero positivo" in r.json()["detail"]
|
||
|
||
|
||
def test_manuale_senza_source_o_nome_400_e_sorgente_ignota_404(sorgenti):
|
||
assert sorgenti.post("/camera/calibrazioni/manuale", json={"nome": "x", "mm_per_px": 0.1}).status_code == 400
|
||
assert _manuale(sorgenti, nome=" ").status_code == 400
|
||
assert _manuale(sorgenti, source="nonesiste.png").status_code == 404
|
||
|
||
|
||
def test_manuale_409_nome_doppio_e_sovrascrivi(sorgenti):
|
||
assert _manuale(sorgenti, mm=0.05).status_code == 201
|
||
r = _manuale(sorgenti, mm=0.1)
|
||
assert r.status_code == 409 and "esiste già una calibrazione" in r.json()["detail"]
|
||
r = _manuale(sorgenti, mm=0.1, sovrascrivi=True)
|
||
assert r.status_code == 201 and r.json()["px_per_mm"] == pytest.approx(10.0)
|
||
assert _manuale(sorgenti, mm=0.1, sovrascrivi="si").status_code == 400
|
||
|
||
|
||
def test_attiva_manuale_ok_su_stessa_risoluzione_e_legge_px_per_mm(sorgenti):
|
||
assert _manuale(sorgenti, mm=0.05).status_code == 201
|
||
r = sorgenti.put("/camera/calibrazione-attiva", json={"source": SRC, "nome": "Manuale"})
|
||
assert r.status_code == 200, r.text
|
||
g = sorgenti.get("/camera/calibration", params={"source": SRC})
|
||
assert g.status_code == 200 and g.json()["px_per_mm"] == pytest.approx(20.0)
|
||
assert g.json()["nome"] == "Manuale" and g.json()["metodo"] == "manuale"
|
||
|
||
|
||
def test_attiva_manuale_409_su_risoluzione_diversa(sorgenti):
|
||
assert _manuale(sorgenti).status_code == 201
|
||
r = sorgenti.put("/camera/calibrazione-attiva", json={"source": ALTRA, "nome": "Manuale"})
|
||
assert r.status_code == 409 and "64×48" in r.json()["detail"]
|
||
|
||
|
||
def test_anteprima_calibrata_con_manuale_200_grezza(sorgenti):
|
||
assert _manuale(sorgenti).status_code == 201
|
||
assert sorgenti.put("/camera/calibrazione-attiva", json={"source": SRC, "nome": "Manuale"}).status_code == 200
|
||
assert _anteprima(sorgenti, SRC).status_code == 200
|
||
r = _calibrata(sorgenti, SRC)
|
||
assert r.status_code == 200, r.text
|
||
assert r.headers["X-Raddrizzato"] == "no" and r.headers["X-Calibrazione"] == "Manuale"
|
||
assert (r.headers["X-Larghezza"], r.headers["X-Altezza"]) == ("64", "48")
|
||
assert r.headers["content-type"] == "image/jpeg"
|
||
|
||
|
||
def test_anteprima_calibrata_manuale_409_se_il_frame_ha_altra_risoluzione(sorgenti):
|
||
assert _manuale(sorgenti).status_code == 201
|
||
assert sorgenti.put("/camera/calibrazione-attiva", json={"source": SRC, "nome": "Manuale"}).status_code == 200
|
||
assert _anteprima(sorgenti, ALTRA).status_code == 200
|
||
# stessa sorgente attiva, ma il frame tenuto cambia (immagine sostituita con altra misura)
|
||
assert _carica(sorgenti, SRC, np.full((60, 80), 9, np.uint8)).status_code == 201
|
||
assert _anteprima(sorgenti, SRC).status_code == 200
|
||
r = _calibrata(sorgenti, SRC)
|
||
assert r.status_code == 409 and "64×48" in r.json()["detail"]
|
||
|
||
|
||
# ---- pagina
|
||
|
||
def test_pagina_scheda_manuale_id_e_testi():
|
||
ids = set(re.findall(r'id="([^"]+)"', PAGINA))
|
||
assert {"mmPx", "pxMmCalc", "nomeManuale", "salvaManuale", "msgManuale"} <= ids
|
||
tab = PAGINA.split('id="pannelloCal"')[1]
|
||
assert tab.index('id="sezioneCalibrazione"') < tab.index('id="sezioneManuale"') < tab.index('id="sezioneSalvate"')
|
||
assert "Calibrazione manuale" in PAGINA and "mm per pixel" in PAGINA
|
||
assert "salvata: per usarla «Carica» o CONFERMA" in PAGINA
|
||
assert "Sovrascrivi «" in PAGINA.split("async function salvaManuale")[1] # stessa conferma del Salva scacchiera
|
||
assert "/camera/calibrazioni/manuale" in PAGINA and "alert(" not in PAGINA and "confirm(" not in PAGINA
|
||
|
||
|
||
def test_pagina_manuale_senza_sorgente_pulsante_spento_con_nota():
|
||
stato = PAGINA.split("function statoManuale")[1].split("function aggiornaManuale")[0]
|
||
assert 'salvaManuale").disabled = !s' in stato and "Scegliere una sorgente" in stato
|
||
assert "statoManuale()" in PAGINA.split("async function aggiornaStato")[1].split("async function caricaCalibrazioni")[0]
|
||
|
||
|
||
def test_pagina_px_mm_calcolato_a_ogni_tasto():
|
||
assert '$("mmPx").oninput = aggiornaManuale' in PAGINA
|
||
assert '"= " + fmt(1 / v, 4) + " px/mm"' in PAGINA
|
||
|
||
|
||
def test_pagina_metodo_manuale_e_campi_null():
|
||
assert 'c.metodo === "densa" || c.metodo === "manuale" ? c.metodo : "planare"' in PAGINA
|
||
righe = PAGINA.split("function righe")[1].split("function tabella")[0]
|
||
assert 'd.metodo === "manuale"' in righe and "Lato dello scacco" in righe # la manuale ha il suo elenco, le altre il solito
|
||
assert 'v === null || v === undefined' in PAGINA.split("function fmt")[1].split("function fmtCasella")[0]
|
||
stato = PAGINA.split("async function aggiornaStato")[1].split("async function caricaCalibrazioni")[0]
|
||
assert "Number(d.square_mm) > 0" in stato
|
||
|
||
|
||
def test_pagina_calibrata_senza_raddrizzamento_dice_solo_scala():
|
||
assert "solo scala, nessuna correzione ottica" in PAGINA
|
||
assert 'r.headers.get("X-Raddrizzato") === "no"' in PAGINA
|