Acquisitore: calibrazione densa su C2/grab/pagina, test (#84)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jbf7qKuxsnAVjxthRy7dnB
This commit is contained in:
2026-10-23 23:56:15 +02:00
parent 896a30b376
commit 7fa5bacdfe
3 changed files with 265 additions and 34 deletions
+34 -21
View File
@@ -3,6 +3,8 @@
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.
Dipende da: niente. E' una stringa, cosi' il pacchetto compilato non ha file dati.
"""
@@ -41,20 +43,18 @@ PAGINA = """<!doctype html>
<div class="scheda">
<div class="riga">
<div><label for="sorgente">Sorgente</label><select id="sorgente"></select></div>
<div><label for="cols">Angoli interni in orizzontale</label><input id="cols" type="number" min="2" value="9"></div>
<div><label for="rows">Angoli interni in verticale</label><input id="rows" type="number" min="2" value="6"></div>
<div><label for="quadrato">Lato del quadrato (mm)</label><input id="quadrato" type="number" min="0.001" step="any" value="2"></div>
<div><label for="quadrato">Lato dello scacco (mm)</label><input id="quadrato" type="number" min="0.001" step="any" value="2"></div>
</div>
<p style="font-size:.85rem;color:#4b5563;margin:10px 0 0">
Gli angoli interni sono quelli dove si toccano quattro quadrati: una scacchiera di 10 x 7 quadrati ha 9 x 6 angoli.
Tenere la scacchiera piana e ferma, ben illuminata.</p>
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.</p>
</div>
<div class="scheda">
<div class="riga">
<button id="acquisisci">Acquisisci e calcola</button>
<button id="acquisisci">Acquisisci e calibra</button>
<div><label for="file">oppure un file immagine</label><input id="file" type="file" accept="image/*"></div>
<button id="carica" class="secondario">Carica immagine e calcola</button>
<button id="carica" class="secondario">Carica immagine e calibra</button>
</div>
<div id="messaggio" class="messaggio"></div>
</div>
@@ -64,7 +64,7 @@ PAGINA = """<!doctype html>
<table id="tabella"></table>
<div id="avviso" class="avviso"></div>
<div class="riga" style="margin-top:10px">
<button id="salva">Salva per questa sorgente</button>
<button id="salva">Salva</button>
<span style="font-size:.85rem;color:#4b5563">Da camera, salvando si acquisisce e si calcola di nuovo: tenere la scacchiera ferma.</span>
</div>
<img id="overlay" alt="Angoli trovati sulla scacchiera">
@@ -95,12 +95,19 @@ function numero(v, cifre) { return Number(v).toFixed(cifre); }
function righe(d) {
return [
["Scala", numero(d.px_per_mm, 4) + " px/mm (" + numero(d.mm_per_px, 5) + " mm/px)"],
["Metodo", d.metodo !== "densa" ? "planare (senza correzione della distorsione)" : "densa (immagine raddrizzata)"],
["Griglia trovata", d.cols + " x " + d.rows + " angoli (" + d.n_corners + ")"]
].concat(d.metodo !== "densa" ? [
["Errore di riproiezione (RMS)", numero(d.rms_px, 3) + " px"],
["Variazione di scala nell'area", numero(d.scale_spread_pct, 2) + " %"],
["Angoli trovati", d.n_corners + " (" + d.cols + " x " + d.rows + ")"],
["Lato del quadrato", d.square_mm + " mm"],
["Variazione di scala nell'area", numero(d.scale_spread_pct, 2) + " %"]
] : [
["Residuo massimo", numero(d.residuo_max_px, 3) + " px"],
["Residuo RMS", numero(d.residuo_rms_px, 3) + " px"],
["Copertura del campo", numero(d.copertura_pct, 1) + " %"]
]).concat([
["Lato dello scacco", d.square_mm + " mm"],
["Immagine", d.image_width + " x " + d.image_height + " px"]
];
]);
}
function tabella(el, d) {
@@ -157,9 +164,19 @@ async function aggiornaStato() {
}
function parametri() {
return {
cols: $("cols").value, rows: $("rows").value, quadrato: $("quadrato").value
};
return { quadrato: $("quadrato").value };
}
// avvisi sulla qualita' (solo calibrazione densa): copertura bassa o residuo alto
function avvisi(d) {
var a = [];
if (d.metodo !== "densa") {
if (d.scale_spread_pct > 1) a.push("Attenzione: la scala varia di " + numero(d.scale_spread_pct, 2) + " % nell'area: scacchiera inclinata o ottica non telecentrica.");
} else {
if (d.copertura_pct < 70) a.push("Attenzione: la scacchiera copre solo il " + numero(d.copertura_pct, 1) + " % del campo (meno del 70 %): la correzione ai bordi non e' misurata.");
if (d.residuo_max_px > 0.5) a.push("Attenzione: residuo massimo " + numero(d.residuo_max_px, 3) + " px (oltre 0,5 px): scacchiera non piana, sfocata o con scacchi irregolari.");
}
return a.join(" ");
}
async function calcola(conFile, salva) {
@@ -168,8 +185,6 @@ async function calcola(conFile, salva) {
var p = parametri();
var f = new FormData();
f.append("source", sorgente);
f.append("cols", p.cols);
f.append("rows", p.rows);
f.append("square_mm", p.quadrato);
f.append("save", salva ? "true" : "false");
if (conFile) {
@@ -186,11 +201,9 @@ async function calcola(conFile, salva) {
ultimo = { conFile: conFile, dati: d };
tabella($("tabella"), d);
$("overlay").src = "data:image/png;base64," + d.overlay_png;
$("avviso").textContent = d.scale_spread_pct > 1
? "Attenzione: la scala varia di " + numero(d.scale_spread_pct, 2) + " % nell'area: scacchiera inclinata o ottica non telecentrica."
: "";
$("avviso").textContent = avvisi(d);
$("risultato").hidden = false;
messaggio(d.saved ? "Calibrazione salvata." : "Calcolo riuscito: controllare e salvare.", "ok");
messaggio(d.saved ? "Calibrazione salvata." : "Calibrazione riuscita: controllare e salvare.", "ok");
if (d.saved) aggiornaStato();
} catch (e) {
messaggio("Servizio non raggiungibile.", "errore");