| "use strict"; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| (function () { |
| const PLOT = window.Plotly; |
| const $ = (id) => document.getElementById(id); |
|
|
| const S = { |
| study: null, trials: [], filtered: [], |
| selected: null, codec: null, built: false, fsKind: null, |
| sort: { key: "mse", dir: 1 }, |
| show: { pbcAll: true, pbcPareto: true, presets: true, jpeg: false, jp2: false, avif: false, webp: false, jxl: false, downsample: false, png: false }, |
| dyn: [], mpMin: null, mpMax: null, logx: true, logy: false, psnr: false, |
| fnum: { mse: NaN, bpp: NaN, sp: NaN }, |
| }; |
|
|
| const COLORS = { |
| gray: "#5c3839", pareto: "#ff3b41", sel: "#ffce2e", base: "#ff280c", |
| jpeg: "#7d8cff", jp2: "#4fa3ff", avif: "#34d39a", webp: "#54c0ff", jxl: "#c084fc", downsample: "#e6e6e6", png: "#ef19ef", |
| }; |
| const TOGGLES = [["pbcAll", "PBC All"], ["pbcPareto", "PBC Pareto"], ["presets", "PBC Presets"], ["jpeg", "JPEG"], ["jp2", "JPEG2000"], ["webp", "WebP"], ["jxl", "JXL"], ["avif", "AVIF"], ["downsample", "Downsample"], ["png", "PNG"]]; |
|
|
| |
| const P2D = { |
| qc: { x: "bpp", y: "mse", xt: "bpp", yt: "MSE", note: "↙ lower-left is best", title: "MSE vs Compression" }, |
| qs: { x: "speed", y: "mse", xt: "seconds", yt: "MSE", note: "↙ lower-left is best", title: "MSE vs Speed" }, |
| cs: { x: "speed", y: "bpp", xt: "seconds", yt: "bpp", note: "↙ lower-left is best", title: "Compression vs Speed" }, |
| qcs: { x: "speed", y: "eff", xt: "seconds", yt: "efficiency 1/(bpp·MSE)", note: "↖ upper-left is best", title: "Efficiency vs Speed" }, |
| }; |
|
|
| const FS_STYLE = ` |
| .sw-plot-box{position:relative;} |
| .sw-fs-btn{position:absolute;top:10px;right:10px;z-index:5;background:rgba(18,18,20,.72); |
| border:1px solid var(--border2);color:var(--text);border-radius:7px;width:28px;height:28px; |
| cursor:pointer;font-size:15px;line-height:1;display:flex;align-items:center;justify-content:center;transition:.15s;} |
| .sw-fs-btn:hover{border-color:var(--red-dim);color:#fff;background:rgba(18,18,20,.9);} |
| #sw-fs{position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,.82);backdrop-filter:blur(6px); |
| display:flex;padding:24px;animation:swfs .14s ease;} |
| #sw-fs[hidden]{display:none;} |
| @keyframes swfs{from{opacity:0}to{opacity:1}} |
| .sw-fs-inner{margin:auto;width:96vw;height:92vh;background:var(--surface);border:1px solid var(--border); |
| border-radius:16px;display:flex;flex-direction:column;padding:14px 16px;box-shadow:0 24px 70px rgba(0,0,0,.55);} |
| .sw-fs-bar{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;} |
| .sw-fs-bar span{font-size:13px;font-weight:600;color:var(--text);} |
| #sw-fs-plot{flex:1;min-height:0;}`; |
|
|
| const fmt = (v, d = 4) => (v === null || v === undefined || isNaN(v)) ? "—" : (+v).toFixed(d); |
| const efficiency = (t) => (t.bpp > 0 && t.mse > 0) ? 1 / (t.bpp * t.mse) : 0; |
| const baseLabel = (t) => t.preset ? `${t.preset} preset` : (t.codec ? `${t.codec} q${t.q}` : ""); |
|
|
| |
| function build() { |
| const st = document.createElement("style"); |
| st.textContent = FS_STYLE; |
| document.head.appendChild(st); |
|
|
| $("view-sweep").innerHTML = ` |
| <div class="sw-wrap"> |
| <div class="sw-card"> |
| <h3>Tuning database<span class="sub">pbc3 · live on the Space</span></h3> |
| <div class="sw-row" style="gap:16px"> |
| <button id="sw-load" class="sw-btn primary">Refresh</button> |
| <div class="sw-f"><label>MP ≥</label><input id="f-mpmin" class="sw-input" type="number" step="0.1" placeholder="any"></div> |
| <div class="sw-f"><label>MP ≤</label><input id="f-mpmax" class="sw-input" type="number" step="0.1" placeholder="any"></div> |
| <button id="sw-mpreset" class="sw-btn sm">Reset MP</button> |
| </div> |
| <div id="sw-msg" class="sw-muted" style="margin-top:8px"></div> |
| </div> |
| |
| <div id="sw-summary-card" class="sw-card" hidden> |
| <h3>Summary</h3> |
| <div id="sw-warn"></div> |
| <div id="sw-summary" class="sw-summary"></div> |
| </div> |
| |
| <div id="sw-main" hidden> |
| <div class="sw-card"> |
| <h3>Filters<span class="sub">applied to charts, table & baselines</span></h3> |
| <div class="sw-row" style="gap:16px"> |
| <div class="sw-f"><label>MSE ≤</label><input id="f-msemax" class="sw-input" type="number" step="1" placeholder="any"></div> |
| <div class="sw-f"><label>BPP ≤</label><input id="f-bppmax" class="sw-input" type="number" step="0.01" placeholder="any"></div> |
| <div class="sw-f"><label>seconds ≤</label><input id="f-spmax" class="sw-input" type="number" step="0.1" placeholder="any"></div> |
| </div> |
| <div id="sw-dyn" style="margin-top:12px;display:flex;flex-direction:column;gap:8px"></div> |
| <div class="sw-row" style="margin-top:10px"> |
| <select id="sw-addfilter" class="sw-select sm"><option value="">+ Add parameter filter…</option></select> |
| </div> |
| </div> |
| |
| <div class="sw-card" style="margin-top:18px"> |
| <h3>Tradeoffs<span class="sub">red = selected · ◆ = baseline</span></h3> |
| <div class="sw-row" id="sw-toggles" style="margin-bottom:10px"></div> |
| <div class="sw-row" id="sw-scale" style="margin-bottom:10px"> |
| <span class="sw-muted" style="margin-right:4px">Scale:</span> |
| <label class="sw-muted" style="text-transform:none;letter-spacing:0"><input type="checkbox" id="sw-logx" checked> log x</label> |
| <label class="sw-muted" style="text-transform:none;letter-spacing:0"><input type="checkbox" id="sw-logy"> log y</label> |
| <label class="sw-muted" style="text-transform:none;letter-spacing:0"><input type="checkbox" id="sw-psnr"> PSNR</label> |
| <button id="sw-deselect" class="sw-btn sm" style="margin-left:auto">Deselect trial</button> |
| </div> |
| <div class="sw-plots"> |
| <div class="sw-plot-box"><button class="sw-fs-btn" data-fs="qc" title="Fullscreen">⤢</button><div id="sw-p-qc" class="sw-plot"></div></div> |
| <div class="sw-plot-box"><button class="sw-fs-btn" data-fs="qs" title="Fullscreen">⤢</button><div id="sw-p-qs" class="sw-plot"></div></div> |
| <div class="sw-plot-box"><button class="sw-fs-btn" data-fs="cs" title="Fullscreen">⤢</button><div id="sw-p-cs" class="sw-plot"></div></div> |
| <div class="sw-plot-box"><button class="sw-fs-btn" data-fs="qcs" title="Fullscreen">⤢</button><div id="sw-p-qcs" class="sw-plot"></div></div> |
| </div> |
| <div id="sw-readout" class="sw-muted" style="margin-top:8px"></div> |
| </div> |
| |
| <div class="sw-card" style="margin-top:18px"> |
| <h3>3D tradeoff</h3> |
| <div class="sw-row" style="margin-bottom:8px"> |
| <label class="sw-muted" style="text-transform:none;letter-spacing:0"><input type="checkbox" id="sw-3d-show"> Show 3D Graph</label> |
| <span class="sw-muted">color by</span> |
| <select id="sw-3dcolor" class="sw-select sm"> |
| <option value="mse">mse</option> |
| <option value="seconds">seconds</option> |
| <option value="bpp">bpp</option> |
| <option value="pareto">pareto</option> |
| </select> |
| <button class="sw-fs-btn" data-fs="3d" title="Fullscreen" style="position:static;margin-left:auto">⤢</button> |
| </div> |
| <div id="sw-3d-wrap" style="height:460px;overflow:hidden;position:relative;border-radius:10px" hidden> |
| <div id="sw-3d" style="width:100%;height:100%"></div> |
| </div> |
| </div> |
| |
| <div class="sw-card" style="margin-top:18px"> |
| <h3>Trials<span class="sub" id="sw-tcount"></span></h3> |
| <div class="sw-table-wrap"><table class="sw-table" id="sw-table"></table></div> |
| </div> |
| |
| <div class="sw-card" style="margin-top:18px"> |
| <h3>Selected trial<span class="sub" id="sw-sel-sub">none</span></h3> |
| <div class="sw-sel-grid"> |
| <div> |
| <div id="sw-sel-metrics" class="sw-kv"></div> |
| <div class="sw-copybar" id="sw-copybar"></div> |
| </div> |
| <div> |
| <div class="sw-muted" style="margin-bottom:6px">Parameters</div> |
| <div id="sw-sel-params" class="sw-params"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div id="sw-fs" hidden> |
| <div class="sw-fs-inner"> |
| <div class="sw-fs-bar"><span id="sw-fs-title"></span><button id="sw-fs-close" class="sw-btn sm">Close ✕</button></div> |
| <div id="sw-fs-plot"></div> |
| </div> |
| </div> |
| </div>`; |
|
|
| $("sw-load").onclick = load; |
| ["f-mpmin", "f-mpmax"].forEach((id) => $(id).addEventListener("change", () => { |
| S.mpMin = $("f-mpmin").value === "" ? null : +$("f-mpmin").value; |
| S.mpMax = $("f-mpmax").value === "" ? null : +$("f-mpmax").value; |
| load(); |
| })); |
| $("sw-mpreset").onclick = () => { |
| $("f-mpmin").value = ""; $("f-mpmax").value = ""; |
| S.mpMin = null; S.mpMax = null; |
| load(); |
| }; |
| ["f-msemax", "f-bppmax", "f-spmax"].forEach((id) => $(id).addEventListener("input", applyFilters)); |
| $("sw-addfilter").onchange = (e) => { if (e.target.value) { addDynFilter(e.target.value); e.target.value = ""; } }; |
|
|
| $("sw-logx").onchange = (e) => { S.logx = e.target.checked; render2d(); }; |
| $("sw-logy").onchange = (e) => { S.logy = e.target.checked; render2d(); }; |
| $("sw-psnr").onchange = (e) => { S.psnr = e.target.checked; render2d(); }; |
| $("sw-deselect").onclick = () => select(null); |
|
|
| $("sw-3d-show").onchange = (e) => { $("sw-3d-wrap").hidden = !e.target.checked; if (e.target.checked) render3d(); }; |
| $("sw-3dcolor").onchange = render3d; |
|
|
| $("view-sweep").querySelectorAll(".sw-fs-btn").forEach((b) => b.onclick = () => openFs(b.dataset.fs)); |
| $("sw-fs-close").onclick = closeFs; |
| document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !$("sw-fs").hidden) closeFs(); }); |
|
|
| buildToggles(); |
| S.built = true; |
| } |
|
|
| function buildToggles() { |
| $("sw-toggles").innerHTML = `<span class="sw-muted" style="margin-right:4px">Show:</span>` + |
| TOGGLES.map(([k, l]) => |
| `<label class="sw-muted" style="text-transform:none;letter-spacing:0"> |
| <input type="checkbox" data-show="${k}" ${S.show[k] ? "checked" : ""}> ${l}</label>`).join(""); |
| $("sw-toggles").querySelectorAll("input").forEach((el) => |
| el.onchange = () => { S.show[el.dataset.show] = el.checked; render2d(); codecReadout(); }); |
| } |
|
|
| |
| async function load() { |
| msg("Loading…", true); |
| const qs = []; |
| if (S.mpMin != null) qs.push("mp_min=" + S.mpMin); |
| if (S.mpMax != null) qs.push("mp_max=" + S.mpMax); |
| try { |
| const d = await (await fetch("/api/sweeps/study?" + qs.join("&"))).json(); |
| if (!d.exists) { $("sw-summary-card").hidden = true; $("sw-main").hidden = true; return msg("No sweep database yet — run a sweep first."); } |
| const all = (d.trials || []).map((t) => ({ ...t, eff: efficiency(t) })); |
| S.study = d; |
| S.trials = all.filter((t) => t.kind === "pbc3"); |
| computePareto(S.trials); |
| buildCodec(all); |
| S.selected = null; S.dyn = []; |
| msg(`${S.trials.length} PBC trials · ${S.trials.filter((t) => t.pareto).length} on the Pareto front`); |
| $("sw-summary-card").hidden = false; |
| $("sw-main").hidden = false; |
| renderSummary(); |
| buildAddFilterOptions(); |
| renderDyn(); |
| applyFilters(); |
| } catch (e) { msg("Failed to load: " + e.message); } |
| } |
|
|
| |
| |
| |
| function computePareto(rows) { |
| rows.forEach((a) => { |
| a.pareto = !rows.some((b) => b !== a && |
| b.speed <= a.speed && b.bpp <= a.bpp && b.mse <= a.mse && |
| (b.speed < a.speed || b.bpp < a.bpp || b.mse < a.mse)); |
| }); |
| } |
|
|
| function buildCodec(all) { |
| const pick = (c) => all.filter((t) => t.codec === c) |
| .map((t) => ({ q: t.q, bpp: t.bpp, mse: t.mse, speed: t.speed, eff: t.eff })).sort((a, b) => a.q - b.q); |
| const jpeg = pick("JPEG"), jp2 = pick("JPEG2000"), avif = pick("AVIF"), webp = pick("WEBP"), jxl = pick("JXL"), downsample = pick("DOWNSAMPLE"), png = pick("PNG"); |
| S.codec = (jpeg.length || jp2.length || avif.length || webp.length || jxl.length || downsample.length || png.length) ? { jpeg, jp2, avif, webp, jxl, downsample, png } : null; |
| } |
|
|
| function renderSummary() { |
| const d = S.study; |
| $("sw-warn").innerHTML = d.completed ? "" : |
| `<div class="sw-warn">No completed trials in this study yet.</div>`; |
| const cells = [ |
| ["Study", d.study_name], |
| ["MP filter", `${S.mpMin ?? "any"} – ${S.mpMax ?? "any"}`], |
| ["PBC trials", S.trials.length, "hl"], |
| ["Pareto trials", S.trials.filter((t) => t.pareto).length, "hl"], |
| ["Metrics", (d.metric_names || []).join(" · ")], |
| ]; |
| $("sw-summary").innerHTML = cells.map(([k, v, c]) => |
| `<div class="cell"><span class="k">${k}</span><span class="v ${c || ""}">${v}</span></div>`).join(""); |
| } |
|
|
| |
| function paramKeys() { |
| const keys = new Set(); |
| S.trials.forEach((t) => Object.keys(t.params || {}).forEach((k) => keys.add(k))); |
| return [...keys].sort(); |
| } |
| function isNumericParam(k) { |
| let seen = false; |
| for (const t of S.trials) { |
| const v = t.params[k]; |
| if (v === undefined || v === null) continue; |
| seen = true; |
| if (isNaN(+v)) return false; |
| } |
| return seen; |
| } |
| function buildAddFilterOptions() { |
| $("sw-addfilter").innerHTML = `<option value="">+ Add parameter filter…</option>` + |
| paramKeys().map((k) => `<option value="${k}">${k}</option>`).join(""); |
| } |
| function addDynFilter(param) { |
| if (S.dyn.some((d) => d.param === param)) return; |
| if (isNumericParam(param)) S.dyn.push({ param, kind: "num", min: null, max: null }); |
| else { |
| const values = [...new Set(S.trials.map((t) => String(t.params[param])).filter((v) => v !== "undefined"))].sort(); |
| S.dyn.push({ param, kind: "cat", values: new Set(values), all: values }); |
| } |
| renderDyn(); |
| applyFilters(); |
| } |
| function renderDyn() { |
| const wrap = $("sw-dyn"); |
| if (!S.dyn.length) { wrap.innerHTML = ""; return; } |
| wrap.innerHTML = S.dyn.map((d, i) => { |
| if (d.kind === "num") { |
| return `<div class="sw-row tight" data-i="${i}" style="gap:10px"> |
| <span class="sw-tag">${d.param}</span> |
| <label class="sw-muted">min <input class="sw-input sm" type="number" data-dyn="min" value="${d.min ?? ""}"></label> |
| <label class="sw-muted">max <input class="sw-input sm" type="number" data-dyn="max" value="${d.max ?? ""}"></label> |
| <button class="sw-btn sm" data-dyn="rm">✕</button></div>`; |
| } |
| return `<div class="sw-row tight" data-i="${i}" style="gap:10px;flex-wrap:wrap"> |
| <span class="sw-tag">${d.param}</span> |
| ${d.all.map((v) => `<label class="sw-muted" style="text-transform:none;letter-spacing:0"> |
| <input type="checkbox" data-dyn="cat" data-v="${v}" ${d.values.has(v) ? "checked" : ""}> ${v}</label>`).join("")} |
| <button class="sw-btn sm" data-dyn="rm">✕</button></div>`; |
| }).join(""); |
| wrap.querySelectorAll("[data-i]").forEach((row) => { |
| const i = +row.dataset.i, d = S.dyn[i]; |
| row.querySelectorAll("[data-dyn]").forEach((el) => { |
| const kind = el.dataset.dyn; |
| if (kind === "rm") el.onclick = () => { S.dyn.splice(i, 1); renderDyn(); applyFilters(); }; |
| else if (kind === "min" || kind === "max") el.oninput = () => { d[kind] = el.value === "" ? null : +el.value; applyFilters(); }; |
| else if (kind === "cat") el.onchange = () => { el.checked ? d.values.add(el.dataset.v) : d.values.delete(el.dataset.v); applyFilters(); }; |
| }); |
| }); |
| } |
|
|
| function applyFilters() { |
| if (!S.study) return; |
| const msemax = parseFloat($("f-msemax").value), |
| bppmax = parseFloat($("f-bppmax").value), |
| spmax = parseFloat($("f-spmax").value); |
| S.fnum = { mse: msemax, bpp: bppmax, sp: spmax }; |
| S.filtered = S.trials.filter((t) => { |
| if (!isNaN(msemax) && t.mse > msemax) return false; |
| if (!isNaN(bppmax) && t.bpp > bppmax) return false; |
| if (!isNaN(spmax) && t.speed > spmax) return false; |
| for (const d of S.dyn) { |
| const v = t.params[d.param]; |
| if (d.kind === "num") { |
| const n = +v; |
| if (d.min !== null && !(n >= d.min)) return false; |
| if (d.max !== null && !(n <= d.max)) return false; |
| } else if (d.values.size && !d.values.has(String(v))) return false; |
| } |
| return true; |
| }); |
| $("sw-tcount").textContent = ` ${S.filtered.length} / ${S.trials.length} shown`; |
| renderAll(); |
| } |
|
|
| function renderAll() { render2d(); render3d(); renderTable(); } |
|
|
| |
| |
| function codecPoints(key) { |
| if (!S.codec || !S.codec[key]) return null; |
| const f = S.fnum || {}; |
| return S.codec[key].filter((p) => |
| (isNaN(f.mse) || p.mse <= f.mse) && |
| (isNaN(f.bpp) || p.bpp <= f.bpp) && |
| (isNaN(f.sp) || p.speed <= f.sp)); |
| } |
|
|
| function interp(xs, ys, x) { |
| for (let i = 1; i < xs.length; i++) { |
| if (x >= xs[i - 1] && x <= xs[i]) { |
| const f = (x - xs[i - 1]) / (xs[i] - xs[i - 1] || 1); |
| return ys[i - 1] + f * (ys[i] - ys[i - 1]); |
| } |
| } |
| return null; |
| } |
|
|
| function codecReadout() { |
| const jp = codecPoints("jpeg"); |
| const sorted = jp ? [...jp].sort((a, b) => a.mse - b.mse) : []; |
| if (!S.show.jpeg || !sorted.length) { $("sw-readout").textContent = ""; return; } |
| const par = S.filtered.filter((t) => t.pareto); |
| const lo = sorted[0].mse, hi = sorted[sorted.length - 1].mse; |
| let line = `JPEG baselines span MSE ${fmt(lo, 1)}–${fmt(hi, 1)}.`; |
| const mids = par.filter((t) => t.mse >= lo && t.mse <= hi); |
| if (mids.length) { |
| const mid = (lo + hi) / 2; |
| const t = mids.reduce((a, b) => (Math.abs(b.mse - mid) < Math.abs(a.mse - mid) ? b : a)); |
| const jbpp = interp(sorted.map((p) => p.mse), sorted.map((p) => p.bpp), t.mse); |
| if (jbpp) { |
| const pct = (t.bpp - jbpp) / jbpp * 100; |
| line += ` At MSE ${fmt(t.mse, 1)} (trial #${t.number}), PBC is ${Math.abs(pct).toFixed(0)}% ${pct < 0 ? "smaller" : "larger"} than JPEG.`; |
| } |
| } |
| $("sw-readout").textContent = line; |
| } |
|
|
| |
| const hover = (t) => { |
| const p = t.params || {}; |
| return `#${t.number}${t.pareto ? " · Pareto" : ""}${t.baseline ? " · " + baseLabel(t) : ""}<br>` + |
| `MSE ${fmt(t.mse, 1)}<br>bpp ${fmt(t.bpp)}<br>seconds ${fmt(t.speed, 3)}<br>eff ${fmt(t.eff, 3)}<br>` + |
| `patches ${p.patch_count ?? "—"} · ${p.color_space ?? ""}`; |
| }; |
|
|
| const floorVal = (v, isLog, floor) => (isLog && (v == null || v <= 0)) ? floor : v; |
|
|
| const mseToPsnr = (v) => (v != null && v > 0) ? 10 * Math.log10(65025 / v) : null; |
|
|
| function ptTrace(rows, xKey, yKey, style, fx = 0, fy = 0, yfn = null, logy = S.logy) { |
| return { |
| x: rows.map((t) => floorVal(t[xKey], S.logx, fx)), |
| y: rows.map((t) => yfn ? yfn(t[yKey]) : floorVal(t[yKey], logy, fy)), |
| customdata: rows.map((t) => t.number), text: rows.map(hover), |
| hovertemplate: "%{text}<extra></extra>", mode: "markers", type: "scatter", ...style, |
| }; |
| } |
|
|
| function codecTrace(pts, xKey, yKey, name, color, fx = 0, fy = 0, yfn = null, logy = S.logy) { |
| return { |
| x: pts.map((p) => floorVal(p[xKey], S.logx, fx)), |
| y: pts.map((p) => yfn ? yfn(p[yKey]) : floorVal(p[yKey], logy, fy)), |
| mode: "lines+markers", type: "scatter", name, |
| line: { color }, marker: { size: 5, color }, |
| text: pts.map((p) => { |
| const tag = name === "DOWNSAMPLE" ? `f${p.q}` : name === "PNG" ? "lossless" : `q${p.q}`; |
| return `${name} ${tag}<br>bpp ${fmt(p.bpp)}<br>MSE ${fmt(p.mse, 1)}<br>seconds ${fmt(p.speed, 3)}`; |
| }), |
| hovertemplate: "%{text}<extra></extra>", |
| }; |
| } |
|
|
| function scatter2d(xKey, yKey, usePsnr = false) { |
| const all = S.filtered, traces = []; |
| const CODECS = ["jpeg", "jp2", "webp", "jxl", "avif", "downsample", "png"]; |
| const yfn = usePsnr ? mseToPsnr : null; |
| const logy = S.logy && !usePsnr; |
| const plotFloor = (key) => { |
| let m = Infinity; |
| const scan = (rows) => rows && rows.forEach((t) => { const v = t[key]; if (v != null && v > 0 && v < m) m = v; }); |
| scan(all); |
| CODECS.forEach((k) => { if (S.show[k]) scan(codecPoints(k)); }); |
| return (m === Infinity ? 1 : m) / 2; |
| }; |
| const fx = S.logx ? plotFloor(xKey) : 0; |
| const fy = logy ? plotFloor(yKey) : 0; |
|
|
| if (S.show.pbcAll) traces.push(ptTrace(all, xKey, yKey, { name: "PBC all", marker: { size: 5, color: COLORS.gray, opacity: .55 } }, fx, fy, yfn, logy)); |
| if (S.show.pbcPareto) traces.push(ptTrace(all.filter((t) => t.pareto), xKey, yKey, { name: "PBC Pareto", marker: { size: 8, color: COLORS.pareto } }, fx, fy, yfn, logy)); |
|
|
| [["jpeg", COLORS.jpeg], ["jp2", COLORS.jp2], ["webp", COLORS.webp], ["jxl", COLORS.jxl], ["avif", COLORS.avif], ["downsample", COLORS.downsample], ["png", COLORS.png]].forEach(([k, c]) => { |
| if (!S.show[k]) return; |
| const pts = codecPoints(k); |
| if (pts && pts.length) traces.push(codecTrace([...pts].sort((a, b) => a[xKey] - b[xKey]), xKey, yKey, k.toUpperCase(), c, fx, fy, yfn, logy)); |
| }); |
|
|
| if (S.show.presets) { |
| const pre = all.filter((t) => t.preset); |
| if (pre.length) traces.push(ptTrace(pre, xKey, yKey, { name: "presets", marker: { size: 12, color: COLORS.base, symbol: "diamond", line: { color: "#fff", width: 1 } } }, fx, fy, yfn, logy)); |
| } |
|
|
| const sel = all.filter((t) => t.number === S.selected); |
| if (sel.length) traces.push(ptTrace(sel, xKey, yKey, { name: "selected", marker: { size: 13, color: COLORS.sel, line: { color: "#fff", width: 1 } } }, fx, fy, yfn, logy)); |
| return traces; |
| } |
|
|
| function layout2d(xt, yt, note, usePsnr = false) { |
| const logy = S.logy && !usePsnr; |
| return { |
| margin: { l: 56, r: 10, t: note ? 26 : 12, b: 38 }, showlegend: false, |
| paper_bgcolor: "rgba(0,0,0,0)", plot_bgcolor: "rgba(0,0,0,0)", |
| font: { color: "#a6a6ad", size: 10 }, |
| title: note ? { text: note, font: { size: 10, color: "#82828a" }, x: 0, xanchor: "left" } : undefined, |
| |
| xaxis: { title: xt, gridcolor: "#262629", zeroline: false, type: S.logx ? "log" : "linear", tickformat: S.logx ? "~g" : undefined }, |
| yaxis: { title: yt, gridcolor: "#262629", zeroline: false, type: logy ? "log" : "linear", tickformat: logy ? "~g" : undefined }, |
| }; |
| } |
|
|
| function bindClick(id) { |
| const el = $(id); |
| if (el._swbound) return; |
| el._swbound = true; |
| el.on("plotly_click", (ev) => { const cd = ev.points[0] && ev.points[0].customdata; if (cd != null) select(cd); }); |
| } |
|
|
| function plot2d(elId, key) { |
| const c = P2D[key]; |
| const usePsnr = S.psnr && (key === "qc" || key === "qs"); |
| const yt = usePsnr ? "PSNR (dB)" : c.yt; |
| const note = usePsnr ? "↖ upper-left is best" : c.note; |
| PLOT.react(elId, scatter2d(c.x, c.y, usePsnr), layout2d(c.xt, yt, note, usePsnr), { displayModeBar: false, responsive: true }); |
| bindClick(elId); |
| } |
|
|
| function render2d() { |
| if (typeof PLOT === "undefined" || !S.study) return; |
| ["qc", "qs", "cs", "qcs"].forEach((k) => plot2d("sw-p-" + k, k)); |
| codecReadout(); |
| if (S.fsKind && S.fsKind !== "3d") { plot2d("sw-fs-plot", S.fsKind); resizeFs(); } |
| } |
|
|
| |
| function render3dInto(elId) { |
| try { |
| const by = $("sw-3dcolor").value, all = S.filtered; |
| const colorVals = by === "pareto" ? all.map((t) => (t.pareto ? 1 : 0)) |
| : ["mse", "bpp"].includes(by) ? all.map((t) => t[by]) |
| : by === "seconds" ? all.map((t) => t.speed) |
| : all.map((t) => +t.params[by]); |
| const traces = [{ |
| x: all.map((t) => t.bpp), y: all.map((t) => t.speed), z: all.map((t) => t.mse), |
| customdata: all.map((t) => t.number), text: all.map(hover), hovertemplate: "%{text}<extra></extra>", |
| mode: "markers", type: "scatter3d", |
| marker: { size: 3, color: colorVals, colorscale: "Viridis", opacity: .85, showscale: true, colorbar: { title: by, thickness: 10, len: .6 } }, |
| }]; |
| const par = all.filter((t) => t.pareto); |
| traces.push({ |
| x: par.map((t) => t.bpp), y: par.map((t) => t.speed), z: par.map((t) => t.mse), |
| text: par.map(hover), hovertemplate: "%{text}<extra></extra>", mode: "markers", type: "scatter3d", |
| name: "Pareto", marker: { size: 5, color: COLORS.pareto }, |
| }); |
| const sel = all.find((t) => t.number === S.selected); |
| if (sel) traces.push({ |
| x: [sel.bpp], y: [sel.speed], z: [sel.mse], mode: "markers", type: "scatter3d", |
| text: [hover(sel)], hovertemplate: "%{text}<extra></extra>", |
| marker: { size: 7, color: COLORS.sel, line: { color: "#fff", width: 1 } }, |
| }); |
| const layout = { |
| margin: { l: 0, r: 0, t: 0, b: 0 }, showlegend: false, autosize: true, |
| paper_bgcolor: "rgba(0,0,0,0)", font: { color: "#a6a6ad", size: 10 }, |
| scene: { |
| xaxis: { title: "bpp", gridcolor: "#262629", backgroundcolor: "rgba(0,0,0,0)" }, |
| yaxis: { title: "seconds", gridcolor: "#262629", backgroundcolor: "rgba(0,0,0,0)" }, |
| zaxis: { title: "MSE", gridcolor: "#262629", backgroundcolor: "rgba(0,0,0,0)" }, |
| }, |
| }; |
| PLOT.react(elId, traces, layout, { displayModeBar: false, responsive: true }); |
| PLOT.Plots.resize($(elId)); |
| bindClick(elId); |
| } catch (e) { |
| $(elId).innerHTML = `<div class="sw-empty">3D render failed: ${e.message}</div>`; |
| } |
| } |
|
|
| function render3d() { |
| if (typeof PLOT === "undefined" || !S.study) return; |
| if ($("sw-3d-show") && $("sw-3d-show").checked) render3dInto("sw-3d"); |
| if (S.fsKind === "3d") { render3dInto("sw-fs-plot"); resizeFs(); } |
| } |
|
|
| |
| function openFs(kind) { |
| if (typeof PLOT === "undefined") return toastMsg("Plotly unavailable"); |
| S.fsKind = kind; |
| $("sw-fs-title").textContent = kind === "3d" ? "3D tradeoff" : P2D[kind].title; |
| $("sw-fs").hidden = false; |
| if (kind === "3d") render3dInto("sw-fs-plot"); else plot2d("sw-fs-plot", kind); |
| resizeFs(); |
| } |
| function closeFs() { |
| if (S.fsKind) { try { PLOT.purge("sw-fs-plot"); } catch (e) { } } |
| S.fsKind = null; |
| $("sw-fs-plot")._swbound = false; |
| $("sw-fs").hidden = true; |
| resizeAll(); |
| } |
| function resizeFs() { |
| const el = $("sw-fs-plot"); |
| if (el && el.data && !$("sw-fs").hidden) PLOT.Plots.resize(el); |
| } |
|
|
| |
| const COLS = [ |
| ["number", "Trial"], ["pareto", "Par"], ["baseline", "Base"], ["mse", "MSE"], |
| ["bpp", "BPP"], ["speed", "sec"], ["eff", "eff"], |
| ["patch_count", "patches"], ["q_init", "q_init"], ["color_space", "color"], ["_actions", ""], |
| ]; |
| const METRIC_KEYS = ["mse", "bpp", "speed", "eff"]; |
| const cellVal = (t, k) => |
| k === "number" ? t.number : |
| k === "pareto" ? (t.pareto ? 1 : 0) : |
| k === "baseline" ? (t.baseline ? 1 : 0) : |
| METRIC_KEYS.includes(k) ? t[k] : t.params[k]; |
|
|
| function renderTable() { |
| const table = $("sw-table"); |
| const rows = [...S.filtered].sort((a, b) => { |
| const va = cellVal(a, S.sort.key), vb = cellVal(b, S.sort.key); |
| const na = +va, nb = +vb; |
| if (!isNaN(na) && !isNaN(nb)) return (na - nb) * S.sort.dir; |
| return String(va).localeCompare(String(vb)) * S.sort.dir; |
| }); |
| const head = COLS.map(([k, l]) => `<th data-k="${k}">${l}${S.sort.key === k ? (S.sort.dir > 0 ? " ▲" : " ▼") : ""}</th>`).join(""); |
| const body = rows.map((t) => { |
| const cells = COLS.map(([k]) => { |
| if (k === "number") return `<td>#${t.number}</td>`; |
| if (k === "pareto") return `<td>${t.pareto ? '<span class="sw-tag par">P</span>' : ""}</td>`; |
| if (k === "baseline") return `<td>${t.baseline ? '<span class="sw-tag base" title="' + baseLabel(t) + '">B</span>' : ""}</td>`; |
| if (k === "_actions") return `<td><button class="sw-btn sm" data-act="copy" data-n="${t.number}">Copy</button></td>`; |
| if (k === "mse") return `<td>${fmt(t.mse, 1)}</td>`; |
| if (k === "speed") return `<td>${fmt(t.speed, 3)}</td>`; |
| if (k === "eff") return `<td>${fmt(t.eff, 3)}</td>`; |
| if (k === "bpp") return `<td>${fmt(t.bpp)}</td>`; |
| return `<td>${t.params[k] ?? "—"}</td>`; |
| }).join(""); |
| return `<tr data-n="${t.number}" class="${t.number === S.selected ? "sel" : ""}">${cells}</tr>`; |
| }).join(""); |
| table.innerHTML = `<thead><tr>${head}</tr></thead><tbody>${body}</tbody>`; |
|
|
| table.querySelectorAll("th").forEach((th) => th.onclick = () => { |
| const k = th.dataset.k; if (k === "_actions") return; |
| S.sort = { key: k, dir: S.sort.key === k ? -S.sort.dir : (METRIC_KEYS.includes(k) ? 1 : -1) }; |
| renderTable(); |
| }); |
| table.querySelectorAll("tbody tr").forEach((tr) => tr.onclick = (e) => { |
| const btn = e.target.closest("button"); |
| if (btn) { copy(optunaText(byNum(+btn.dataset.n)), "Params copied"); return; } |
| select(+tr.dataset.n); |
| }); |
| } |
|
|
| |
| const byNum = (n) => S.trials.find((t) => t.number === n); |
|
|
| function select(n) { |
| S.selected = n; |
| renderTable(); render2d(); render3d(); renderSelected(); |
| } |
|
|
| function renderSelected() { |
| const t = byNum(S.selected); |
| $("sw-sel-sub").textContent = t ? `#${t.number}` : "none"; |
| if (!t) { |
| $("sw-sel-metrics").innerHTML = ""; |
| $("sw-sel-params").textContent = ""; |
| $("sw-copybar").innerHTML = ""; |
| return; |
| } |
| $("sw-sel-metrics").innerHTML = [ |
| ["MSE", fmt(t.mse, 1)], ["BPP", fmt(t.bpp)], ["Seconds", fmt(t.speed, 3)], |
| ["Efficiency", fmt(t.eff, 3)], ["Pareto", t.pareto ? "yes" : "no"], |
| ["Baseline", t.baseline ? baseLabel(t) : "no"], |
| ].map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join(""); |
|
|
| $("sw-sel-params").textContent = optunaText(t); |
|
|
| $("sw-copybar").innerHTML = ` |
| <button class="sw-btn sm" id="cp-opt">Copy params</button> |
| <button class="sw-btn sm" id="cp-cfg">Copy PBC3Config</button> |
| <button class="sw-btn sm" id="cp-json">Copy JSON</button> |
| <button class="sw-btn sm primary" id="cp-load">Load into Demo</button>`; |
| $("cp-opt").onclick = () => copy(optunaText(t), "Params copied"); |
| $("cp-cfg").onclick = () => copy(configPython(t), "PBC3Config copied"); |
| $("cp-json").onclick = () => copy(JSON.stringify(t.params, null, 2), "JSON copied"); |
| $("cp-load").onclick = () => loadIntoDemo(t); |
| } |
|
|
| |
| function optunaText(t) { |
| return Object.entries(t.params).map(([k, v]) => |
| `${k} ${v === true ? "True" : v === false ? "False" : v}`).join("\n"); |
| } |
|
|
| function configPython(t) { |
| const lines = Object.entries(t.params).map(([k, v]) => { |
| if (typeof v === "boolean") return `${k}=${v ? "True" : "False"}`; |
| if (typeof v === "number") return `${k}=${v}`; |
| if (Array.isArray(v)) return `${k}=(${v.join(", ")})`; |
| return `${k}="${v}"`; |
| }); |
| return `PBC3Config(\n ${lines.join(",\n ")},\n)`; |
| } |
|
|
| function loadIntoDemo(t) { |
| const text = optunaText(t); |
| if (typeof window.applyOptuna === "function") { |
| window.applyOptuna(text); |
| if (window.SweepLab && typeof window.SweepLab.toApp === "function") window.SweepLab.toApp(); |
| else if (typeof window.gotoView === "function") window.gotoView("compress"); |
| toastMsg("Loaded trial #" + t.number + " into the demo"); |
| } else copy(text, "Copied — paste into the demo's params"); |
| } |
|
|
| function copy(text, ok) { |
| navigator.clipboard.writeText(text).then(() => toastMsg(ok)).catch(() => toastMsg("Copy failed")); |
| } |
|
|
| |
| function msg(text, busy) { $("sw-msg").innerHTML = (busy ? `<span class="sw-spin"></span> ` : "") + text; } |
| function toastMsg(m) { typeof window.toast === "function" ? window.toast(m) : msg(m); } |
|
|
| function resizeAll() { |
| if (!S.built || typeof PLOT === "undefined") return; |
| ["sw-p-qc", "sw-p-qs", "sw-p-cs", "sw-p-qcs"].forEach((id) => { const el = $(id); if (el && el.data) PLOT.Plots.resize(el); }); |
| const td = $("sw-3d"); |
| if (td && td.data && $("sw-3d-show").checked) PLOT.Plots.resize(td); |
| } |
|
|
| window.SweepAnalyzer = { |
| onShow() { |
| if (!S.built) build(); |
| if (typeof PLOT === "undefined") msg("Plotly failed to load (offline / blocked). Charts disabled."); |
| if (!S.study) load(); else resizeAll(); |
| }, |
| }; |
| })(); |