| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- <!doctype html>
- <html lang="et">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <title>Värvikalibratsioon</title>
- <style>
- :root{--bg:#12151a;--card:#1b2028;--fg:#e8ecf1;--muted:#93a0b4;--acc:#4da3ff;--ok:#3ddc97;--warn:#ffb454;--err:#ff6b6b}
- *{box-sizing:border-box}
- body{margin:0;font:15px/1.45 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--fg)}
- header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #2a3140}
- header a{color:var(--acc);text-decoration:none}
- main{max-width:720px;margin:0 auto;padding:18px}
- .card{background:var(--card);border:1px solid #2a3140;border-radius:12px;padding:16px;margin:12px 0}
- h1{font-size:20px;margin:0}
- h2{font-size:16px;margin:0 0 8px}
- p{margin:6px 0}
- .muted{color:var(--muted)}
- .row{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
- button{font:inherit;padding:10px 16px;border-radius:9px;border:1px solid #33405a;background:#243044;color:var(--fg);cursor:pointer}
- button.primary{background:var(--acc);border-color:var(--acc);color:#08111f;font-weight:600}
- button:disabled{opacity:.45;cursor:not-allowed}
- .badge{display:inline-block;padding:2px 8px;border-radius:99px;background:#243044;color:var(--muted);font-size:12px}
- .badge.ok{background:#12362a;color:var(--ok)}
- .badge.err{background:#3a1717;color:var(--err)}
- .bar{height:8px;background:#243044;border-radius:99px;overflow:hidden;margin-top:10px}
- .bar>i{display:block;height:100%;width:0;background:var(--acc);transition:width .25s}
- #patch{position:fixed;inset:0;display:none;z-index:10}
- #patch .bar2{position:absolute;left:0;right:0;bottom:0;display:flex;gap:10px;align-items:center;justify-content:center;padding:14px;background:rgba(0,0,0,.55);backdrop-filter:blur(4px)}
- #patch .bar2 button{background:rgba(255,255,255,.92);color:#111;border:0;font-weight:600}
- table{width:100%;border-collapse:collapse;font-size:13px}
- td,th{padding:4px 6px;border-bottom:1px solid #2a3140;text-align:left}
- code{background:#0e1218;padding:1px 5px;border-radius:5px}
- </style>
- </head>
- <body>
- <header><h1>🎨 Värvikalibratsioon</h1><a href="/">← Nutikodu</a></header>
- <main>
- <div id="msg" class="card muted">Laen…</div>
- <section id="intro" class="card" hidden>
- <h2>Kuidas käib</h2>
- <ol>
- <li><b>Tume:</b> kata andur täiesti (must riie/karp; LED on plaadilt eemaldatud) → “Mõõda tume”. Nullväärtused on OK.</li>
- <li><b>Valge:</b> näita ekraanil täisvalget (see leht teeb ise) samal kaugusel ja nurga all, kus hakkadki mõõtma → “Mõõda valge”.</li>
- <li><b>24 plaastrit:</b> iga plaaster kuvatakse täisekraanil; vajuta “Mõõda” ja oota ~10–15 s.</li>
- <li><b>Sobita:</b> arvutan 3×8 maatriksi ja ΔE00 vead.</li>
- </ol>
- <p class="muted">Tähtis: püsi kogu seeria jooksul samas valgustuses, kauguses ja nurgas. Ekraani heledus maksimumi, auto-heledus ja sinise valguse filter välja.</p>
- <div class="row"><button class="primary" id="start">Alusta</button></div>
- </section>
- <section id="stage" class="card" hidden>
- <h2 id="stage-title">—</h2>
- <p id="stage-desc" class="muted"></p>
- <div id="stage-status"></div>
- <div class="bar"><i id="prog"></i></div>
- <div class="row" id="stage-actions"></div>
- </section>
- <section id="result" class="card" hidden>
- <h2>Tulemus</h2>
- <div id="result-body"></div>
- </section>
- </main>
- <div id="patch"><div class="bar2"><button id="measure">Mõõda see plaat</button><span id="patch-label" style="color:#fff;font:600 14px system-ui"></span></div></div>
- <script>
- const PATCHES = [
- ["tumedanahk","735244"],["heledanahk","C29682"],["taevasinine","627A9D"],
- ["lehestik","576C43"],["sinine lill","8580B1"],["rohekassinine","67BDAA"],
- ["oranž","D67E2C"],["sinakaslilla","505BA6"],["mõõdukas punane","C15A63"],
- ["lilla","5E3C6C"],["kollakasroheline","9DBC40"],["oranžkollane","E0A32E"],
- ["sinine","383D96"],["roheline","469449"],["punane","AF363C"],
- ["kollane","E7C71F"],["magenta","BB5695"],["tsüaan","0885A1"],
- ["valge","F3F3F2"],["hall 8","C8C8C8"],["hall 6.5","A0A0A0"],
- ["hall 5","7A7A79"],["hall 3.5","555555"],["must","343434"]
- ];
- const $ = (id) => document.getElementById(id);
- let st = {dark:null, white:null, idx:0, samples:3};
- async function api(path, opts={}) {
- const r = await fetch(path, {credentials:"same-origin", headers:{"Content-Type":"application/json"}, ...opts});
- let data = null;
- try { data = await r.json(); } catch(e) {}
- if (!r.ok) throw new Error((data && (data.detail||data.error)) || ("HTTP "+r.status));
- return data;
- }
- function say(text, kind) {
- const m = $("msg");
- m.hidden = false;
- m.className = "card " + (kind || "muted");
- m.textContent = text;
- }
- async function capture() {
- return await api("/api/varv/capture", {method:"POST", body: JSON.stringify({n: st.samples, timeout: 45})});
- }
- function progress(p) { $("prog").style.width = Math.round(p*100) + "%"; }
- async function init() {
- try {
- const me = await api("/api/me");
- if (!me.authed) { say("Vaja sisse logida. Mine /login ja tule tagasi.", "err"); return; }
- } catch(e) { say("Server ei vasta: " + e.message, "err"); return; }
- $("msg").hidden = true;
- $("intro").hidden = false;
- $("start").onclick = beginDark;
- }
- async function beginDark() {
- $("intro").hidden = true;
- $("stage").hidden = false;
- $("stage-title").textContent = "1/3 · Tume referents";
- $("stage-desc").textContent = "Kata andur täiesti (must riie või karp). Plaadi roheline toite-LED on eemaldatud — täielik pimedus ja nullväärtused on ootuspärased.";
- $("stage-status").textContent = "";
- $("stage-actions").innerHTML = '<button class="primary" id="go">Mõõda tume</button>';
- $("go").onclick = async () => {
- $("go").disabled = true;
- try {
- const r = await capture();
- $("stage-status").innerHTML = '<span class="badge ' + (r.stable?"ok":"err") + '">clear ' + r.clear + ' · CV ' + (r.cv*100).toFixed(1) + '%</span>';
- const res = await api("/api/varv/calib", {method:"POST", body: JSON.stringify({kind:"dark", last:r.n})});
- st.dark = res.reference;
- say("Tume salvestatud.", "ok");
- beginWhite();
- } catch(e) { $("go").disabled = false; say("Viga: " + e.message, "err"); }
- };
- }
- async function beginWhite() {
- $("stage-title").textContent = "2/3 · Valge referents";
- $("stage-desc").textContent = "Näita ekraanil valget pinda (nt ava see leht täisekraanil ja suuna andur sellele) samas valgustuses ja geomeetrias.";
- $("stage-status").textContent = "";
- $("stage-actions").innerHTML = '<button class="primary" id="go">Mõõda valge</button>';
- $("go").onclick = async () => {
- $("go").disabled = true;
- try {
- const r = await capture();
- $("stage-status").innerHTML = '<span class="badge ' + (r.stable?"ok":"err") + '">clear ' + r.clear + ' · CV ' + (r.cv*100).toFixed(1) + '%</span>';
- const res = await api("/api/varv/calib", {method:"POST", body: JSON.stringify({kind:"white", last:r.n})});
- st.white = res.reference;
- say("Valge salvestatud.", "ok");
- beginPatches();
- } catch(e) { $("go").disabled = false; say("Viga: " + e.message, "err"); }
- };
- }
- function beginPatches() {
- st.idx = 0;
- $("stage-title").textContent = "3/3 · Värvikaart";
- showPatch();
- }
- function showPatch() {
- const [name, hex] = PATCHES[st.idx];
- progress(st.idx / PATCHES.length);
- $("stage").hidden = true;
- $("patch").style.display = "block";
- $("patch").style.background = "#" + hex;
- const r = parseInt(hex.slice(0,2),16), g = parseInt(hex.slice(2,4),16), b = parseInt(hex.slice(4,6),16);
- const lum = (0.299*r + 0.587*g + 0.114*b);
- $("patch-label").textContent = (st.idx+1) + "/24 · " + name;
- $("patch-label").style.color = lum > 140 ? "#111" : "#fff";
- $("measure").onclick = measurePatch;
- }
- async function measurePatch() {
- $("measure").disabled = true;
- $("patch-label").textContent = "Mõõdan… (~10–15 s)";
- try {
- const r = await capture();
- if (r.saturated) throw new Error("Küllastunud — vähenda ekraani heledust või suurenda kaugust.");
- if (!r.stable) throw new Error("Mõõtmine pole stabiilne (CV " + (r.cv*100).toFixed(1) + "%) — oota ja proovi uuesti.");
- const [name, hex] = PATCHES[st.idx];
- await api("/api/varv/ref", {method:"POST", body: JSON.stringify({name:"wiz"+String(st.idx+1).padStart(2,"0"), hex, last:r.n})});
- st.idx++;
- if (st.idx >= PATCHES.length) { finish(); return; }
- showPatch();
- } catch(e) {
- $("patch-label").textContent = "Viga: " + e.message;
- } finally {
- $("measure").disabled = false;
- }
- }
- async function finish() {
- $("patch").style.display = "none";
- $("stage").hidden = false;
- $("stage-title").textContent = "Sobitan maatriksit…";
- $("stage-desc").textContent = "24 referentsi on salvestatud. Arvutan 3×8 maatriksi ja ΔE00.";
- $("stage-actions").innerHTML = "";
- progress(1);
- try {
- const r = await api("/api/varv/ccm/fit?prefix=wiz", {method:"POST"});
- $("result").hidden = false;
- const s = r.de00_loo, i = r.de00_in_sample;
- $("result-body").innerHTML =
- '<p><b>Referentse:</b> ' + r.n_refs + '<br>' +
- '<b>ΔE00 (in-sample):</b> keskmine ' + i.mean + ', mediaan ' + i.median + ', max ' + i.max + '<br>' +
- '<b>ΔE00 (LOO):</b> keskmine ' + s.mean + ', mediaan ' + s.median + ', max ' + s.max + '</p>' +
- '<p class="muted">LOO (leave-one-out) on aus hinnang uutele värvidele. Alla ~3 on hea, 3–6 rahuldav, üle 6 nõrk.</p>' +
- '<table><tr><th>Plaaster</th><th>Siht</th><th>Tulemus</th><th>ΔE00</th></tr>' +
- r.preview.map(p => '<tr><td>'+p.name+'</td><td><code>'+p.target+'</code></td><td><code>'+p.pred+'</code></td><td>'+p.de00+'</td></tr>').join('') +
- '</table>';
- $("stage-title").textContent = "Valmis";
- $("stage-desc").textContent = "Maatriks on salvestatud. Mine Värv-vahelehele ja lülita sisse kalibreeritud vaade.";
- } catch(e) {
- $("stage-title").textContent = "Sobitamine ebaõnnestus";
- $("stage-desc").textContent = e.message;
- }
- }
- init();
- </script>
- </body>
- </html>
|