Below is a small hidden pattern. Press Add noise to corrupt it step by step until it dissolves into static — this is the forward process, and it is easy. Then press Denoise to run the reverse process: at each step the demo nudges every pixel back toward the pattern, and the image slowly reappears.
<p class="hint">A hidden pattern lives in this grid. <b>Add noise</b> corrupts it step by step (the easy forward process) until it becomes static. <b>Denoise</b> runs the reverse process: each step nudges every cell back toward the pattern, and the image re-forms.</p>
<canvas id="grid" width="256" height="256"></canvas>
<div class="meter">noise level: <span id="lvl">0%</span></div>
<div class="status" id="status">Press "Add noise", then "Denoise".</div>
<div class="btns">
<button id="noise" type="button">Add noise</button>
<button id="denoise" type="button">Denoise</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
canvas { width: 256px; height: 256px; image-rendering: pixelated; border-radius: 10px;
border: 1px solid #cdd9e3; display: block; background: #000; }
.meter { font-size: .85rem; color: #1d3557; margin: .5rem 0 .2rem; font-weight: 600; }
.status { font-size: 1rem; font-weight: 600; margin: .3rem 0 .6rem; min-height: 1.4em; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// Toy diffusion model. The "clean" image is a fixed target pattern (a ring + dot).
// Forward process: blend each pixel toward fresh random noise (adds noise, easy).
// Reverse process: blend each pixel back toward the known target (denoise).
// A real diffusion model replaces the "known target" with a neural net that PREDICTS
// the denoising direction from data (Sohl-Dickstein 2015; Ho, Jain, Abbeel 2020).
const G = 32; // grid is 32x32 cells
const canvas = document.getElementById('grid');
const ctx = canvas.getContext('2d');
canvas.width = G; canvas.height = G; // draw at cell resolution, CSS upscales
// Target "image": a circle outline with a bright center, values in [0,1].
const target = new Float32Array(G * G);
for (let y = 0; y < G; y++) for (let x = 0; x < G; x++) {
const dx = x - G/2 + 0.5, dy = y - G/2 + 0.5;
const r = Math.sqrt(dx*dx + dy*dy);
let v = 0;
if (r < 3) v = 1; // center dot
else if (r > 9 && r < 12) v = 0.85; // ring
target[y*G + x] = v;
}
let img = target.slice(); // current canvas state
let noise = 0; // 0..1 conceptual noise level
function rand() { return Math.random(); }
function clamp(v){ return v < 0 ? 0 : v > 1 ? 1 : v; }
function render() {
const id = ctx.createImageData(G, G);
for (let i = 0; i < G*G; i++) {
const c = Math.round(clamp(img[i]) * 255);
id.data[i*4+0] = c; id.data[i*4+1] = c;
id.data[i*4+2] = Math.min(255, c + 30); // slight cool tint
id.data[i*4+3] = 255;
}
ctx.putImageData(id, 0, 0);
document.getElementById('lvl').textContent = Math.round(noise*100) + '%';
}
function setStatus(t){ document.getElementById('status').textContent = t; }
// Forward step: move each pixel a fraction toward fresh random noise.
function addNoise() {
const beta = 0.25;
for (let i = 0; i < G*G; i++) img[i] = clamp(img[i]*(1-beta) + rand()*beta);
noise = clamp(noise + beta*0.9);
render();
setStatus(noise > 0.85 ? 'Almost pure static — now try Denoise.' : 'Adding noise (forward process)...');
}
// Reverse step: move each pixel a fraction back toward the target (learned direction).
// The noise counter winds down slower than alpha, so several reverse steps run and
// the image fully re-forms — mirroring the hundreds of steps a real model takes.
function denoise() {
if (noise <= 0.001) { setStatus('Already clean. Add noise first.'); return; }
const alpha = 0.45;
for (let i = 0; i < G*G; i++) img[i] = clamp(img[i]*(1-alpha) + target[i]*alpha);
noise = clamp(noise - 0.12);
render();
setStatus(noise < 0.05 ? '✓ Image recovered from noise!' : 'Denoising (reverse process)...');
}
function reset(){ img = target.slice(); noise = 0; render(); setStatus('Press "Add noise", then "Denoise".'); }
document.getElementById('noise').addEventListener('click', addNoise);
document.getElementById('denoise').addEventListener('click', denoise);
document.getElementById('reset').addEventListener('click', reset);
reset();
<p class="hint">A hidden pattern lives in this grid. <b>Add noise</b> corrupts it step by step (the easy forward process) until it becomes static. <b>Denoise</b> runs the reverse process: each step nudges every cell back toward the pattern, and the image re-forms.</p>
<canvas id="grid" width="256" height="256"></canvas>
<div class="meter">noise level: <span id="lvl">0%</span></div>
<div class="status" id="status">Press "Add noise", then "Denoise".</div>
<div class="btns">
<button id="noise" type="button">Add noise</button>
<button id="denoise" type="button">Denoise</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
canvas { width: 256px; height: 256px; image-rendering: pixelated; border-radius: 10px;
border: 1px solid #cdd9e3; display: block; background: #000; }
.meter { font-size: .85rem; color: #1d3557; margin: .5rem 0 .2rem; font-weight: 600; }
.status { font-size: 1rem; font-weight: 600; margin: .3rem 0 .6rem; min-height: 1.4em; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// Toy diffusion model. The "clean" image is a fixed target pattern (a ring + dot).
// Forward process: blend each pixel toward fresh random noise (adds noise, easy).
// Reverse process: blend each pixel back toward the known target (denoise).
// A real diffusion model replaces the "known target" with a neural net that PREDICTS
// the denoising direction from data (Sohl-Dickstein 2015; Ho, Jain, Abbeel 2020).
const G = 32; // grid is 32x32 cells
const canvas = document.getElementById('grid');
const ctx = canvas.getContext('2d');
canvas.width = G; canvas.height = G; // draw at cell resolution, CSS upscales
// Target "image": a circle outline with a bright center, values in [0,1].
const target = new Float32Array(G * G);
for (let y = 0; y < G; y++) for (let x = 0; x < G; x++) {
const dx = x - G/2 + 0.5, dy = y - G/2 + 0.5;
const r = Math.sqrt(dx*dx + dy*dy);
let v = 0;
if (r < 3) v = 1; // center dot
else if (r > 9 && r < 12) v = 0.85; // ring
target[y*G + x] = v;
}
let img = target.slice(); // current canvas state
let noise = 0; // 0..1 conceptual noise level
function rand() { return Math.random(); }
function clamp(v){ return v < 0 ? 0 : v > 1 ? 1 : v; }
function render() {
const id = ctx.createImageData(G, G);
for (let i = 0; i < G*G; i++) {
const c = Math.round(clamp(img[i]) * 255);
id.data[i*4+0] = c; id.data[i*4+1] = c;
id.data[i*4+2] = Math.min(255, c + 30); // slight cool tint
id.data[i*4+3] = 255;
}
ctx.putImageData(id, 0, 0);
document.getElementById('lvl').textContent = Math.round(noise*100) + '%';
}
function setStatus(t){ document.getElementById('status').textContent = t; }
// Forward step: move each pixel a fraction toward fresh random noise.
function addNoise() {
const beta = 0.25;
for (let i = 0; i < G*G; i++) img[i] = clamp(img[i]*(1-beta) + rand()*beta);
noise = clamp(noise + beta*0.9);
render();
setStatus(noise > 0.85 ? 'Almost pure static — now try Denoise.' : 'Adding noise (forward process)...');
}
// Reverse step: move each pixel a fraction back toward the target (learned direction).
// The noise counter winds down slower than alpha, so several reverse steps run and
// the image fully re-forms — mirroring the hundreds of steps a real model takes.
function denoise() {
if (noise <= 0.001) { setStatus('Already clean. Add noise first.'); return; }
const alpha = 0.45;
for (let i = 0; i < G*G; i++) img[i] = clamp(img[i]*(1-alpha) + target[i]*alpha);
noise = clamp(noise - 0.12);
render();
setStatus(noise < 0.05 ? '✓ Image recovered from noise!' : 'Denoising (reverse process)...');
}
function reset(){ img = target.slice(); noise = 0; render(); setStatus('Press "Add noise", then "Denoise".'); }
document.getElementById('noise').addEventListener('click', addNoise);
document.getElementById('denoise').addEventListener('click', denoise);
document.getElementById('reset').addEventListener('click', reset);
reset();
Notice the asymmetry. Destroying the image is trivial — just keep adding randomness. Re-creating it is the hard part, and it only works because each reverse step "knows" which way to push. A real diffusion model replaces that hand-coded nudge with a giant neural network that has learned the right direction from data. Same loop, vastly more knowledge baked into one step.
Comments
Loading comments...