|
Download README.md from lyte-codes/clockface: direct link, hf CLI and curl.
- Browser
- Download file 5.39 kB
-
https://huggingface.co/lyte-codes/clockface/resolve/98f945c8702fd79792ed3cbc1718052d3cb12fd2/README.md
- Command line
-
hf download hf://lyte-codes/clockface@98f945c8702fd79792ed3cbc1718052d3cb12fd2/README.md
-
curl -L -o README.md https://huggingface.co/lyte-codes/clockface/resolve/98f945c8702fd79792ed3cbc1718052d3cb12fd2/README.md
5.39 kB
| license: apache-2.0 | |
| datasets: | |
| - lyte-codes/clockface-synth-dataset-10k | |
| pipeline_tag: image-to-text | |
| tags: | |
| - analog-clock | |
| - time-reading | |
| - sim2real | |
| # clockface | |
| Reads an analog clock face and returns the time. Two stages: find the dial, | |
| then read it. 11.5 MB of weights in total. | |
| > **This model does not work well on real photographs.** On 1,728 real photos it | |
| > scores **155.9 minutes** mean error, against **180 minutes for guessing at | |
| > random**. It scores 4.8 minutes on the synthetic data it was trained on. It is | |
| > published because the gap between those two numbers is the useful part. | |
| Version 263701. Trained on synth v2, revision `main` of the dataset above. | |
| ## The metric | |
| A clock face is a ring of 720 minutes. Error is the shorter way round it, so it | |
| lies in [0, 360] and 11:58 against 12:02 is 4 minutes, not 716. AM/PM does not | |
| exist: a face cannot show it, so 3:47 and 15:47 are the same reading. | |
| Guessing uniformly at random scores 180 minutes. That is the number to beat, and | |
| it is the number this model is measured against. | |
| ## Results | |
| ### Real photographs (1,728 images, third-party labels) | |
| | | MAE | median | within 5 min | worse than 30 min | | |
| |---|---|---|---|---| | |
| | two-stage | 155.9 min | 139.9 | 11.4% | 82.2% | | |
| | whole image, no locator | 151.8 min | 133.7 | 9.2% | 82.5% | | |
| | **random guessing** | **180 min** | — | — | — | | |
| Sources: 1,092 film stills from Marclay's *The Clock*, 506 photographs from | |
| `moondream/analog-clock-benchmark`, 131 cropped faces from | |
| `oliverj990/clock-faces-v1-times`. Their labels are third party and mostly | |
| unverified; restricted to the 106 labels checked by hand against the image, the | |
| result is MAE 148.5, so label noise does not explain it. | |
| By source, the pattern is a domain gap and not much else: | |
| | source | MAE | within 5 min | | |
| |---|---|---| | |
| | cropped clock faces (dial fills frame) | 87.8 min | 45.0% | | |
| | photographs of scenes | 163.3 min | 9.3% | | |
| The closer an image is to the synthetic framing, the better it does. | |
| ### Synthetic validation (1,200 held-out renders) — training telemetry only | |
| | | MAE | median | within 1 min | within 5 min | | |
| |---|---|---|---|---| | |
| | ground-truth crops | 4.82 min | 0.42 | 79.0% | 92.5% | | |
| | end to end | 5.07 min | 0.43 | 79.0% | 92.2% | | |
| **These numbers are not a result.** The model is being scored by the same | |
| renderer that produced its labels. They are here to show the size of the | |
| sim-to-real gap, which is the point: 4.8 minutes on renders, 156 on photographs. | |
| ## What goes wrong | |
| - **Hand confusion.** 720 of the 1,421 gross failures would improve if the hour | |
| and minute hands were swapped. The generator used a narrow band of hand | |
| lengths and widths, and the model learned that convention rather than learning | |
| which hand is longer. | |
| - **The confidence signal does not survive.** The two hands' disagreement is a | |
| good confidence signal on synthetic data (MAE 4.82 falls to 1.64 when keeping | |
| the most confident 75%) and is inert on real photographs (155.9 to 153.5). The | |
| model is confidently wrong, which is worse than being uncertain. | |
| - **Small clocks.** The locator finds a dial to 1.5% of image width on renders | |
| and fails on wide scenes where the clock is a small part of the frame. | |
| ## Architecture | |
| stage 1 DialLocator 554K params, 2.2 MB image -> dial centre, radius | |
| stage 2 PretrainedReader 2.3M params, 9.2 MB crop -> two angle distributions | |
| Stage 2 is a MobileNetV3-Small backbone pretrained on ImageNet, with two heads | |
| predicting a distribution over 180 angular bins for each hand. Targets are von | |
| Mises bumps rather than one-hot, and decoding takes a circular soft-argmax. | |
| An identical from-scratch network never leaves chance on this task: it sits at | |
| 2*ln(180), a uniform distribution, however long it trains, while overfitting 400 | |
| images perfectly. ImageNet initialisation is what makes it learn at all. | |
| Both hands are predicted, never the time directly, so their disagreement gives a | |
| confidence signal for free. Decoding is a vernier: the minute hand supplies | |
| precision, the hour hand picks which hour. Measured against reading the hour hand | |
| alone, that is about 11x more accurate under noise. | |
| ## Usage | |
| import torch | |
| from twostage import DialLocator, PretrainedReader, crop_dial | |
| from model import decode_cls | |
| reader = PretrainedReader(bins=180, arch="mobilenet_v3_small") | |
| reader.load_state_dict(torch.load("stage2_best.pt")["model"]) | |
| hour_logits, minute_logits = reader(crop) # crop: 1x3x256x256, ImageNet norm | |
| minutes, hour_only, disagreement, sharpness = decode_cls(hour_logits, minute_logits) | |
| `minutes` is the position on the 720-minute ring; `disagreement` is how far apart | |
| the two hands' readings are, in minutes. | |
| ## Intended use | |
| Reading round analog clock faces. Out of scope: digital-analog hybrids, subdials, | |
| chronographs, 24-hour faces. | |
| **Not suitable for any use where a wrong time carries a cost.** It is wrong by | |
| more than half an hour on 82% of real photographs. | |
| ## Reproducing | |
| git clone https://github.com/lyte-codes/clockface | |
| ./eval.py --self-test # 32/32, checks the metric | |
| python3 predict.py --labels <labels> --root <dir> \ | |
| --stage2 stage2_best.pt --stage1 stage1_best.pt --out preds.jsonl | |
| ./eval.py --labels <labels> --pred preds.jsonl | |
| ## Licence | |
| Apache 2.0. The synthetic dataset is CC BY 4.0. | |