M5 · Decoding & closed loop
Pretrained decoder demo first, then train your own. End-to-end co-optimization through the differentiable simulator, and the minimal closed-loop demo. This module is optional — it goes deeper than M1–M4 and assumes you are comfortable with model training, so it works best as a self-guided dive after the earlier modules.
01The loop in one picture
The previous four modules ran open loop: image → stim → phosphenes, done. The loop closes when a decoder reads the phosphenes back out and that readout drives the next stim. Without it, a prosthesis can’t cope with drift, adaptation, or a wearer who shifts their gaze mid-sentence.
Hover any node for a one-line tour · click to jump to that section.
What is decoded?
On the page, brightness of the phosphene field, one number per frame. In the notebook, a small CNN reads the canvas and produces a digit, letter, or brightness scalar.
What is fed back?
Two flavours. §02 feeds back stim current via a PID controller, one electrode, one number per frame. §04 feeds back vision-model weights via gradients through the (differentiable) dynaphos simulator.
What is the goal?
Either track a target (PID holding a reference brightness despite adaptation) or recover the input (an end-to-end loss that asks the decoder’s output to match the original image). Both close the loop in the same place.
02PID: control the dynamics
Dynaphos has state. Activation builds up and then leaks; an adaptation trace makes the same current dim over seconds. Open-loop stimulation can’t correct for any of that. A controller can — given one number per frame to read. The cheapest such number is the mean of the rendered phosphene’s pixels: a real decoder (§03) does better, but mean-pixel is enough to close the loop. A classical PID reads it, compares to a target, writes the next stim current. Three gains, one electrode.
Crank Ki to the maximum on the ‘step’ target with Kp and Kd at zero. What does the trace do, and why?
It oscillates and overshoots wildly. Pure integral has no idea where the error is going. It just keeps stuffing accumulated error into the current. By the time the brightness reaches the target, the integral has already commanded a huge overshoot, which has to be unwound with negative error, which then undershoots. Adding a little Kd reads the “we’re moving fast toward the target” signal and pulls back early.
Pure P-only is the simplest, but on the ‘ramp’ target it never quite catches up. Why?
P-only is proportional to the current error. On a ramp, the target keeps moving away, so to make the brightness move at the same rate you need a non-zero error to drive a non-zero control output. That constant gap is the classic steady-state error that integral action exists to eliminate.
03Train your own decoder
The decoder’s job is to take a phosphene render and recover what the user is looking at. Classification is the cleanest version: one digit in, one digit out. The model gets ten training examples (one per digit, MNIST-style, rendered into phosphenes by dynaphos). Hit Train. The weights below update as the network learns to read its own percepts.
What do the first-layer weight maps start to look like after a few hundred training steps?
They develop digit-shaped templates. Some units light up for round shapes (0, 6, 9), some for vertical strokes (1, 7), some for the closed loop of an 8. They’re not pretty after only ten examples, but the structure is unmistakable: the network has carved its hidden layer into class-specific feature detectors. With the full MNIST training set in the notebook, these templates sharpen into proper handwritten-digit prototypes.
04End-to-end through the simulator
So far the decoder has been learning to read a fixed dynaphos render. Because dynaphos is differentiable, gradients flow all the way from a decoder loss back through the simulator into a learnable vision model. Vision model and decoder co-train. The vision model learns what makes the decoder’s job easy, even if that means producing pre-stim patterns that look nothing like Sobel edges.
05The whole loop, live
Everything together, on one canvas, with one play button. An input image gets gaze-cropped, run through the vision model, turned into per-channel stimulation, fed to the dynaphos temporal simulator, and decoded. In closed-loop mode, the decoder reading drives the next stim through the PID from §02. Toggle open / closed to see what the feedback actually does.
06Self-check
Predict the answer first, then verify with the demos above.
Open loop drifts even with a perfect vision model. Why?
Because dynaphos has state. The adaptation trace builds with stimulation; the same current that produced a bright phosphene at t = 0 evokes a dimmer one at t = 2 s. Open loop never sees that drift, so it never corrects. Closed loop reads the decoder, notices the brightness has dropped, and pushes more current in.
When does PID over-correct, and what does the trace look like?
Whenever Ki dominates and there’s any non-zero delay between command and percept. The integral keeps stuffing accumulated error into the current even after the brightness has reached the target, so you get a big overshoot, then a slow climb-down. The §02 PID trace shows this clearly. Pure I gives high-amplitude oscillations; a small Kd term reads the “we’re heading there fast” signal and pulls back early.
After end-to-end training, what does the vision model learn that hand-tuned Sobel doesn’t?
It learns to encode for the decoder it’s coupled to, not for human perception. Sobel produces edge maps that look right to us. The end-to-end vision model is free to produce whatever pre-stim pattern makes the decoder’s job easiest, even if that pattern looks nothing like a Sobel edge. It’s also why end-to-end systems can outperform hand-tuned ones on the actual recovery loss, even though the intermediate stages look weirder.
Which loop ingredient matters most: a better decoder, a better controller, or a better vision model?
Depends on what’s broken. If the decoder is wrong, the loop chases a phantom target (garbage in, garbage out). If the controller is wrong, the loop oscillates or never converges. If the vision model is wrong, the loop converges to a sub-optimal percept. In practice, real systems iterate on all three. End-to-end training (§04) is the one trick that lets them co-improve.
Could you replace PID with a learned controller? What would that look like?
Yes. Train a small recurrent network on the error signal with the same loss as the PID (track the target). It would essentially learn to be a non-linear PID (with state-dependent gains), and could in principle handle the dynaphos non-linearities better than a fixed-gain PID. The cost is opacity. PID gives you three numbers you can reason about; a learned controller is a black box that mostly works. Real prosthesis pipelines tend to use the simplest controller that works.
07Where to next
The notebook is the optional, deeper Python version of everything you just touched. The three workshop tracks pick up from here.
Notebook companion
The page demo trains a 1-layer MLP on synthetic canvases. The notebook trains a small CNN end-to-end through dynaphos on a small real dataset and plots a proper learning curve.
Experimental track
Pick a target image. Compare phosphene renders from hand-tuned vs end-to-end pipelines; collect subjective ratings; quantify the difference.
Developer track
Port §02’s PID to per-channel control on the Utah array from M3. Investigate where it breaks (channel coupling, charge budget). Optionally try a learned gain-scheduler.
08Tools & references
- Tool TensorFlow.js: in-browser training and inference. tensorflow.org/js
- Tool dynaphos: differentiable phosphene simulator used everywhere in this module. neuralcodinglab/dynaphos
- Paper van der Grinten et al. (2024). Towards biologically plausible phosphene simulation. eLife 13:e85812. doi:10.7554/eLife.85812
- Paper de Ruyter van Steveninck, Güçlü, van Wezel & van Gerven (2022). End-to-end optimization of prosthetic vision. J. Vision 22(2):20. doi:10.1167/jov.22.2.20
- Book Åström & Murray, Feedback Systems (open access). Chapters 10–11 on PID design and tuning. fbswiki.org
- Pattern Ziegler & Nichols (1942), Optimum settings for automatic controllers. The original PID-tuning recipe; still useful as a first cut.
Further reading — vision-restoration field
- overview docs/vision-restoration-field.md — programs by tissue (retina · LGN · V1), with links to each company / consortium and a primary-sources list. The decoders and closed loops in this module are what makes any of these implants usable, not just safe.
- Paper Beauchamp et al. (2020), Dynamic stimulation of visual cortex produces form vision in sighted and blind humans, Cell 181(4):774–783.e5. doi:10.1016/j.cell.2020.04.033 — sequential stim along a cortical path produces a perceived line: the “draw on cortex” result this module's PID loop chases.
- Program NeuraViPeR & SIGHTED — the EU H2020 / EIC consortia building the higher-channel cortical and LGN devices these decoders would actually run on.