From 0629f1b5ec6bf28cd9b1751c5c6332a471cb1c6a Mon Sep 17 00:00:00 2001 From: "Adriano (daitarn)" Date: Sat, 24 Oct 2026 14:38:39 +0200 Subject: [PATCH 1/2] Acquisizione: pagina nuova con anteprima, parametri camera e stile TMF (#87) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01Jbf7qKuxsnAVjxthRy7dnB --- scripts/calibrazione_pagina.py | 769 ++++++++++++++++++---- scripts/tests/test_pagina_acquisizione.py | 54 ++ 2 files changed, 705 insertions(+), 118 deletions(-) create mode 100644 scripts/tests/test_pagina_acquisizione.py diff --git a/scripts/calibrazione_pagina.py b/scripts/calibrazione_pagina.py index 500ddb2..9cb0935 100644 --- a/scripts/calibrazione_pagina.py +++ b/scripts/calibrazione_pagina.py @@ -1,80 +1,193 @@ -"""Pagina HTML della calibrazione a scacchiera (servita da camera_service su /calibrazione, #83). +"""Pagina HTML dell'acquisizione camera (servita da camera_service su /calibrazione e /acquisizione, #83 #87). -Cosa fa: una pagina sola, autonoma e offline (niente CDN, JS e CSS inline), in -italiano. Parla con le rotte dello stesso servizio (/camera/sources, -/camera/calibration, /camera/calibrate). -#84: calibrazione densa, un solo dato dell'operatore (lato dello scacco in mm): -scacchiera che riempie il campo, griglia trovata da sola, mappa di distorsione. +Cosa fa: una pagina sola, autonoma e offline (niente CDN, niente font esterni, +JS e CSS inline), in italiano, stile scuro TMF. Quattro sezioni: Sorgente, +Anteprima (dal vivo, con nitidezza/saturazione/livello medio), Parametri camera +(esposizione e guadagno), Calibrazione a scacchiera. +Parla con le rotte dello stesso servizio: /camera/sources, /camera/anteprima, +/camera/parametri, /camera/parametri/salva, /camera/calibration, /camera/calibrate. +#84: calibrazione densa, un solo dato dell'operatore (lato dello scacco in mm). +#87: anteprima, parametri camera, stile TMF. Dipende da: niente. E' una stringa, cosi' il pacchetto compilato non ha file dati. """ -PAGINA = """ +PAGINA = r""" -Calibrazione camera +Acquisizione camera
-

Calibrazione camera con scacchiera

+

Acquisizione camera

-
-
-
-
+
+
+ +
+

Anteprima

+
+ + + + + +
+
+
Anteprima ferma. Premere «Avvia anteprima» o «Scatta».
+ +
+
+
+
Nitidezza
+
-
+
+
massimo visto: - +
+
+
+
Saturati
+
-
+
pixel a 250 o più; da tenere sotto l'1 %
+
+
+
Livello medio
+
-
+
scala 0-255
+
+
+
Frame pieno
+
-
+
 
+
+
+
+
-

- Servono una scacchiera che riempie tutto il campo della camera, piana, ferma e ben illuminata, e il lato di uno scacco. - La griglia si trova da sola; il risultato corregge anche la distorsione dell'ottica.

-
-
-
- -
- -
-
-
+
+ +
+

Sorgente

+ + +
-
+
+
- + +
+

Parametri camera

+
-
+ +
+
-
- Calibrazione salvata -
-
-
- +
+

Calibrazione

+
+
+
+

+ Servono una scacchiera che riempie tutto il campo della camera, piana, ferma e ben illuminata, e il lato di uno scacco. + La griglia si trova da sola; il risultato corregge anche la distorsione dell'ottica. + Durante il calcolo l'anteprima si sospende.

+
+ + + + nessuna immagine scelta + +
+
+ + + +
+ Calibrazione salvata +
-
+
+ +
+
+
@@ -82,31 +195,437 @@ PAGINA = """ diff --git a/scripts/tests/test_pagina_acquisizione.py b/scripts/tests/test_pagina_acquisizione.py new file mode 100644 index 0000000..052ce57 --- /dev/null +++ b/scripts/tests/test_pagina_acquisizione.py @@ -0,0 +1,54 @@ +"""Test della pagina Acquisizione camera (#87): contenuto statico di PAGINA, senza servizio.""" + +import importlib.util +import re +from pathlib import Path + +_FILE = Path(__file__).resolve().parents[1] / "calibrazione_pagina.py" +_spec = importlib.util.spec_from_file_location("calibrazione_pagina", _FILE) +_mod = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_mod) +PAGINA = _mod.PAGINA + + +def test_titolo_e_stile_scuro(): + assert "Acquisizione camera" in PAGINA + assert "#0f172a" in PAGINA and "#1e293b" in PAGINA and "#3b82f6" in PAGINA + + +def test_rotte_usate(): + for rotta in ("/camera/sources", "/camera/anteprima", "/camera/parametri", + "/camera/parametri/salva", "/camera/calibrate", "/camera/calibration"): + assert '"' + rotta in PAGINA, rotta + + +def test_offline_e_senza_finestre_del_browser(): + for vietato in ("alert(", "confirm(", "prompt(", "http://", "https://"): + assert vietato not in PAGINA, vietato + + +def test_id_principali(): + ids = set(re.findall(r'id="([^"]+)"', PAGINA)) + attesi = {"sorgente", "avvia", "ferma", "scatta", "grezzo", "mirinoCasella", "immagine", + "nitidezza", "nitidezzaMax", "saturati", "media", "dimensioni", + "parametri", "salvaParametri", "quadrato", "acquisisci", "scegliFile", "file", + "carica", "salva", "elimina", "stato", "statoSorgente"} + assert attesi <= ids, attesi - ids + # ogni id usato dal JS con $("...") esiste nel markup + usati = set(re.findall(r'\$\("([^"]+)"\)', PAGINA)) + assert usati <= ids, usati - ids + + +def test_sezioni_e_testi_italiani(): + for testo in ("Sorgente", "Anteprima", "Parametri camera", "Calibrazione", + "Salva come predefiniti", "Scegli immagine...", "Questa sorgente non ha parametri regolabili", + "Immagine grezza (senza correzione)"): + assert testo in PAGINA, testo + assert "it-IT" in PAGINA # numeri con la virgola + assert "Choose File" not in PAGINA + + +def test_localstorage_protetto(): + # ogni uso di localStorage sta dentro un try + for m in re.finditer(r"localStorage[.]", PAGINA): + assert "try {" in PAGINA[max(0, m.start() - 200):m.start()] From 13eeec711930cb496191e7332dbb13189ad50a0a Mon Sep 17 00:00:00 2001 From: "Adriano (daitarn)" Date: Sat, 24 Oct 2026 14:41:25 +0200 Subject: [PATCH 2/2] Acquisizione: pagina, attributo hidden rispettato, indicatori su una riga, mirino a 2 px (#87) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01Jbf7qKuxsnAVjxthRy7dnB --- scripts/calibrazione_pagina.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/calibrazione_pagina.py b/scripts/calibrazione_pagina.py index 9cb0935..fbb7aee 100644 --- a/scripts/calibrazione_pagina.py +++ b/scripts/calibrazione_pagina.py @@ -22,6 +22,7 @@ PAGINA = r""" :root { --sfondo: #0f172a; --pannello: #1e293b; --bordo: #334155; --accento: #3b82f6; --testo: #e2e8f0; --secondario: #94a3b8; --errore: #f87171; --ok: #4ade80; --avviso: #fbbf24; } * { box-sizing: border-box; } + [hidden] { display: none !important; } /* #87: vale anche dove il CSS imposta display */ body { font-family: "Segoe UI", system-ui, sans-serif; margin: 0; background: var(--sfondo); color: var(--testo); } main { max-width: 1500px; margin: 0 auto; padding: 14px; } h1 { font-size: 1.35rem; margin: 0 0 12px; } @@ -57,10 +58,10 @@ PAGINA = r""" .cornice { position: relative; display: inline-block; line-height: 0; max-width: 100%; } .cornice img { max-width: 100%; max-height: calc(100vh - 330px); min-height: 200px; border: 0; margin: 0; } #mirino { position: absolute; inset: 0; pointer-events: none; - background: linear-gradient(#fbbf24, #fbbf24) center / 1px 100% no-repeat, - linear-gradient(#fbbf24, #fbbf24) center / 100% 1px no-repeat; opacity: .85; } + background: linear-gradient(#fbbf24, #fbbf24) center / 2px 100% no-repeat, + linear-gradient(#fbbf24, #fbbf24) center / 100% 2px no-repeat; opacity: .85; } #vuoto { padding: 30px 16px; color: var(--secondario); text-align: center; } - .indicatori { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; margin-top: 10px; } + .indicatori { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-top: 10px; } .indicatore { background: var(--sfondo); border: 1px solid var(--bordo); border-radius: 6px; padding: 8px 10px; } .indicatore .nome { font-size: .78rem; color: var(--secondario); } .indicatore .valore { font-size: 1.35rem; font-weight: 600; font-variant-numeric: tabular-nums; }