Grading diabetic eye disease from a single retina photo.
Diabetes can quietly damage the retina, and the damage is visible in a photograph of the back of the eye long before sight is lost. This page grades such a photo into the five standard stages, shows where the network looked, and tells the story of how the model was built: every mistake included.
The model runs in your browser
Pick a sample or drop in your own retina (fundus) photo. The whole pipeline runs on this device with WebAssembly; the photo is never uploaded anywhere. The first grading downloads the model once (47 MB).
1 · Choose a photo
Sample photos are test images from the APTOS 2019 dataset (Kaggle), shown for coursework review. The number is the grade a doctor gave.
Referral cut-off
Where on the severity score a patient is sent to a specialist. Lower refers more people: fewer missed patients, more unnecessary visits. Measured on 523 validation photos.
Four agents turn a grade into a brief
A small multi-agent system built with LangGraph. The agents share one case file (the state), each does one job (a node), and they hand the case on (edges). The safety reviewer can send a draft back once, a small debate that catches claims the facts do not support. They receive only the CNN's numbers, never the photo.
Language model: Gemma 4 26B A4B on Cloudflare Workers AI, thinking switched off, low temperature. A student prototype and a screening aid, not a diagnosis.
Questions about how it was built
A guide that answers from the project's own results and notes, in English, සිංහල or Singlish. It does not give advice about anyone's eyes.
3,662 photos, one screening programme
APTOS 2019 is a set of retina photos from an eye-screening programme in rural India, each graded by a doctor from 0 (no disease) to 4 (proliferative, where fragile new vessels grow and bleed). Before any training, the photos were audited.
Photos per grade
Half are healthy; the grades a screening programme most needs to catch are the rarest.
Audit before training
Every photo was fingerprinted twice: an exact pixel hash, and a "looks-alike" hash that survives resizing. Copies of the same eye were grouped so no copy can sit in training while its twin is tested, and groups whose copies disagreed on the grade were dropped. The split was then fixed once and never changed.
A model can pass by looking at the camera
The photos come from 17 different cameras, and some cameras photographed almost only healthy eyes. A rule that never looks at the eye, only at the photo's size, already scores well. So every model here is also scored on the "mixed-camera" test photos, whose camera does not give the grade away.
Grades seen by each camera (training photos)
A camera is "telling" when one grade makes up 90% or more of its training photos. The honest yardstick is how well a model does on the other photos: there, recognising the camera does not help. The first models in the journey barely beat the camera rule on those photos; transfer learning was the first real step past it.
From textbook basics to the final model
Each version changed one idea and was archived, mistakes included. Settings were chosen on validation photos; the test photos were scored once per version. Whiskers show the 95% confidence interval, so two versions whose whiskers overlap a lot are not really different.
Show as a table
QWK (quadratic weighted kappa) measures agreement with the doctor from 0 (chance) to 1 (perfect); a miss by two grades costs four times a miss by one.
Trained offline, served as plain files
Training ran as plain Python scripts with PyTorch on a Mac. The finished model, with its eight views and heatmap built in, was exported to one ONNX file and checked against the archived results: all 1,046 validation and test photos get the same grade in the browser as in Python.
Why the heatmap needs no gradients
Grad-CAM weighs each of the network's last 2,048 feature maps by how much it pushes the score. This model ends by averaging each map and taking one weighted sum, so that push is simply the map's weight. Grad-CAM therefore equals CAM, the weights applied at each of the 7 × 7 positions, and it was built into the exported file. Checked: correlation 0.9999 with the Python Grad-CAM.
Checked, not assumed
The browser code was run on the 1,046 original validation and test photos in Chrome and compared with the archived Python results: the same grade for every photo, test QWK 0.925 in both. Tiny pixel differences remain inside OpenCV's CLAHE (vector code on the Mac, plain code in WebAssembly); they move scores by 0.002 on average and never change a grade.
What this is not
Not a medical device
A student prototype. It has not been clinically validated and must not be used to make decisions about anyone's eyes.
One programme, one population
Trained on about 2,400 photos from one screening programme. How it behaves on other cameras, clinics or populations is unknown.
Rare grades are weakest
Severe eyes are only 5% of the data, and Mild is the grade it most often misses. Most mistakes are one grade off; the referral decision is more reliable than the exact grade.
Heatmaps show where, not why
A heatmap on a lesion is reassuring, but it cannot prove the model ignored a shortcut hidden in colour or texture. That is why the mixed-camera score exists.