"use strict";
window.BenchmarkPage = (function () {
const $ = (id) => document.getElementById(id);
const CODECS = ["PBC3", "JPEG", "JPEG2000", "WEBP", "JXL", "AVIF", "PNG"];
const COLORS = { PBC3: "#ff4d3d", JPEG: "#7d8cff", JPEG2000: "#4fa3ff", WEBP: "#54c0ff", JXL: "#c084fc", AVIF: "#34d39a", PNG: "#ef19ef" };
const S = { built: false, poll: null, points: [], filtered: [], show: Object.fromEntries(CODECS.map(c => [c, true])), logx: false, logy: false, fsKind: null };
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);}
#bm-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;}
#bm-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);}
#bm-fs-plot{flex:1;min-height:0;}`;
const esc = (s) => String(s).replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" }[c]));
const psnr = (mse) => mse <= 0 ? 99 : 10 * Math.log10(255 * 255 / mse);
const f3 = (v) => Number.isFinite(+v) ? (+v).toFixed(3) : "—";
const f5 = (v) => Number.isFinite(+v) ? (+v).toFixed(5) : "—";
const codecLabel = (p) => p.codec === "PBC3" ? `PBC3 ${p.variant}` : (p.codec === "PNG" ? "PNG" : `${p.codec} ${p.variant}`);
function onShow() { build(); refreshResults(); refreshStatus(); }
function onHide() { stopPolling(); closeFs(); }
function build() {
if (S.built) return;
const st = document.createElement("style");
st.textContent = FS_STYLE;
document.head.appendChild(st);
$("view-benchmark").innerHTML = `
Run benchmarkfixed dataset from hpt_data/
loading dataset…
Benchmark Database Results
EfficiencyPSNR / bpp vs seconds
Speed averagesaveraged over visible filtered points
`;
renderCodecChecks("bm-codecs", Object.fromEntries(CODECS.map(c => [c, true])));
renderCodecChecks("bm-show-codecs", S.show, () => { applyFilters(); renderPlots(); });
$("bm-upload").onchange = (e) => e.target.files[0] && uploadDb(e.target.files[0]);
$("bm-download").onclick = () => window.open("/api/benchmark/download_db", "_blank");
$("bm-reset-pbc").onclick = resetPbcRows;
$("bm-n").oninput = (e) => $("bm-n-val").textContent = e.target.value;
$("bm-run").onclick = start;
$("bm-stop").onclick = stop;
["bm-mpmin", "bm-mpmax"].forEach(id => $(id).addEventListener("change", refreshResults));
$("bm-mpreset").onclick = () => { $("bm-mpmin").value = ""; $("bm-mpmax").value = ""; refreshResults(); };
$("bm-refresh").onclick = refreshResults;
["bm-psnrmin", "bm-msemax", "bm-bppmax", "bm-encmax", "bm-decmax"].forEach(id => $(id).addEventListener("input", () => { applyFilters(); renderPlots(); }));
$("bm-logx").onchange = (e) => { S.logx = e.target.checked; renderPlots(); };
$("bm-logy").onchange = (e) => { S.logy = e.target.checked; renderPlots(); };
$("view-benchmark").querySelectorAll(".sw-fs-btn").forEach(b => b.onclick = () => openFs(b.dataset.bmFs));
$("bm-fs-close").onclick = closeFs;
document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !$('bm-fs').hidden) closeFs(); });
fetch("/api/benchmark/dataset").then(r => r.json()).then(ds => {
$("bm-dataset").innerHTML = ds.count
? `${ds.count} images · ` + ds.images.map(i => `${esc(i.name)} (${i.mp} MP)`).join(" · ")
: "No images found in hpt_data/.";
});
S.built = true;
}
function renderCodecChecks(id, state, onchange) {
$(id).innerHTML = CODECS.map(c => ``).join("");
$(id).querySelectorAll("input").forEach(el => el.onchange = () => {
state[el.dataset.codec] = el.checked;
if (onchange) onchange();
});
}
function selectedCodecs() {
return [...$("bm-codecs").querySelectorAll("input:checked")].map(x => x.dataset.codec);
}
async function start() {
const body = { codecs: selectedCodecs(), n_trials: +$("bm-n").value };
const d = await (await fetch("/api/benchmark/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })).json();
if (d.error) return window.toast ? window.toast(d.error) : alert(d.error);
startPolling();
refreshStatus();
}
async function stop() { await fetch("/api/benchmark/stop", { method: "POST" }); refreshStatus(); }
async function uploadDb(file) {
const fd = new FormData(); fd.append("file", file);
const d = await (await fetch("/api/benchmark/upload_db", { method: "POST", body: fd })).json();
$("bm-dbmsg").textContent = d.error || "Benchmark database loaded.";
refreshResults();
}
async function resetPbcRows() {
if (!confirm("Reset all PBC3 rows from the benchmark database? Other codec rows will be kept.")) return;
const r = await fetch("/api/benchmark/reset_pbc", { method: "POST" });
const d = await r.json();
$("bm-dbmsg").textContent = d.error || `Deleted ${d.deleted || 0} PBC3 benchmark row(s).`;
refreshResults();
}
function startPolling() { if (!S.poll) S.poll = setInterval(refreshStatus, 2000); }
function stopPolling() { if (S.poll) { clearInterval(S.poll); S.poll = null; } }
async function refreshStatus() {
if (!S.built) return;
let s; try { s = await (await fetch("/api/benchmark/status")).json(); } catch { return; }
$("bm-badge").textContent = s.running ? "running" : (s.error ? "error" : "idle");
$("bm-badge").style.color = s.running ? "var(--red)" : "";
$("bm-run").disabled = s.running;
$("bm-stop").disabled = !s.running;
const prog = s.total ? ` · ${s.done}/${s.total}` : "";
$("bm-status").innerHTML = (s.running ? `Benchmarking${prog}` : (s.error ? `Error: ${esc(s.error)}` : "Idle.")) +
(s.current ? `
current: ${esc(JSON.stringify(s.current))}` : "");
$("bm-log").textContent = (s.log || []).slice(-40).join("\n");
if (s.running) { startPolling(); refreshResults(); }
else { stopPolling(); refreshResults(); }
}
async function refreshResults() {
if (!S.built) return;
const qs = new URLSearchParams();
if ($("bm-mpmin").value !== "") qs.set("mp_min", $("bm-mpmin").value);
if ($("bm-mpmax").value !== "") qs.set("mp_max", $("bm-mpmax").value);
const d = await (await fetch("/api/benchmark/results?" + qs.toString())).json();
S.points = (d.points || []).map(p => ({ ...p, psnr: +p.psnr || psnr(+p.mse), eff: (+p.psnr || psnr(+p.mse)) / Math.max(+p.bpp, 1e-9) }));
applyFilters();
renderPlots();
}
function num(id) { const v = $(id).value; return v === "" ? NaN : +v; }
function applyFilters() {
const psnrMin = num("bm-psnrmin"), mseMax = num("bm-msemax"), bppMax = num("bm-bppmax"), encMax = num("bm-encmax"), decMax = num("bm-decmax");
S.filtered = S.points.filter(p => S.show[p.codec])
.filter(p => !Number.isFinite(psnrMin) || p.psnr >= psnrMin)
.filter(p => !Number.isFinite(mseMax) || p.mse <= mseMax)
.filter(p => !Number.isFinite(bppMax) || p.bpp <= bppMax)
.filter(p => !Number.isFinite(encMax) || p.encode_seconds <= encMax)
.filter(p => !Number.isFinite(decMax) || p.decode_seconds <= decMax);
}
function scatterTrace(codec, speedKey = "encode_seconds") {
const pts = S.filtered.filter(p => p.codec === codec).sort((a, b) => a.bpp - b.bpp);
return {
x: pts.map(p => p[speedKey]), y: pts.map(p => p.eff), mode: "markers+lines", type: "scatter", name: codec,
marker: { color: COLORS[codec], size: codec === "PBC3" ? 10 : 8 }, line: { color: COLORS[codec], width: 1.5 },
text: pts.map(p => `${codecLabel(p)}
PSNR ${f3(p.psnr)} · bpp ${f5(p.bpp)}
encode ${f3(p.encode_seconds)}s · decode ${f3(p.decode_seconds)}s`),
hovertemplate: "%{text}",
};
}
function avgByCodec(key) {
return CODECS.map(codec => {
const pts = S.filtered.filter(p => p.codec === codec);
const n = pts.reduce((a, p) => a + (+p.count || 1), 0);
const v = n ? pts.reduce((a, p) => a + (+p[key]) * (+p.count || 1), 0) / n : 0;
return { codec, v, n };
}).filter(x => x.n > 0);
}
function barTrace(rows, key, title) {
const max = Math.max(...rows.map(r => r.v), 0);
return [{
x: rows.map(r => r.codec), y: rows.map(r => r.v), type: "bar",
marker: { color: rows.map(r => COLORS[r.codec]) },
text: rows.map(r => `${f3(r.v)}s`), textposition: "outside",
textangle: 0, cliponaxis: false,
hovertext: rows.map(r => `${r.codec}
${title}: ${f3(r.v)}s`), hovertemplate: "%{hovertext}",
}];
}
function layout(title, x, y) {
return {
title: { text: title, font: { size: 13, color: "#ddd" } }, paper_bgcolor: "transparent", plot_bgcolor: "transparent",
font: { color: "#cfcfcf", size: 11 }, margin: { l: 56, r: 16, t: 42, b: 44 },
xaxis: { title: x, type: S.logx ? "log" : "linear", tickformat: S.logx ? "~g" : undefined, gridcolor: "rgba(255,255,255,.08)", zeroline: false },
yaxis: { title: y, type: S.logy ? "log" : "linear", tickformat: S.logy ? "~g" : undefined, gridcolor: "rgba(255,255,255,.08)", zeroline: false },
legend: { orientation: "h" },
};
}
function barLayout(title, y) {
return {
title: { text: title, font: { size: 13, color: "#ddd" } }, paper_bgcolor: "transparent", plot_bgcolor: "transparent",
font: { color: "#cfcfcf", size: 11 }, margin: { l: 56, r: 16, t: 42, b: 52 },
xaxis: { gridcolor: "rgba(255,255,255,.08)" },
yaxis: { title: y, type: S.logy ? "log" : "linear", tickformat: S.logy ? "~g" : undefined, gridcolor: "rgba(255,255,255,.08)", zeroline: false },
uniformtext: { mode: "show", minsize: 9 },
};
}
function visibleCodecs() {
return CODECS.filter(c => S.show[c] && S.filtered.some(p => p.codec === c));
}
function plotEfficiency(elId, speedKey, title, xt) {
Plotly.react(elId, visibleCodecs().map(c => scatterTrace(c, speedKey)), layout(title, xt, "PSNR / bpp"), { responsive: true, displaylogo: false, displayModeBar: false });
}
function plotBar(elId, key, title) {
Plotly.react(elId, barTrace(avgByCodec(key), key, title), barLayout(title + " Speed", "seconds"), { responsive: true, displaylogo: false, displayModeBar: false });
}
function renderPlots() {
if (!window.Plotly || !S.built) return;
plotEfficiency("bm-p-eff", "encode_seconds", "Efficiency vs Encode Speed", "encode seconds");
plotEfficiency("bm-p-eff-dec", "decode_seconds", "Efficiency vs Decode Speed", "decode seconds");
plotBar("bm-p-enc", "encode_seconds", "Encode");
plotBar("bm-p-dec", "decode_seconds", "Decode");
if (S.fsKind) renderFsPlot();
}
function openFs(kind) {
S.fsKind = kind;
$("bm-fs").hidden = false;
renderFsPlot();
setTimeout(resizeFs, 30);
}
function closeFs() {
if (!S.built || !$('bm-fs') || $('bm-fs').hidden) return;
S.fsKind = null;
try { Plotly.purge("bm-fs-plot"); } catch (_) {}
$("bm-fs").hidden = true;
}
function renderFsPlot() {
if (!S.fsKind) return;
const titles = { "eff-enc": "Efficiency vs Encode Speed", "eff-dec": "Efficiency vs Decode Speed", "bar-enc": "Encode Speed", "bar-dec": "Decode Speed" };
$("bm-fs-title").textContent = titles[S.fsKind] || "Benchmark plot";
if (S.fsKind === "eff-enc") plotEfficiency("bm-fs-plot", "encode_seconds", "Efficiency vs Encode Speed", "encode seconds");
else if (S.fsKind === "eff-dec") plotEfficiency("bm-fs-plot", "decode_seconds", "Efficiency vs Decode Speed", "decode seconds");
else if (S.fsKind === "bar-enc") plotBar("bm-fs-plot", "encode_seconds", "Encode");
else if (S.fsKind === "bar-dec") plotBar("bm-fs-plot", "decode_seconds", "Decode");
resizeFs();
}
function resizeFs() {
const el = $("bm-fs-plot");
if (el && el.data && !$('bm-fs').hidden) Plotly.Plots.resize(el);
}
return { onShow, onHide, refreshResults };
})();