File size: 9,809 Bytes
b454846 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e c9c9700 0e1903e c9c9700 0e1903e c9c9700 0e1903e c9c9700 0e1903e c9c9700 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb 0e1903e 264e8fb b454846 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MOZAIC Sample Explorer</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<header>
<h1>MOZAIC sample explorer</h1>
<p>An audio-visual deepfake benchmark: every clip pairs a real or fake face with a real or fake voice.</p>
<span class="model">Evaluated model <code id="model">…</code></span>
</header>
<nav class="tabs" role="tablist" id="tabs"></nav>
<section id="panel"></section>
<p class="fine">Research demo. Fake clips are synthetic media made for detection research; they do not show anything
the people in them said or did. A pilot this small is far too few for conclusions.</p>
</main>
<script>
const TASKS = {
t1: ["T1 · Spot the fake", "Which part is fake: the video, the audio, both, or neither?", "32 frames and the audio."],
t2: ["T2 · Manipulation type", "Which part of this video, if any, was generated or altered by AI?", "32 frames, no sound."],
t3: ["T3 · Identity check", "Is clip 1 real or fake, and is it the same person as clip 2?", "Face: frames of both clips. Voice: sound of both clips."],
t4: ["T4 · Explanation", "What is fake, where, and what in the clip shows it?", "Reference answers, annotated twice on the same clips: v5 and v7."],
};
const LABELS = { RVRA: "real video · real audio", RVFA: "real video · fake audio",
FVRA: "fake video · real audio", FVFA: "fake video · fake audio" };
const METHODS = { real: "real", facefusion: "FaceFusion", s2v: "Wan2.2-S2V", animate: "Wan2.2-Animate", seed_vc: "Seed-VC" };
const esc = s => String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
const $ = id => document.getElementById(id);
function clipDesc(clip) {
const [label, method, frames, arm] = clip.replace(/\.mp4$/, "").split("__");
const parts = [LABELS[label] || label, METHODS[method] || method, frames.replace("f", " frames")];
if (arm && arm !== "main") parts.push(`${arm} audio`);
return parts.join(" · ");
}
function video(src, caption) {
return `<figure><video controls preload="metadata" playsinline src="clips/${encodeURIComponent(src)}"></video>
<figcaption>${esc(caption)}</figcaption></figure>`;
}
function stat(value, label) { return `<div class="stat"><b>${value}</b><span>${esc(label)}</span></div>`; }
let DATA, state = { task: null, i: 0, cell: "", flagged: false };
function renderTabs() {
$("tabs").innerHTML = "";
for (const [key, [title]] of Object.entries(TASKS)) {
if (!DATA.tasks[key] || !DATA.tasks[key].items.length) continue;
const b = document.createElement("button");
b.role = "tab"; b.textContent = title; b.setAttribute("aria-selected", key === state.task);
b.onclick = () => { state = { task: key, i: 0, cell: "", flagged: false }; renderTabs(); render(); };
$("tabs").appendChild(b);
}
}
function navBar(items, label, extra = "") {
const opts = items.map(([i, text]) => `<option value="${i}" ${i === state.i ? "selected" : ""}>${esc(text)}</option>`).join("");
return `<div class="bar">${extra}
<button id="prev" aria-label="Previous">◀</button>
<select id="pick" aria-label="${esc(label)}">${opts}</select>
<button id="next" aria-label="Next">▶</button></div>`;
}
function wireNav(order, redraw) {
const pos = Math.max(0, order.indexOf(state.i));
const go = d => { state.i = order[(pos + d + order.length) % order.length]; redraw(); };
$("prev").onclick = () => go(-1);
$("next").onclick = () => go(1);
$("pick").onchange = e => { state.i = +e.target.value; redraw(); };
}
/* ---------- T1 to T3: questions with a right answer ---------- */
function renderScored() {
const key = state.task, task = DATA.tasks[key], s = task.scores, it = task.items[state.i];
const [, question, inputs] = TASKS[key];
const order = task.items.map((_, i) => i);
const nav = navBar(task.items.map((x, i) =>
[i, `${x.correct ? "✓" : "✗"} #${i + 1}${x.part ? ` [${x.part}]` : ""} ${clipDesc(x.clips[0])}`]), "Sample");
$("panel").innerHTML = `
<p class="intro">${esc(question)} <span>Input: ${esc(inputs)}</span></p>
<div class="stats">
${stat(`${Math.round(s.accuracy * s.n)}/${s.n}`, "correct")}
${stat(s.accuracy.toFixed(2), `accuracy (95% CI ${s.ci95[0].toFixed(2)}–${s.ci95[1].toFixed(2)})`)}
${stat(s.balanced_accuracy.toFixed(2), "balanced accuracy")}
${stat(s.cohen_kappa.toFixed(2), "Cohen's κ")}
</div>
${nav}
<div class="clips">${it.clips.map((c, n) => video(c, it.clips.length > 1 ? `Clip ${n + 1}` : clipDesc(c))).join("")}</div>
<div class="card">
<div class="chips">${it.options.map(o => `<span class="chip">${esc(o)}</span>`).join("")}</div>
<div class="answer">
<div><span>Correct answer</span><b>${esc(it.gt_answer)}</b></div>
<div><span>Model answered</span><b>${esc(it.pred_answer ?? "unreadable")}</b></div>
<div class="result ${it.correct ? "ok" : "bad"}">${it.correct ? "✓ correct" : "✗ wrong"}</div>
</div>
<details><summary>About the clips (hidden from the model)</summary>
<ul>${it.clips.map((c, n) => `<li>Clip ${n + 1}: ${esc(clipDesc(c))}</li>`).join("")}</ul></details>
<details><summary>Model's raw reply</summary><pre>${esc((it.raw || "").trim())}</pre></details>
</div>`;
wireNav(order, renderScored);
}
/* ---------- T4: v5 and v7 side by side ---------- */
function partBlock(name, part, withWhere) {
if (!part) return `<div class="part"><div class="part-name">${name}</div><p class="none">no reply yet</p></div>`;
const chips = part.artifacts.length
? `<div class="chips">${part.artifacts.map(a => withWhere
? `<span class="chip">${esc(a.artifact)}<small>${esc(a.where)} · ${esc(a.when)}</small></span>`
: `<span class="chip">${esc(a)}</span>`).join("")}</div>`
: `<span class="none">no artifact</span>`;
return `<div class="part"><div class="part-name">${name}</div><p>${esc(part.text)}</p>${chips}</div>`;
}
function renderT4() {
const task = DATA.tasks.t4, s = task.summary;
const cells = [...new Set(task.items.map(x => x.cell))].sort();
const order = task.items.map((x, i) => [x, i])
.filter(([x]) => (!state.cell || x.cell === state.cell) && (!state.flagged || x.flags.length))
.map(([, i]) => i);
if (!order.length) { state.flagged = false; state.cell = ""; return renderT4(); }
if (!order.includes(state.i)) state.i = order[0];
const it = task.items[state.i];
const filters = `
<select id="cell" aria-label="Kind"><option value="">All kinds (${task.items.length})</option>
${cells.map(c => `<option value="${c}" ${c === state.cell ? "selected" : ""}>${esc(c.replace("_", " · "))}</option>`).join("")}
</select>
<label><input type="checkbox" id="flagged" ${state.flagged ? "checked" : ""}> flagged only</label>`;
const nav = navBar(order.map(i => {
const x = task.items[i];
return [i, `${x.flags.length ? "⚠ " : ""}#${i + 1} ${clipDesc(x.clips[0])}`];
}), "Clip", filters);
const fact = p => {
const f = it.facts[p];
return `<span class="badge ${f.fake ? "fake" : "real"}">${p} · ${f.fake ? "fake · " + esc(f.region) : "real"}</span>`;
};
const flagCount = Object.values(s.flags).reduce((a, b) => a + b, 0);
$("panel").innerHTML = `
<p class="intro">The same ${s.n} clips, annotated twice. What is fake comes from metadata; the evidence is written
by the annotator (<code>${esc(s.annotator)}</code>), who is told the facts.</p>
<div class="stats">
${stat(s.n, "clips")}
${stat(`${s.v7_answered}/${s.n}`, "answered by v7")}
${stat(s.flags["v5: voice-face mismatch, but face and voice are one person"] || 0, "v5 voice-face tags that are wrong")}
${stat(flagCount, "clips flagged")}
</div>
${nav}
<div class="clips">${it.clips.map((c, n) => video(c, n === 0 ? `Clip to explain · ${clipDesc(c)}` : "Real recording it was made from (reference)")).join("")}</div>
<div class="facts">${fact("video")}${fact("audio")}</div>
${it.flags.map(f => `<div class="flag">⚠ ${esc(f)}</div>`).join("")}
<div class="compare">
<div class="card"><h3>v5<small>previous annotation</small></h3>
${partBlock("Video", it.v5.video, false)}${partBlock("Audio", it.v5.audio, false)}</div>
<div class="card"><h3>v7<small>current annotation</small></h3>
${partBlock("Video", it.v7 && it.v7.video, true)}${partBlock("Audio", it.v7 && it.v7.audio, true)}</div>
</div>
<details><summary>What was changed, from metadata</summary><ul>
${["video", "audio"].map(p => `<li><b>${p}:</b> ${esc(it.facts[p].fake ? it.facts[p].changed + "; " + it.facts[p].original : it.facts[p].original)}</li>`).join("")}
</ul></details>
<details><summary>Artifact categories over all ${s.n} clips, v5 vs v7</summary>
<table><tr><th>Category</th><th class="num">v5</th><th class="num">v7</th></tr>
${s.categories.map(([c, a, b]) => `<tr><td>${esc(c)}</td><td class="num">${a}</td><td class="num">${b}</td></tr>`).join("")}
</table></details>`;
$("cell").onchange = e => { state.cell = e.target.value; renderT4(); };
$("flagged").onchange = e => { state.flagged = e.target.checked; renderT4(); };
wireNav(order, renderT4);
}
function render() { return state.task === "t4" ? renderT4() : renderScored(); }
fetch("samples.json").then(r => r.json()).then(d => {
DATA = d;
$("model").textContent = d.model;
state.task = Object.keys(TASKS).find(k => d.tasks[k] && d.tasks[k].items.length);
renderTabs(); render();
}).catch(e => { $("panel").textContent = "Could not load samples.json: " + e; });
</script>
</body>
</html>
|