Four brightness illusions, each predicted live by a model of retinal ganglion cells.
The Hermann grid, the scintillating grid, Mach bands and the Cornsweet edge sit beside a model of the retina. Each stimulus is rendered to a 200 x 200 luminance grid and convolved with a difference of Gaussians, a narrow excitatory centre minus a wider inhibitory surround, using true separable Gaussians whenever a slider moves. The model percept adds that response back to the image, and gray spots appear at the grid crossings and bright and dark bands at the knees of the Mach ramp; for the Cornsweet edge a retinex fill-in keeps only steep gradients and integrates them, turning the cusp into a step. The probe shows one cell's centre and surround sums and fires Poisson spikes at its predicted rate, and the model admits where it fails: wavy streets erase the Hermann spots for people, but not for the DoG.
Try it. Move over either panel to place the receptive field probe and read its response and spike train. Switch illusions with the tabs or keys 1 to 4, and drag the sliders to strengthen, weaken or switch off each effect. V swaps between the ganglion response map and the model percept, up and down arrows resize the receptive field, and C covers the Cornsweet edge.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a demo that explains the Hermann grid illusion with a model of the retina, using JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Make a canvas that fills the window, stays sharp on high-DPI screens (scale by devicePixelRatio), and resizes with the window. Use a dark background.
- On the left, draw a Hermann grid: black squares separated by white streets, about 6 x 6 squares in a square panel.
- Render the same grid into a 200 x 200 Float32Array of luminance values between 0 and 1.
- Write a separable Gaussian blur yourself (a 1D kernel applied to rows, then to columns). Blur the grid twice, once with a small sigma (the receptive field centre) and once with a sigma three times larger (the surround), and subtract: that difference of Gaussians is the response of an ON-centre retinal ganglion cell.
- On the right, draw the response as an image: blue for negative, black for zero, orange for positive. The street crossings should come out dimmer than the middle of the streets.
Once that works, make it beautiful:
- Add a model percept view: the image compressed toward mid gray plus the normalised response. The gray spots at the crossings should appear in the computed picture.
- Draw the receptive field under the mouse as a green centre and a red surround ring, and print the centre, surround and response values for that spot.
- Add sliders for street width and receptive field size, and recompute only when they change.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as Mach bands and the Cornsweet edge, a scrolling spike train for the probed cell, or wavy streets that fool the model but not people.