Give it a photo
Any photo works, but treat this as a memory made real, not a print of your original — the goal was never a pixel-perfect copy.
Works well: one clear subject filling most of the frame — a face, a pet, a skyline, a single object — with decent light and a simple background. Works less well: busy group shots, subjects that are small or far away, heavy text, or anything so dark or blurry the subject isn't obvious to begin with. The network only gets 256 numbers to describe whatever you give it — the simpler the ask, the more faithful the impression.
Pick the part that matters
The network only works with square photos. Drag the box to reposition it, and use the slider to resize.
Watch it learn your photo
The big network — the decoder — was already trained, once, offline, and is frozen for good. What trains now, live, right here, is much smaller: 256 numbers (and later, 454 more) getting nudged by gradient descent, over and over, until the frozen decoder's output matches your photo as closely as it can. First an instant forward pass gives those 256 numbers a rough starting point; then a few hundred training steps refine them, and a few hundred more train a small personalization on top — usually 30-120 seconds total. Every pixel below is the network's actual output at that point in training, not a lookup or a filter, and the chart tracks the error dropping step by step so you can watch the descent itself, not just its result.
Take the impression with you
None of the three QR codes below contain your photo — only numbers, rebuilt through the same frozen decoder anyone else on this page has. The first two split into a matched pair on purpose: think of it like a locket that comes in two halves. The essence, on its own, is already a complete little impression — soft, but real. The key, on its own, is nothing at all — just 454 meaningless numbers with no photo to apply them to. Give one half to one person and the other half to someone else, and only together do the two scans reveal the sharper, truer version. The third code just carries both halves at once, for a single keepsake.
This is the part that's actually fun to give someone. Here's the whole move, start to finish:
- Download the essence QR above and the impression image from the training step, and print them together onto one gift — a mug, a print, whatever Zazzle will put an uploaded image on.
- Download the key QR and print it onto a second gift — it doesn't need the photo on it at all, since it's meaningless without the first piece.
- Give one gift to one person, the second to another. (Or keep both — plenty of people just like owning the whole locket themselves.)
- To see the sharp version: come back to this page, scan the essence gift first, then scan the key gift — they'll combine automatically, right in the "load a shared impression" box below.
Open a Zazzle "upload your own image" template → — works for a mug, and most of Zazzle's other blank templates take the same kind of upload, so pick two different products if you want the pair to feel like separate gifts.
Prefer one object instead of two? Some mug templates print two separate images — one per side — rather than a single wraparound design. If you find one of those, put the key QR on one side and the essence QR (plus the impression, if there's room) on the other, and the whole locket lives on a single mug: nobody can unlock the sharp version from only one side of their own coffee cup, but scanning both sides back here does it. Check the product listing for "front and back" or "two-sided" printing before assuming any given template supports this — plenty only take one image.
Load a shared impression instead
Drop in a photo of one QR code to load it — the essence alone reconstructs something on its own. Then drop in a second QR photo (the matching key, or a "both together" code) and it upgrades automatically. Order doesn't matter, and you can drop both at once.
One honest caveat: this page, the frozen decoder, and the exact QR format it reads are all things one person maintains for fun, not a permanent service with any guarantees. It could change or disappear entirely someday — printed impressions will still be lovely on their own, but a future version of this page may not be able to decode an old QR code. Keep the original photo too, if that would ever matter.
Drive the latent vector yourself
Before dragging anything: the latent space is the 256-dimensional room the decoder actually lives in. It isn't pixels and it isn't your photo — it's the network's own compressed sense of what a photo can mean, shaped entirely by the thirteen thousand photos it trained on. Your photo landed at one specific point in that room. Every nearby point decodes to something too — a plausible image the network can imagine, blending smoothly into whatever's next to it. This section lets you leave that point on purpose and see what's nearby.
The grid below is the same 256 numbers as the live heatmap above, plotted on the exact same black-to-white scale — black is this scale's minimum, white is its maximum, and that's the entire axis every dimension lives on. Drag across the grid to select a region (a single click selects one cell; shift-drag adds a second region without losing the first). Then drag the slider — styled as that same black-to-white scale, on purpose — to set exactly where the selected cells sit on it. Everything unselected stays exactly where the search left it.
This is an absolute position, not a nudge: dragging sets every selected cell to this exact point on the black-to-white scale, all at once. Selecting a new region snaps the slider to show where that region already sits, so nothing jumps until you actually drag it.
How this actually works — training vs. rendering, explained
A neural network is just a pile of adjustable numbers that gets tuned until it's good at some task. A CNN — convolutional neural network — is the flavor built for images: it slides small pattern-detectors across the picture, finding edges first, then shapes, then whole recognizable chunks. An autoencoder bolts two CNNs back to back — one that squeezes an image down to a short list of numbers, one that expands that list back into an image — and trains them together until the round trip barely loses anything. That short list is the latent space: not pixels, but the network's own compressed sense of what the image is. Afterimage freezes the expanding half and, per photo, searches for the short list that makes it produce your picture.
There are two completely separate phases here, and only one of them ever touches your photo.
Training happened once, ahead of time, on a GPU, using 13,000 ordinary photos of animals, vehicles, and people — never yours. A pair of networks, an encoder and a decoder, learned together to compress a photo down to 256 numbers and rebuild it from just those numbers.
Rendering is what happens in your browser just now. Both networks came along, but only one of them touches your final result: the encoder runs once, instantly, purely to get the search started somewhere reasonable instead of from nothing. Every pixel of the actual answer comes from the decoder being nudged, step by step, toward your photo — a slower process, but one that measurably beats trusting the encoder's one-shot guess. The last stretch of that search also fits 454 extra numbers — a personalization layered onto the decoder's last three layers — which is why the image kept sharpening even after the main shape had already settled.
Coral is the encoder and teal is the decoder — the two networks' weights, the numbers that define what each layer computes. Those are byte-for-byte identical no matter whose browser is running it, forever: they were fitted once, offline, on 13,000 photos that were never yours, and never change again. Everything marked ★ UNIQUE TO YOU depends on your specific photo instead: the gray blocks are your actual photo pixels going in and the decoder's actual output coming out; the violet 256-number latent vector is what the encoder reduces your photo to, and no one else's photo reduces to that same vector; and the 454-number personalization, fused onto the decoder's last three layers, is fitted live in your browser from your latent vector alone. That head is also the only piece that gets trained twice over — first the 256 numbers alone, then this fitted on top of them. You already saw what difference it makes in the "latent only" vs. "latent + head" comparison back in the training step — that gap is entirely this one adapter, sitting on top of decoder weights that stayed exactly the same either way.
Debug: reference latent check
Loaded via ?debugLatent=reference. The two images below should match
pixel-for-pixel — if they don't, the JavaScript decoder has diverged from the exported weights.