Acquisizione: immagine da file come sorgente, riquadri grezza e calibrata affiancati (Refs #92)
Pagina: Sorgente in cima alla tab Acquisizione con carica/rimuovi immagine, riquadro Calibrata con casella, calibrazione sempre dal frame grezzo della sorgente, ciclo anteprima sospeso nella tab Calibrazione. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+200
-69
@@ -12,6 +12,11 @@ Parla con le rotte dello stesso servizio: /camera/sources, /camera/anteprima,
|
|||||||
visibile in entrambe. Calibrazioni salvate con nome in un archivio unico
|
visibile in entrambe. Calibrazioni salvate con nome in un archivio unico
|
||||||
(/camera/calibrazioni, /camera/calibrazione-attiva); il calcolo non salva piu':
|
(/camera/calibrazioni, /camera/calibrazione-attiva); il calcolo non salva piu':
|
||||||
si salva dal calcolo_id con un nome.
|
si salva dal calcolo_id con un nome.
|
||||||
|
#92: l'immagine da file e' una SORGENTE di acquisizione (POST /camera/immagini), non
|
||||||
|
un'opzione della calibrazione: la calibrazione usa sempre il frame grezzo della sorgente
|
||||||
|
scelta nella tab Acquisizione. Sorgente in cima alla tab; riquadri «Immagine» (grezza) e,
|
||||||
|
con la casella, «Calibrata» (/camera/anteprima/calibrata) affiancati; il ciclo anteprima
|
||||||
|
si sospende mentre la tab Calibrazione e' aperta.
|
||||||
Dipende da: niente. E' una stringa, cosi' il pacchetto compilato non ha file dati.
|
Dipende da: niente. E' una stringa, cosi' il pacchetto compilato non ha file dati.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -61,10 +66,19 @@ PAGINA = r"""<!doctype html>
|
|||||||
display: flex; align-items: center; justify-content: center; margin-top: 10px; overflow: hidden; }
|
display: flex; align-items: center; justify-content: center; margin-top: 10px; overflow: hidden; }
|
||||||
.cornice { position: relative; display: inline-block; line-height: 0; max-width: 100%; }
|
.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; }
|
.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;
|
.mirino { position: absolute; inset: 0; pointer-events: none; /* #92: classe, vale per i due riquadri */
|
||||||
background: linear-gradient(#fbbf24, #fbbf24) center / 2px 100% no-repeat,
|
background: linear-gradient(#fbbf24, #fbbf24) center / 2px 100% no-repeat,
|
||||||
linear-gradient(#fbbf24, #fbbf24) center / 100% 2px no-repeat; opacity: .85; }
|
linear-gradient(#fbbf24, #fbbf24) center / 100% 2px no-repeat; opacity: .85; }
|
||||||
#vuoto { padding: 30px 16px; color: var(--secondario); text-align: center; }
|
#vuoto, #vuotoCal { padding: 30px 16px; color: var(--secondario); text-align: center; }
|
||||||
|
/* #92: due riquadri affiancati (grezza | calibrata); in colonna sotto i 900 px */
|
||||||
|
.riquadri { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; margin-top: 10px; }
|
||||||
|
.riquadri.doppio { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
||||||
|
.riquadro .titolo { font-size: .88rem; font-weight: 600; }
|
||||||
|
.riquadro .titolo span { font-weight: 400; font-size: .78rem; color: var(--secondario); margin-left: 6px; }
|
||||||
|
.riquadro .palco { margin-top: 4px; }
|
||||||
|
.sorgente-riga { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
|
||||||
|
.sorgente-riga .campo-sorgente { flex: 1 1 260px; min-width: 0; }
|
||||||
|
.sorgente-riga select { width: 100%; }
|
||||||
.indicatori { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 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 { background: var(--sfondo); border: 1px solid var(--bordo); border-radius: 6px; padding: 8px 10px; }
|
||||||
.indicatore .nome { font-size: .78rem; color: var(--secondario); }
|
.indicatore .nome { font-size: .78rem; color: var(--secondario); }
|
||||||
@@ -94,10 +108,9 @@ PAGINA = r"""<!doctype html>
|
|||||||
.scheda-cal .azioni button { padding: 3px 10px; font-size: .82rem; }
|
.scheda-cal .azioni button { padding: 3px 10px; font-size: .82rem; }
|
||||||
.segno { display: inline-block; font-size: .72rem; padding: 0 6px; border-radius: 9px; background: var(--ok); color: #052e16; margin-left: 6px; }
|
.segno { display: inline-block; font-size: .72rem; padding: 0 6px; border-radius: 9px; background: var(--ok); color: #052e16; margin-left: 6px; }
|
||||||
.segno.diversa { background: var(--avviso); color: #422006; }
|
.segno.diversa { background: var(--avviso); color: #422006; }
|
||||||
.origine label { display: inline-block; margin: 0 14px 4px 0; color: var(--testo); font-size: .92rem; }
|
|
||||||
input[type=radio] { accent-color: var(--accento); }
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.griglia { grid-template-columns: minmax(0, 1fr); }
|
.griglia { grid-template-columns: minmax(0, 1fr); }
|
||||||
|
.riquadri.doppio { grid-template-columns: minmax(0, 1fr); } /* #92: in colonna */
|
||||||
.cornice img { max-height: 60vh; }
|
.cornice img { max-height: 60vh; }
|
||||||
}
|
}
|
||||||
@media (max-width: 500px) { /* #91: utilizzabile anche stretta (400 px) */
|
@media (max-width: 500px) { /* #91: utilizzabile anche stretta (400 px) */
|
||||||
@@ -112,29 +125,59 @@ PAGINA = r"""<!doctype html>
|
|||||||
<main>
|
<main>
|
||||||
<h1>Acquisizione camera</h1>
|
<h1>Acquisizione camera</h1>
|
||||||
|
|
||||||
<!-- #91: due tab; la colonna dell'anteprima resta visibile in entrambe -->
|
<!-- #91: due tab; #92: l'anteprima sta nella tab Acquisizione e si sospende nella tab Calibrazione -->
|
||||||
<div class="tabs" role="tablist" aria-label="Sezioni della pagina">
|
<div class="tabs" role="tablist" aria-label="Sezioni della pagina">
|
||||||
<button id="tabAcq" type="button" role="tab" aria-selected="true" aria-controls="pannelloAcq">Acquisizione</button>
|
<button id="tabAcq" type="button" role="tab" aria-selected="true" aria-controls="pannelloAcq">Acquisizione</button>
|
||||||
<button id="tabCal" type="button" role="tab" aria-selected="false" aria-controls="pannelloCal" tabindex="-1">Calibrazione</button>
|
<button id="tabCal" type="button" role="tab" aria-selected="false" aria-controls="pannelloCal" tabindex="-1">Calibrazione</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="griglia">
|
<div class="colonna" id="pannelloAcq" role="tabpanel" aria-labelledby="tabAcq">
|
||||||
<div class="colonna">
|
<!-- #92: Sorgente in cima, a larghezza piena: camere, cartella e immagini caricate da file -->
|
||||||
<!-- #87: sezione Anteprima -->
|
<section class="scheda" id="sezioneSorgente">
|
||||||
|
<h2>Sorgente</h2>
|
||||||
|
<div class="sorgente-riga">
|
||||||
|
<div class="campo-sorgente">
|
||||||
|
<label for="sorgente">Camera o immagine</label>
|
||||||
|
<select id="sorgente"></select>
|
||||||
|
</div>
|
||||||
|
<button id="caricaImmagine" class="secondario" type="button">Carica immagine da file...</button>
|
||||||
|
<input id="fileImmagine" type="file" accept="image/*" hidden>
|
||||||
|
<button id="rimuoviImmagine" class="secondario" type="button" hidden>Rimuovi immagine</button>
|
||||||
|
</div>
|
||||||
|
<div class="stato" id="statoSorgente">-</div>
|
||||||
|
<div id="msgSorgente" class="messaggio"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- #87: sezione Anteprima; #92: barra, due riquadri affiancati (grezza | calibrata), indicatori dal frame grezzo -->
|
||||||
<section class="scheda" id="sezioneAnteprima">
|
<section class="scheda" id="sezioneAnteprima">
|
||||||
<h2>Anteprima</h2>
|
<h2>Anteprima</h2>
|
||||||
<div class="riga">
|
<div class="riga">
|
||||||
<button id="avvia">Avvia anteprima</button>
|
<button id="avvia">Avvia anteprima</button>
|
||||||
<button id="ferma" class="secondario" disabled>Ferma anteprima</button>
|
<button id="ferma" class="secondario" disabled>Ferma anteprima</button>
|
||||||
<button id="scatta" class="secondario">Scatta</button>
|
<button id="scatta" class="secondario">Scatta</button>
|
||||||
<label style="margin:0 0 6px"><input id="grezzo" type="checkbox"> Immagine grezza (senza correzione)</label>
|
<label style="margin:0 0 6px"><input id="mostraCal" type="checkbox"> Mostra immagine calibrata</label>
|
||||||
<label style="margin:0 0 6px"><input id="mirinoCasella" type="checkbox"> Mirino centrale</label>
|
<label style="margin:0 0 6px"><input id="mirinoCasella" type="checkbox"> Mirino centrale</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="riquadri" id="riquadri">
|
||||||
|
<div class="riquadro">
|
||||||
|
<div class="titolo">Immagine<span>grezza, senza correzione</span></div>
|
||||||
<div class="palco">
|
<div class="palco">
|
||||||
<div id="vuoto">Anteprima ferma. Premere «Avvia anteprima» o «Scatta».</div>
|
<div id="vuoto">Anteprima ferma. Premere «Avvia anteprima» o «Scatta».</div>
|
||||||
<div class="cornice" id="cornice" hidden>
|
<div class="cornice" id="cornice" hidden>
|
||||||
<img id="immagine" alt="Anteprima della camera">
|
<img id="immagine" alt="Anteprima grezza della sorgente">
|
||||||
<div id="mirino" hidden></div>
|
<div id="mirino" class="mirino" hidden></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="riquadro" id="riquadroCal" hidden>
|
||||||
|
<div class="titolo">Calibrata<span id="nomeCalibrata"></span></div>
|
||||||
|
<div class="palco">
|
||||||
|
<div id="vuotoCal">Premere «Avvia anteprima» o «Scatta».</div>
|
||||||
|
<div class="cornice" id="corniceCal" hidden>
|
||||||
|
<img id="immagineCal" alt="Anteprima della sorgente raddrizzata dalla calibrazione">
|
||||||
|
<div id="mirinoCal" class="mirino" hidden></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="indicatori">
|
<div class="indicatori">
|
||||||
@@ -163,17 +206,6 @@ PAGINA = r"""<!doctype html>
|
|||||||
</div>
|
</div>
|
||||||
<div id="msgAnteprima" class="messaggio"></div>
|
<div id="msgAnteprima" class="messaggio"></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="colonna" id="pannelloAcq" role="tabpanel" aria-labelledby="tabAcq">
|
|
||||||
<!-- #87: sezione Sorgente -->
|
|
||||||
<section class="scheda" id="sezioneSorgente">
|
|
||||||
<h2>Sorgente</h2>
|
|
||||||
<label for="sorgente">Camera o cartella</label>
|
|
||||||
<select id="sorgente"></select>
|
|
||||||
<div class="stato" id="statoSorgente">-</div>
|
|
||||||
<div id="msgSorgente" class="messaggio"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- #87: sezione Parametri camera -->
|
<!-- #87: sezione Parametri camera -->
|
||||||
<section class="scheda" id="sezioneParametri">
|
<section class="scheda" id="sezioneParametri">
|
||||||
@@ -186,20 +218,11 @@ PAGINA = r"""<!doctype html>
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- #91: tab Calibrazione (colonna destra al posto di Sorgente e Parametri) -->
|
<!-- #91: tab Calibrazione; #92: sempre sul frame grezzo della sorgente scelta in Acquisizione, niente file qui -->
|
||||||
<div class="colonna" id="pannelloCal" role="tabpanel" aria-labelledby="tabCal" hidden>
|
<div class="griglia" id="pannelloCal" role="tabpanel" aria-labelledby="tabCal" hidden>
|
||||||
<section class="scheda" id="sezioneCalibrazione">
|
<section class="scheda" id="sezioneCalibrazione">
|
||||||
<h2>Calibrazione</h2>
|
<h2>Calibrazione</h2>
|
||||||
<div class="stato" style="margin:0 0 10px">Sorgente: <strong id="calSorgente">-</strong> (si cambia nella tab Acquisizione)</div>
|
<div class="stato" style="margin:0 0 10px">Sorgente: <strong id="calSorgente">-</strong> — immagine grezza dalla tab Acquisizione</div>
|
||||||
<div class="origine">
|
|
||||||
<label for="origSorgente"><input id="origSorgente" name="origine" type="radio" value="sorgente" checked> Sorgente selezionata</label>
|
|
||||||
<label for="origFile"><input id="origFile" name="origine" type="radio" value="file"> Immagine da file</label>
|
|
||||||
</div>
|
|
||||||
<div class="riga" style="margin-top:4px">
|
|
||||||
<button id="scegliFile" class="secondario">Scegli immagine...</button>
|
|
||||||
<input id="file" type="file" accept="image/*" hidden>
|
|
||||||
<span id="nomeFile" class="nota" style="margin:0 0 6px">nessuna immagine scelta</span>
|
|
||||||
</div>
|
|
||||||
<div class="riga" style="margin-top:10px">
|
<div class="riga" style="margin-top:10px">
|
||||||
<div><label for="quadrato">Lato dello scacco (mm)</label><input id="quadrato" type="text" inputmode="decimal" value="2" size="8"></div>
|
<div><label for="quadrato">Lato dello scacco (mm)</label><input id="quadrato" type="text" inputmode="decimal" value="2" size="8"></div>
|
||||||
<button id="acquisisci" title="Acquisisci e calibra">Calcola</button>
|
<button id="acquisisci" title="Acquisisci e calibra">Calcola</button>
|
||||||
@@ -207,7 +230,7 @@ PAGINA = r"""<!doctype html>
|
|||||||
<p class="nota">
|
<p class="nota">
|
||||||
Servono una scacchiera che riempie tutto il campo della camera, piana, ferma e ben illuminata, e il lato di uno scacco.
|
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.
|
La griglia si trova da sola; il risultato corregge anche la distorsione dell'ottica.
|
||||||
Calcolando dalla sorgente l'anteprima si sospende e poi riprende.</p>
|
Il frame è sempre quello grezzo della sorgente; l'anteprima resta sospesa finché questa tab è aperta.</p>
|
||||||
<div id="messaggio" class="messaggio"></div>
|
<div id="messaggio" class="messaggio"></div>
|
||||||
|
|
||||||
<div id="risultato" hidden style="margin-top:10px">
|
<div id="risultato" hidden style="margin-top:10px">
|
||||||
@@ -231,7 +254,6 @@ PAGINA = r"""<!doctype html>
|
|||||||
<div id="msgElenco" class="messaggio"></div>
|
<div id="msgElenco" class="messaggio"></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -289,8 +311,11 @@ async function dettaglio(risposta) {
|
|||||||
|
|
||||||
// ---- Sorgente
|
// ---- Sorgente
|
||||||
|
|
||||||
async function caricaSorgenti() {
|
// #92: scegli = nome da selezionare (immagine appena caricata); senza, l'ultima scelta ricordata
|
||||||
|
async function caricaSorgenti(scegli) {
|
||||||
var sel = $("sorgente");
|
var sel = $("sorgente");
|
||||||
|
var prima = sel.value;
|
||||||
|
sel.textContent = "";
|
||||||
try {
|
try {
|
||||||
var r = await fetch("/camera/sources");
|
var r = await fetch("/camera/sources");
|
||||||
var j = await r.json();
|
var j = await r.json();
|
||||||
@@ -298,23 +323,84 @@ async function caricaSorgenti() {
|
|||||||
var o = document.createElement("option");
|
var o = document.createElement("option");
|
||||||
o.value = s.name;
|
o.value = s.name;
|
||||||
o.textContent = s.label || s.name;
|
o.textContent = s.label || s.name;
|
||||||
|
o.dataset.kind = s.kind || ""; // #92: «image» = immagine caricata da file
|
||||||
sel.appendChild(o);
|
sel.appendChild(o);
|
||||||
});
|
});
|
||||||
} catch (e) { dire("msgSorgente", "Elenco delle sorgenti non disponibile.", "errore"); }
|
} catch (e) { dire("msgSorgente", "Elenco delle sorgenti non disponibile.", "errore"); }
|
||||||
if (!sel.options.length) dire("msgSorgente", "Nessuna sorgente camera trovata.", "errore");
|
if (!sel.options.length) dire("msgSorgente", "Nessuna sorgente camera trovata.", "errore");
|
||||||
var ricordata = memoria("tmf.acq.sorgente"); // #87: ultima scelta ricordata
|
var voluta = scegli || (prima === "" ? memoria("tmf.acq.sorgente") : prima); // #87: ultima scelta ricordata
|
||||||
if (ricordata) {
|
if (voluta) {
|
||||||
for (var i = 0; i < sel.options.length; i++) if (sel.options[i].value === ricordata) sel.value = ricordata;
|
for (var i = 0; i < sel.options.length; i++) if (sel.options[i].value === voluta) sel.value = voluta;
|
||||||
}
|
}
|
||||||
cambioSorgente(false);
|
cambioSorgente(!!scegli);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #92: la sorgente selezionata e' un'immagine caricata (si puo' rimuovere)?
|
||||||
|
function sorgenteImmagine() {
|
||||||
|
var sel = $("sorgente");
|
||||||
|
return sel.selectedIndex >= 0 && sel.options[sel.selectedIndex].dataset.kind === "image";
|
||||||
|
}
|
||||||
|
|
||||||
|
// #92: «Carica immagine da file...» -> POST /camera/immagini, poi la nuova immagine diventa la sorgente
|
||||||
|
$("caricaImmagine").onclick = function () { $("fileImmagine").click(); };
|
||||||
|
$("fileImmagine").onchange = async function () {
|
||||||
|
var file = $("fileImmagine").files[0];
|
||||||
|
$("fileImmagine").value = ""; // lo stesso file si puo' ricaricare
|
||||||
|
if (!file) return;
|
||||||
|
var f = new FormData();
|
||||||
|
f.append("image", file);
|
||||||
|
$("caricaImmagine").disabled = true;
|
||||||
|
dire("msgSorgente", "Caricamento di «" + file.name + "»...", "");
|
||||||
|
try {
|
||||||
|
var r = await fetch("/camera/immagini", { method: "POST", body: f });
|
||||||
|
if (!r.ok) { dire("msgSorgente", await dettaglio(r), "errore"); return; }
|
||||||
|
var j = await r.json();
|
||||||
|
await caricaSorgenti(j.source);
|
||||||
|
dire("msgSorgente", "Immagine «" + j.source + "» caricata: premere «Scatta» per vederla.", "ok");
|
||||||
|
} catch (e) {
|
||||||
|
dire("msgSorgente", "Servizio non raggiungibile.", "errore");
|
||||||
|
} finally {
|
||||||
|
$("caricaImmagine").disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// #92: «Rimuovi immagine» con doppio clic entro 4 s (niente finestre del browser)
|
||||||
|
var R = { timer: null };
|
||||||
|
function annullaRimuovi() {
|
||||||
|
clearTimeout(R.timer);
|
||||||
|
R.timer = null;
|
||||||
|
$("rimuoviImmagine").textContent = "Rimuovi immagine";
|
||||||
|
}
|
||||||
|
$("rimuoviImmagine").onclick = async function () {
|
||||||
|
var nome = $("sorgente").value;
|
||||||
|
if (!nome || !sorgenteImmagine()) return;
|
||||||
|
if (R.timer === null) {
|
||||||
|
$("rimuoviImmagine").textContent = "Confermi? Clic ancora";
|
||||||
|
R.timer = setTimeout(annullaRimuovi, 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
annullaRimuovi();
|
||||||
|
fermaAnteprima(); // la sorgente sta per sparire: niente richieste in volo su di lei
|
||||||
|
while (A.inVolo) await pausa(50);
|
||||||
|
try {
|
||||||
|
var r = await fetch("/camera/immagini?nome=" + encodeURIComponent(nome), { method: "DELETE" });
|
||||||
|
if (!r.ok) { dire("msgSorgente", await dettaglio(r), "errore"); return; }
|
||||||
|
await caricaSorgenti("");
|
||||||
|
dire("msgSorgente", "Immagine «" + nome + "» rimossa.", "ok");
|
||||||
|
} catch (e) {
|
||||||
|
dire("msgSorgente", "Servizio non raggiungibile.", "errore");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// #87: cambiando sorgente si azzerano gli indicatori e si rileggono parametri e calibrazione
|
// #87: cambiando sorgente si azzerano gli indicatori e si rileggono parametri e calibrazione
|
||||||
function cambioSorgente(salva) {
|
function cambioSorgente(salva) {
|
||||||
if (salva) memoria("tmf.acq.sorgente", $("sorgente").value);
|
if (salva) memoria("tmf.acq.sorgente", $("sorgente").value);
|
||||||
var era = A.attiva;
|
var era = A.attiva;
|
||||||
fermaAnteprima();
|
fermaAnteprima();
|
||||||
azzeraIndicatori();
|
azzeraIndicatori();
|
||||||
|
svuotaRiquadri(); // #92: il frame vecchio non e' della nuova sorgente
|
||||||
|
annullaRimuovi();
|
||||||
|
$("rimuoviImmagine").hidden = !sorgenteImmagine();
|
||||||
dire("msgAnteprima", "");
|
dire("msgAnteprima", "");
|
||||||
dire("msgElenco", ""); // #91
|
dire("msgElenco", ""); // #91
|
||||||
aggiornaStato();
|
aggiornaStato();
|
||||||
@@ -324,7 +410,8 @@ function cambioSorgente(salva) {
|
|||||||
|
|
||||||
// ---- Anteprima (#87)
|
// ---- Anteprima (#87)
|
||||||
|
|
||||||
var A = { attiva: false, gen: 0, inVolo: false, url: null, nitMax: 0, ris: null }; // #91: ris = {w, h} dell'ultimo frame
|
// #91: ris = {w, h} dell'ultimo frame; #92: urlCal/seqCal = riquadro calibrata, riprendi = ciclo sospeso dalla tab Calibrazione, lato = ultimo lato_max
|
||||||
|
var A = { attiva: false, gen: 0, inVolo: false, url: null, urlCal: null, seqCal: 0, riprendi: false, lato: 1280, nitMax: 0, ris: null };
|
||||||
|
|
||||||
function azzeraIndicatori() {
|
function azzeraIndicatori() {
|
||||||
A.nitMax = 0;
|
A.nitMax = 0;
|
||||||
@@ -350,6 +437,52 @@ function mostraImmagine(blob) {
|
|||||||
$("vuoto").hidden = true;
|
$("vuoto").hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #92: riquadro «Calibrata»: l'immagine raddrizzata oppure il motivo per cui non c'e' (404/409 del servizio)
|
||||||
|
function mostraCalibrata(blob, nome) {
|
||||||
|
var vecchio = A.urlCal;
|
||||||
|
A.urlCal = URL.createObjectURL(blob);
|
||||||
|
$("immagineCal").src = A.urlCal;
|
||||||
|
if (vecchio) URL.revokeObjectURL(vecchio);
|
||||||
|
$("corniceCal").hidden = false;
|
||||||
|
$("vuotoCal").hidden = true;
|
||||||
|
$("nomeCalibrata").textContent = nome ? "«" + nome + "»" : "";
|
||||||
|
}
|
||||||
|
function messaggioCalibrata(testo) {
|
||||||
|
$("corniceCal").hidden = true;
|
||||||
|
$("vuotoCal").hidden = false;
|
||||||
|
$("vuotoCal").textContent = testo;
|
||||||
|
$("nomeCalibrata").textContent = "";
|
||||||
|
}
|
||||||
|
// #92: cambio sorgente: via i due frame, nessuna immagine di un'altra sorgente
|
||||||
|
function svuotaRiquadri() {
|
||||||
|
A.seqCal++; // una risposta calibrata ancora in volo non vale piu'
|
||||||
|
$("cornice").hidden = true;
|
||||||
|
$("vuoto").hidden = false;
|
||||||
|
messaggioCalibrata("Premere «Avvia anteprima» o «Scatta».");
|
||||||
|
}
|
||||||
|
// #92: raddrizza l'ULTIMO frame grezzo tenuto dal servizio (nessuna lettura nuova); chiamata dopo ogni anteprima, dopo Carica/Scollega e alla casella
|
||||||
|
async function aggiornaCalibrata(lato) {
|
||||||
|
var sorgente = $("sorgente").value;
|
||||||
|
if (!$("mostraCal").checked || !sorgente) return;
|
||||||
|
var n = ++A.seqCal;
|
||||||
|
try {
|
||||||
|
var r = await fetch("/camera/anteprima/calibrata", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ source: sorgente, lato_max: lato || A.lato })
|
||||||
|
});
|
||||||
|
if (n !== A.seqCal) return; // risposta superata da una piu' recente o da un cambio sorgente
|
||||||
|
if (!r.ok) { messaggioCalibrata(await dettaglio(r)); return; } // l'anteprima grezza continua
|
||||||
|
var nome = "";
|
||||||
|
try { nome = decodeURIComponent(r.headers.get("X-Calibrazione") || ""); } catch (e) { /* intestazione non decodificabile: nessun nome */ }
|
||||||
|
var blob = await r.blob();
|
||||||
|
if (n !== A.seqCal) return;
|
||||||
|
mostraCalibrata(blob, nome);
|
||||||
|
} catch (e) {
|
||||||
|
if (n === A.seqCal) messaggioCalibrata("Servizio non raggiungibile.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function aggiornaIndicatori(h) {
|
function aggiornaIndicatori(h) {
|
||||||
var nit = parseFloat(h.get("X-Nitidezza"));
|
var nit = parseFloat(h.get("X-Nitidezza"));
|
||||||
var sat = parseFloat(h.get("X-Saturati-Pct"));
|
var sat = parseFloat(h.get("X-Saturati-Pct"));
|
||||||
@@ -372,8 +505,7 @@ function aggiornaIndicatori(h) {
|
|||||||
var nw = parseInt(w, 10), na = parseInt(a, 10);
|
var nw = parseInt(w, 10), na = parseInt(a, 10);
|
||||||
if (!A.ris || A.ris.w !== nw || A.ris.h !== na) { A.ris = { w: nw, h: na }; segnaRisoluzioni(); }
|
if (!A.ris || A.ris.w !== nw || A.ris.h !== na) { A.ris = { w: nw, h: na }; segnaRisoluzioni(); }
|
||||||
}
|
}
|
||||||
var r = h.get("X-Raddrizzato");
|
$("raddrizzato").textContent = "frame grezzo"; // #92: l'indicatore viene sempre dal frame grezzo
|
||||||
$("raddrizzato").textContent = r === "si" ? "raddrizzata dalla calibrazione" : r === "no" ? "non raddrizzata" : " ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// una richiesta di anteprima; ritorna "ok" | "errore" | "fatale" (errore che non passa riprovando)
|
// una richiesta di anteprima; ritorna "ok" | "errore" | "fatale" (errore che non passa riprovando)
|
||||||
@@ -381,12 +513,13 @@ async function unaAnteprima(lato) {
|
|||||||
var sorgente = $("sorgente").value;
|
var sorgente = $("sorgente").value;
|
||||||
if (!sorgente) { dire("msgAnteprima", "Scegliere una sorgente.", "errore"); return "fatale"; }
|
if (!sorgente) { dire("msgAnteprima", "Scegliere una sorgente.", "errore"); return "fatale"; }
|
||||||
var g = A.gen;
|
var g = A.gen;
|
||||||
|
A.lato = lato;
|
||||||
A.inVolo = true;
|
A.inVolo = true;
|
||||||
try {
|
try {
|
||||||
var r = await fetch("/camera/anteprima", {
|
var r = await fetch("/camera/anteprima", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ source: sorgente, grezzo: $("grezzo").checked, lato_max: lato, timeout_ms: 5000 })
|
body: JSON.stringify({ source: sorgente, grezzo: true, lato_max: lato, timeout_ms: 5000 }) // #92: il riquadro «Immagine» e' sempre grezzo
|
||||||
});
|
});
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
var d = await dettaglio(r);
|
var d = await dettaglio(r);
|
||||||
@@ -398,6 +531,7 @@ async function unaAnteprima(lato) {
|
|||||||
mostraImmagine(blob);
|
mostraImmagine(blob);
|
||||||
aggiornaIndicatori(r.headers);
|
aggiornaIndicatori(r.headers);
|
||||||
dire("msgAnteprima", "");
|
dire("msgAnteprima", "");
|
||||||
|
await aggiornaCalibrata(lato); // #92: stesso frame, raddrizzato; resta in volo finche' non risponde
|
||||||
return "ok";
|
return "ok";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (g === A.gen) dire("msgAnteprima", "Servizio non raggiungibile.", "errore");
|
if (g === A.gen) dire("msgAnteprima", "Servizio non raggiungibile.", "errore");
|
||||||
@@ -447,7 +581,15 @@ $("avvia").onclick = avviaAnteprima;
|
|||||||
$("ferma").onclick = fermaAnteprima;
|
$("ferma").onclick = fermaAnteprima;
|
||||||
$("scatta").onclick = scatta;
|
$("scatta").onclick = scatta;
|
||||||
$("azzeraMax").onclick = function () { A.nitMax = 0; $("nitidezzaMax").textContent = "-"; $("barraNitidezza").style.width = "0"; };
|
$("azzeraMax").onclick = function () { A.nitMax = 0; $("nitidezzaMax").textContent = "-"; $("barraNitidezza").style.width = "0"; };
|
||||||
$("mirinoCasella").onchange = function () { $("mirino").hidden = !$("mirinoCasella").checked; };
|
// #92: il mirino vale per entrambi i riquadri; la casella «Mostra immagine calibrata» apre il riquadro e raddrizza il frame gia' tenuto
|
||||||
|
$("mirinoCasella").onchange = function () { $("mirino").hidden = $("mirinoCal").hidden = !$("mirinoCasella").checked; };
|
||||||
|
function mostraRiquadroCal() {
|
||||||
|
var si = $("mostraCal").checked;
|
||||||
|
$("riquadroCal").hidden = !si;
|
||||||
|
$("riquadri").className = "riquadri" + (si ? " doppio" : "");
|
||||||
|
if (si) aggiornaCalibrata();
|
||||||
|
}
|
||||||
|
$("mostraCal").onchange = mostraRiquadroCal;
|
||||||
|
|
||||||
// ---- Parametri camera (#87)
|
// ---- Parametri camera (#87)
|
||||||
|
|
||||||
@@ -676,6 +818,10 @@ function scegliTab(nome, salva) {
|
|||||||
$("tabCal").tabIndex = cal ? 0 : -1;
|
$("tabCal").tabIndex = cal ? 0 : -1;
|
||||||
$("pannelloAcq").hidden = cal;
|
$("pannelloAcq").hidden = cal;
|
||||||
$("pannelloCal").hidden = !cal;
|
$("pannelloCal").hidden = !cal;
|
||||||
|
// #92: nella tab Calibrazione il ciclo anteprima si sospende; tornando in Acquisizione riprende se era attivo
|
||||||
|
if (cal && A.attiva) { A.riprendi = true; fermaAnteprima(); }
|
||||||
|
if (!cal && A.riprendi) { A.riprendi = false; avviaAnteprima(); }
|
||||||
|
if (!cal && salva) aggiornaCalibrata(); // il riquadro calibrata riflette la calibrazione attiva di adesso
|
||||||
if (salva) { memoria("tmf.acq.tab", nome); if (cal) caricaCalibrazioni(); } // l'elenco si rilegge entrando
|
if (salva) { memoria("tmf.acq.tab", nome); if (cal) caricaCalibrazioni(); } // l'elenco si rilegge entrando
|
||||||
}
|
}
|
||||||
$("tabAcq").onclick = function () { scegliTab("acq", true); };
|
$("tabAcq").onclick = function () { scegliTab("acq", true); };
|
||||||
@@ -770,7 +916,7 @@ async function aggiornaStato() {
|
|||||||
stato.appendChild(titolo);
|
stato.appendChild(titolo);
|
||||||
stato.appendChild(t);
|
stato.appendChild(t);
|
||||||
stato.appendChild(quando);
|
stato.appendChild(quando);
|
||||||
$("statoSorgente").textContent = "calibrata: " + fmt(d.px_per_mm, 2) + " px/mm";
|
$("statoSorgente").textContent = "calibrata: " + (d.nome || "(senza nome)") + " · " + fmt(d.px_per_mm, 2) + " px/mm"; // #92
|
||||||
if (d.square_mm) $("quadrato").value = fmtCasella(d.square_mm); // #87: parte dal lato gia' usato
|
if (d.square_mm) $("quadrato").value = fmtCasella(d.square_mm); // #87: parte dal lato gia' usato
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -877,8 +1023,8 @@ async function caricaNome(nome) {
|
|||||||
if (sorgente !== $("sorgente").value) return;
|
if (sorgente !== $("sorgente").value) return;
|
||||||
if (!r.ok) { dire("msgElenco", await dettaglio(r), "errore"); return; }
|
if (!r.ok) { dire("msgElenco", await dettaglio(r), "errore"); return; }
|
||||||
dire("msgElenco", "Calibrazione «" + nome + "» caricata sulla sorgente.", "ok");
|
dire("msgElenco", "Calibrazione «" + nome + "» caricata sulla sorgente.", "ok");
|
||||||
$("raddrizzato").textContent = $("grezzo").checked ? "non raddrizzata" : "raddrizzata dalla calibrazione"; // il prossimo frame conferma
|
|
||||||
aggiornaStato();
|
aggiornaStato();
|
||||||
|
aggiornaCalibrata(); // #92: il riquadro calibrata usa subito la nuova calibrazione
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dire("msgElenco", "Servizio non raggiungibile.", "errore");
|
dire("msgElenco", "Servizio non raggiungibile.", "errore");
|
||||||
}
|
}
|
||||||
@@ -891,11 +1037,11 @@ async function scollega() {
|
|||||||
try {
|
try {
|
||||||
var r = await fetch("/camera/calibration?source=" + encodeURIComponent(sorgente), { method: "DELETE" });
|
var r = await fetch("/camera/calibration?source=" + encodeURIComponent(sorgente), { method: "DELETE" });
|
||||||
dire("msgElenco", r.ok ? "Calibrazione scollegata dalla sorgente (resta fra le salvate)." : await dettaglio(r), r.ok ? "ok" : "errore");
|
dire("msgElenco", r.ok ? "Calibrazione scollegata dalla sorgente (resta fra le salvate)." : await dettaglio(r), r.ok ? "ok" : "errore");
|
||||||
if (r.ok) $("raddrizzato").textContent = "non raddrizzata";
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dire("msgElenco", "Servizio non raggiungibile.", "errore");
|
dire("msgElenco", "Servizio non raggiungibile.", "errore");
|
||||||
}
|
}
|
||||||
aggiornaStato();
|
aggiornaStato();
|
||||||
|
aggiornaCalibrata(); // #92: senza calibrazione il riquadro mostra il 409 del servizio
|
||||||
}
|
}
|
||||||
|
|
||||||
// «Elimina» di una riga: conferma in pagina con un secondo clic entro 4 s (niente finestre del browser)
|
// «Elimina» di una riga: conferma in pagina con un secondo clic entro 4 s (niente finestre del browser)
|
||||||
@@ -927,25 +1073,18 @@ async function eliminaNome(nome, btn) {
|
|||||||
|
|
||||||
// #91: il calcolo non salva mai; il servizio tiene il risultato e ne da' il calcolo_id per «Salva»
|
// #91: il calcolo non salva mai; il servizio tiene il risultato e ne da' il calcolo_id per «Salva»
|
||||||
async function calcola() {
|
async function calcola() {
|
||||||
var daFile = $("origFile").checked;
|
var sorgente = $("sorgente").value; // #92: sempre la sorgente della tab Acquisizione, frame grezzo
|
||||||
var sorgente = $("sorgente").value;
|
if (!sorgente) { messaggio("Scegliere una sorgente nella tab Acquisizione.", "errore"); return; }
|
||||||
if (!daFile && !sorgente) { messaggio("Scegliere una sorgente nella tab Acquisizione.", "errore"); return; }
|
|
||||||
var lato = leggiNumero($("quadrato").value);
|
var lato = leggiNumero($("quadrato").value);
|
||||||
if (!(lato > 0)) { messaggio("Il lato dello scacco deve essere un numero maggiore di zero.", "errore"); return; }
|
if (!(lato > 0)) { messaggio("Il lato dello scacco deve essere un numero maggiore di zero.", "errore"); return; }
|
||||||
var f = new FormData();
|
var f = new FormData();
|
||||||
f.append("square_mm", String(lato)); // al servizio sempre con il punto
|
f.append("square_mm", String(lato)); // al servizio sempre con il punto
|
||||||
if (daFile) {
|
|
||||||
var file = $("file").files[0];
|
|
||||||
if (!file) { messaggio("Scegliere un file immagine.", "errore"); return; }
|
|
||||||
f.append("image", file);
|
|
||||||
} else {
|
|
||||||
f.append("source", sorgente);
|
f.append("source", sorgente);
|
||||||
}
|
fermaAnteprima(); // #92: in questa tab il ciclo e' gia' sospeso; qui si aspetta anche l'eventuale richiesta ancora in volo
|
||||||
var eraAttiva = !daFile && A.attiva; // #87: da sorgente l'anteprima si sospende e poi riprende
|
|
||||||
if (eraAttiva) fermaAnteprima();
|
|
||||||
messaggio("Calcolo in corso...", "");
|
messaggio("Calcolo in corso...", "");
|
||||||
["acquisisci", "salva"].forEach(function (i) { $(i).disabled = true; });
|
["acquisisci", "salva"].forEach(function (i) { $(i).disabled = true; });
|
||||||
try {
|
try {
|
||||||
|
while (A.inVolo) await pausa(50);
|
||||||
var r = await fetch("/camera/calibrate", { method: "POST", body: f });
|
var r = await fetch("/camera/calibrate", { method: "POST", body: f });
|
||||||
if (!r.ok) { messaggio(await dettaglio(r), "errore"); return; }
|
if (!r.ok) { messaggio(await dettaglio(r), "errore"); return; }
|
||||||
var d = await r.json();
|
var d = await r.json();
|
||||||
@@ -963,7 +1102,6 @@ async function calcola() {
|
|||||||
} finally {
|
} finally {
|
||||||
$("acquisisci").disabled = false;
|
$("acquisisci").disabled = false;
|
||||||
$("salva").disabled = false;
|
$("salva").disabled = false;
|
||||||
if (eraAttiva) avviaAnteprima();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,14 +1145,7 @@ async function salvaNome() {
|
|||||||
$("acquisisci").onclick = calcola;
|
$("acquisisci").onclick = calcola;
|
||||||
$("salva").onclick = salvaNome;
|
$("salva").onclick = salvaNome;
|
||||||
$("nomeCal").onkeydown = function (e) { if (e.key === "Enter") salvaNome(); };
|
$("nomeCal").onkeydown = function (e) { if (e.key === "Enter") salvaNome(); };
|
||||||
// #87: il pulsante del file e' in italiano, l'input nativo resta nascosto
|
$("sorgente").onchange = function () { dire("msgSorgente", ""); cambioSorgente(true); }; // #92
|
||||||
$("scegliFile").onclick = function () { $("file").click(); };
|
|
||||||
$("file").onchange = function () {
|
|
||||||
$("nomeFile").textContent = $("file").files[0] ? $("file").files[0].name : "nessuna immagine scelta";
|
|
||||||
if ($("file").files[0]) $("origFile").checked = true; // #91: scelto un file, l'origine diventa il file
|
|
||||||
};
|
|
||||||
|
|
||||||
$("sorgente").onchange = function () { cambioSorgente(true); };
|
|
||||||
scegliTab(memoria("tmf.acq.tab") === "cal" ? "cal" : "acq", false); // #91: tab ricordata
|
scegliTab(memoria("tmf.acq.tab") === "cal" ? "cal" : "acq", false); // #91: tab ricordata
|
||||||
caricaSorgenti();
|
caricaSorgenti();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ def test_offline_e_senza_finestre_del_browser():
|
|||||||
|
|
||||||
def test_id_principali():
|
def test_id_principali():
|
||||||
ids = set(re.findall(r'id="([^"]+)"', PAGINA))
|
ids = set(re.findall(r'id="([^"]+)"', PAGINA))
|
||||||
attesi = {"sorgente", "avvia", "ferma", "scatta", "grezzo", "mirinoCasella", "immagine",
|
attesi = {"sorgente", "avvia", "ferma", "scatta", "mirinoCasella", "immagine",
|
||||||
"nitidezza", "nitidezzaMax", "saturati", "media", "dimensioni",
|
"nitidezza", "nitidezzaMax", "saturati", "media", "dimensioni",
|
||||||
"parametri", "salvaParametri", "quadrato", "acquisisci", "scegliFile", "file",
|
"parametri", "salvaParametri", "quadrato", "acquisisci", "caricaImmagine", "fileImmagine",
|
||||||
"salva", "stato", "statoSorgente"} # #91: carica/elimina sono ora per riga, nell'elenco
|
"mostraCal", "salva", "stato", "statoSorgente"} # #91: carica/elimina sono ora per riga; #92: file e calibrata nell'Acquisizione
|
||||||
assert attesi <= ids, attesi - ids
|
assert attesi <= ids, attesi - ids
|
||||||
# ogni id usato dal JS con $("...") esiste nel markup
|
# ogni id usato dal JS con $("...") esiste nel markup
|
||||||
usati = set(re.findall(r'\$\("([^"]+)"\)', PAGINA))
|
usati = set(re.findall(r'\$\("([^"]+)"\)', PAGINA))
|
||||||
@@ -41,8 +41,8 @@ def test_id_principali():
|
|||||||
|
|
||||||
def test_sezioni_e_testi_italiani():
|
def test_sezioni_e_testi_italiani():
|
||||||
for testo in ("Sorgente", "Anteprima", "Parametri camera", "Calibrazione",
|
for testo in ("Sorgente", "Anteprima", "Parametri camera", "Calibrazione",
|
||||||
"Salva come predefiniti", "Scegli immagine...", "Questa sorgente non ha parametri regolabili",
|
"Salva come predefiniti", "Carica immagine da file...", "Questa sorgente non ha parametri regolabili",
|
||||||
"Immagine grezza (senza correzione)"):
|
"Mostra immagine calibrata"): # #92
|
||||||
assert testo in PAGINA, testo
|
assert testo in PAGINA, testo
|
||||||
assert "it-IT" in PAGINA # numeri con la virgola
|
assert "it-IT" in PAGINA # numeri con la virgola
|
||||||
assert "Choose File" not in PAGINA
|
assert "Choose File" not in PAGINA
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Test della pagina a due tab (#91): contenuto statico di PAGINA, senza servizio."""
|
"""Test della pagina a due tab (#91, #92): contenuto statico di PAGINA, senza servizio."""
|
||||||
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import re
|
import re
|
||||||
@@ -20,37 +20,58 @@ def test_due_tab_con_ruolo_e_selezione():
|
|||||||
assert '"tmf.acq.tab"' in PAGINA # tab scelta ricordata (via memoria(), protetta)
|
assert '"tmf.acq.tab"' in PAGINA # tab scelta ricordata (via memoria(), protetta)
|
||||||
|
|
||||||
|
|
||||||
def test_anteprima_fuori_dai_pannelli():
|
def test_sorgente_in_cima_poi_riquadri():
|
||||||
# l'anteprima sta nella colonna sinistra, prima dei due pannelli: visibile in entrambe le tab
|
# #92: Sorgente, anteprima e parametri stanno nel pannello Acquisizione; Sorgente prima dei riquadri
|
||||||
i_ant = PAGINA.index('id="sezioneAnteprima"')
|
|
||||||
i_acq = PAGINA.index('id="pannelloAcq"')
|
i_acq = PAGINA.index('id="pannelloAcq"')
|
||||||
i_cal = PAGINA.index('id="pannelloCal"')
|
i_cal = PAGINA.index('id="pannelloCal"')
|
||||||
assert i_ant < i_acq < i_cal
|
i_sorg = PAGINA.index('id="sezioneSorgente"')
|
||||||
# Sorgente e Parametri nel pannello Acquisizione, la calibrazione in quello Calibrazione
|
i_ant = PAGINA.index('id="sezioneAnteprima"')
|
||||||
assert i_acq < PAGINA.index('id="sezioneSorgente"') < PAGINA.index('id="sezioneParametri"') < i_cal
|
i_riq = PAGINA.index('id="riquadri"')
|
||||||
|
assert i_acq < i_sorg < i_ant < i_riq < PAGINA.index('id="sezioneParametri"') < i_cal
|
||||||
assert PAGINA.index('id="sezioneCalibrazione"') > i_cal
|
assert PAGINA.index('id="sezioneCalibrazione"') > i_cal
|
||||||
|
# barra: casella calibrata e mirino; la vecchia casella «Immagine grezza» non c'e' piu'
|
||||||
|
assert 'id="mostraCal"' in PAGINA and "Mostra immagine calibrata" in PAGINA
|
||||||
|
assert 'id="grezzo"' not in PAGINA and "Immagine grezza (senza correzione)" not in PAGINA
|
||||||
|
# riquadri: «Immagine» sempre visibile, «Calibrata» nascosta finche' la casella e' spenta
|
||||||
|
assert re.search(r'<div class="riquadro" id="riquadroCal" hidden>', PAGINA)
|
||||||
|
assert "grezzo: true" in PAGINA
|
||||||
|
|
||||||
|
|
||||||
def test_id_del_pannello_calibrazione():
|
def test_id_del_pannello_calibrazione():
|
||||||
ids = set(re.findall(r'id="([^"]+)"', PAGINA))
|
ids = set(re.findall(r'id="([^"]+)"', PAGINA))
|
||||||
attesi = {"calSorgente", "origSorgente", "origFile", "scegliFile", "file", "quadrato", "acquisisci",
|
attesi = {"calSorgente", "quadrato", "acquisisci", "nomeCal", "salva", "msgSalva", "calibrazioni",
|
||||||
"nomeCal", "salva", "msgSalva", "calibrazioni", "msgElenco", "stato", "risultato", "overlay"}
|
"msgElenco", "stato", "risultato", "overlay",
|
||||||
|
"caricaImmagine", "fileImmagine", "rimuoviImmagine", "riquadroCal", "immagineCal", "mirinoCal"}
|
||||||
assert attesi <= ids, attesi - ids
|
assert attesi <= ids, attesi - ids
|
||||||
usati = set(re.findall(r'\$\("([^"]+)"\)', PAGINA))
|
usati = set(re.findall(r'\$\("([^"]+)"\)', PAGINA))
|
||||||
assert usati <= ids, usati - ids
|
assert usati <= ids, usati - ids
|
||||||
assert len(re.findall(r'id="([^"]+)"', PAGINA)) == len(ids) # nessun id doppio
|
assert len(re.findall(r'id="([^"]+)"', PAGINA)) == len(ids) # nessun id doppio
|
||||||
|
|
||||||
|
|
||||||
|
def test_calibrazione_senza_file_ne_radio():
|
||||||
|
# #92: l'immagine da file e' una sorgente di acquisizione, non un'opzione della calibrazione
|
||||||
|
for sparito in ("origFile", "origSorgente", "scegliFile", "nomeFile", 'type="radio"', "Immagine da file", "Sorgente selezionata"):
|
||||||
|
assert sparito not in PAGINA, sparito
|
||||||
|
# l'unico input file e' nella sezione Sorgente, in cima alla tab Acquisizione
|
||||||
|
assert PAGINA.count('type="file"') == 1
|
||||||
|
assert PAGINA.index('type="file"') < PAGINA.index('id="pannelloCal"')
|
||||||
|
assert "immagine grezza dalla tab Acquisizione" in PAGINA
|
||||||
|
# il calcolo manda solo la sorgente (e il lato), mai il file
|
||||||
|
assert 'f.append("source", sorgente)' in PAGINA and 'f.append("image"' not in PAGINA.split("async function calcola")[1].split("async function salvaNome")[0]
|
||||||
|
|
||||||
|
|
||||||
def test_testi_del_pannello():
|
def test_testi_del_pannello():
|
||||||
for testo in ("Sorgente selezionata", "Immagine da file", "Scegli immagine...", "Calcola",
|
for testo in ("Camera o immagine", "Carica immagine da file...", "Rimuovi immagine", "Calcola",
|
||||||
"Calibrazioni salvate", "si cambia nella tab Acquisizione", "risoluzione diversa",
|
"Calibrazioni salvate", "risoluzione diversa", "Calibrata",
|
||||||
"Carica", "Elimina", "Scollega", "Sovrascrivi «", "Clic ancora", "Nome"):
|
"Carica", "Elimina", "Scollega", "Sovrascrivi «", "Clic ancora", "Nome",
|
||||||
|
"calibrata: ", "non calibrata"):
|
||||||
assert testo in PAGINA, testo
|
assert testo in PAGINA, testo
|
||||||
|
|
||||||
|
|
||||||
def test_rotte_del_contratto():
|
def test_rotte_del_contratto():
|
||||||
for rotta in ("/camera/calibrazioni", "/camera/calibrazione-attiva", "/camera/calibrate",
|
for rotta in ("/camera/calibrazioni", "/camera/calibrazione-attiva", "/camera/calibrate",
|
||||||
"/camera/calibration?source="):
|
"/camera/calibration?source=", "/camera/immagini", "/camera/immagini?nome=",
|
||||||
|
"/camera/anteprima/calibrata"): # #92
|
||||||
assert '"' + rotta in PAGINA, rotta
|
assert '"' + rotta in PAGINA, rotta
|
||||||
for parola in ("calcolo_id", "sovrascrivi", "method: \"PUT\"", "method: \"DELETE\"", "X-Larghezza", "X-Altezza"):
|
for parola in ("calcolo_id", "sovrascrivi", "method: \"PUT\"", "method: \"DELETE\"", "X-Larghezza", "X-Altezza"):
|
||||||
assert parola in PAGINA, parola
|
assert parola in PAGINA, parola
|
||||||
@@ -65,7 +86,7 @@ def test_nessuna_finestra_del_browser_e_offline():
|
|||||||
|
|
||||||
def test_acquisizione_di_87_90_conservata():
|
def test_acquisizione_di_87_90_conservata():
|
||||||
# anteprima, indicatori, mirino, grezzo, parametri camera e ciclo a 100 ms restano
|
# anteprima, indicatori, mirino, grezzo, parametri camera e ciclo a 100 ms restano
|
||||||
for parola in ("Parametri camera", "Immagine grezza (senza correzione)", "Mirino centrale",
|
for parola in ("Parametri camera", "Mirino centrale",
|
||||||
"Math.max(100 - (Date.now() - t0), 0)", "/camera/anteprima", "/camera/parametri"):
|
"Math.max(100 - (Date.now() - t0), 0)", "/camera/anteprima", "/camera/parametri"):
|
||||||
assert parola in PAGINA, parola
|
assert parola in PAGINA, parola
|
||||||
|
|
||||||
@@ -75,3 +96,19 @@ def test_pagina_stretta():
|
|||||||
# #91: elenco a schede, niente tabella larga ne' scorrimento orizzontale
|
# #91: elenco a schede, niente tabella larga ne' scorrimento orizzontale
|
||||||
assert 'id="calibrazioni" class="elenco"' in PAGINA and "overflow-x" not in PAGINA
|
assert 'id="calibrazioni" class="elenco"' in PAGINA and "overflow-x" not in PAGINA
|
||||||
assert "Lettura dei parametri..." in PAGINA
|
assert "Lettura dei parametri..." in PAGINA
|
||||||
|
|
||||||
|
|
||||||
|
def test_ciclo_sospeso_nella_tab_calibrazione():
|
||||||
|
# #92: entrando in Calibrazione il ciclo si ferma e poi riprende; Calcola aspetta le richieste in volo
|
||||||
|
assert "A.riprendi = true; fermaAnteprima();" in PAGINA
|
||||||
|
assert "if (!cal && A.riprendi)" in PAGINA
|
||||||
|
calcola = PAGINA.split("async function calcola")[1].split("async function salvaNome")[0]
|
||||||
|
assert "fermaAnteprima();" in calcola and "while (A.inVolo)" in calcola
|
||||||
|
|
||||||
|
|
||||||
|
def test_calibrata_dopo_anteprima_e_dopo_carica():
|
||||||
|
# #92: la calibrata segue ogni anteprima grezza riuscita e si aggiorna dopo Carica/Scollega; 404/409 al posto dell'immagine
|
||||||
|
assert "await aggiornaCalibrata(lato);" in PAGINA
|
||||||
|
assert PAGINA.count("aggiornaCalibrata();") >= 3
|
||||||
|
assert "messaggioCalibrata(await dettaglio(r))" in PAGINA
|
||||||
|
assert 'decodeURIComponent(r.headers.get("X-Calibrazione")' in PAGINA
|
||||||
|
|||||||
Reference in New Issue
Block a user