Drop in a photo of an instrument cluster. A detector boxes each warning light it recognises and says in plain words what it means and how urgent it is. It began as a training notebook and a phone export. Rebuilding it as a service showed me what its old score had really been measuring, and led me to an earlier checkpoint of the same project that had learned more. Telltale is the car engineer's word for a warning light.
The samples are plain phone photos from the test part of the dataset. The model did not train on these files, but it did train on other shots of the same dashboards taken seconds apart, so they flatter it. Your own photo is the fairer test. It is sent to this server, processed in memory and never written to disk, and nothing is kept once the answer has been sent back.
This was a company project from the end of 2023: a model for a phone app that reads dashboard warning lights. The score on record looked healthy. Scoring the exported file again, against the dataset as it was published, told a different story. The first version of this page served that file. What runs here now is an earlier checkpoint of the same project, one that had been shown every symbol.
The training log ends with a mean average precision of 0.83 on the validation pictures, with precision 0.82 and recall 0.78. There were only 91 validation pictures, so each of those figures can move a few points on a different sample.
In the project's working copy of the dataset, every label for 29 of the 50 symbols had been deleted: the rare ones, with only a handful of examples each. They were gone from the validation and test files and from all 300 training files that had one. The class list still said 50. So, as far as I can tell, the final training runs never showed the model those symbols, and the score never asked about them.
The dataset as published still marks the rare symbols. Scored against it by the same library, the same file reaches 0.49 on the same 91 pictures. Nothing got worse. The 21 symbols it was taught are found about as well as the log says, and the other 29 are never found: 0 of 40 labelled rare lights in the test pictures.
The project folder still held the weights of the training runs from before the labels were deleted. Four of them had learned every symbol, and they score about the same. I took the one whose class list matches the published one by name. It trained at the end of 2023 on the full published labels, with the same split of pictures, so none of the test pictures were in its training data.
Through this service, on the same 129 test pictures and at the same settings, it names 32 kinds of light against 21, and finds 18 of 40 rare lights where the final run found none. Its F1 is 0.78 against 0.78: it finds more and is wrong a little more often. On the 21 common symbols alone it is a little behind, an F1 of 0.79 against 0.80. A light the model cannot name at all is a worse failure than one named with slightly less care, so this is the model that runs here now.
The model was trained in a hosted notebook with Ultralytics YOLOv8 and exported for a phone app. Nothing could be tried without the notebook, a graphics card session and the training framework. The PyTorch weights of the final run are no longer in the project folder. What is left of that run is the ONNX file that the exporter writes on its way to a TensorFlow Lite model. The earlier run still has its weights.
The final run's file gives its boxes as fractions of the picture instead of pixels and carries no class names. Loaded with the library that made it, every box collapses to a point in the top left corner and its own validator reports a precision and recall of zero. Once the units are corrected the file is fine. The earlier run I exported again from its weights. It learned the first version of the published labels, with 52 classes, and the second version merged a misspelt copy of Service due into the real one and dropped one symbol. So the end of the file has a small added step that maps its 52 scores onto the 50 published classes, keeping the higher score where two became one, checked against the same mapping done in numpy.
Fitting the photo into a 640 pixel square, reading the 8,400 candidate boxes out of the raw output, class aware non maximum suppression and mapping the boxes back onto the photo are written in numpy and Pillow. The server needs ONNX Runtime and nothing else: no Ultralytics, no PyTorch, no OpenCV.
The model loads on the first request and stays in memory. One photo is processed at a time on three threads, and requests are rate limited per address.
I tried storing the weights at half precision and two kinds of 8 bit quantisation, and compared every detection with the original file before deciding which one to serve.
It finds the common lights well: engine, oil, battery, brakes, ABS, airbag, seatbelt, tyre pressure, doors, temperature, fuel, stability control and a few more. It also knows some rarer ones, such as fog lights, glow plugs, hill assist and brake wear, but it found fewer than half of the rare lights in the test pictures. An uncommon symbol is still more often missed than found, without any warning, or now and then named as the nearest common one. A light it does not box is not a light that is off.
Reflections on the glass, glare around a light at night, motion blur and symbols that are only a few pixels across all cost it detections. It was trained on pictures shrunk to 416 pixels, most of them screenshots, so a sharp close photo of a real cluster is somewhat outside what it has seen.
It does not read the colour of a light or whether it is flashing, and both change the meaning. The descriptions are general ones that I wrote, and the same symbol differs between makes. The owner's manual for the car is the authority.
The validation and test pictures are few, and many of them are screenshots taken moments apart from pictures in the training set. I also chose between the two models with these pictures in view, which flatters the one I chose a little. Treat the scores as a check that the service is wired correctly, not as a promise about your car.
This is not a diagnostic tool and it cannot see what is wrong with a car. If a red light is on, stop when it is safe and look it up in the manual or call a mechanic.
Dataset: Enhanced Dashboard DS from Roboflow Universe, licensed CC BY 4.0. The model trained on its version 1; the scores, the class list and the samples use version 2. The sample photos come from its test split, trimmed to remove the black border. Model trained with Ultralytics YOLOv8, which is licensed AGPL 3.0.