Views
No views yet
pip install -r requirements.txt, run train.py,
and reproduce the same per-slot accuracy reported in the dataset card.
06672429 — d6 at θ=97° (just past park-2, mid-snap stress case)

06629789 — d4 = 9 mid-roll (single rollover edge)

06615699 — d6 = 9 and d7 = 9 (double rollover, hardest case)
badger-55-meterreader/
├── README.md
├── requirements.txt
├── rectifier.py # deskew + digit-window detect + affine warp
├── models.py # DINOv2 wrapper + head architectures
├── train.py # download dataset, train three heads
├── demo.py # one-image rectify + infer + annotate
├── weights/
│ ├── digit_classifier.pt # 10-class digit head (pooled d4-d7)
│ ├── d4d5_predictor90.pt # 90-bin angular head, slots 4+5
│ └── d6d7_predictor90.pt # 90-bin angular head, slots 6+7 (incl. platinum atlas)
├── sample_midsnap_d6.jpg # d6 slightly past park-2; v0 stress case
├── sample_rollover_d4.jpg # d4 = 9 mid-roll
├── sample_rollover_d6d7.jpg # d6 = 9 AND d7 = 9 (double rollover)
└── demo_sample_*.jpg # reference annotated outputs for each input1python3 -m venv venv
2./venv/bin/pip install -U pip
3./venv/bin/pip install -r requirements.txt./venv/bin/python3 train.pyS3CUR/badger-55-watermeter (just slots.parquet,
~29 MB — bytes are embedded inline, no per-file rate-limit dance) and
facebook/dinov2-small (~85 MB) into ~/.cache/huggingface/. Then:d4d5_predictor90.pt on slot 4+5 angular labels (KL on
wrapped-Gaussian soft targets).d6d7_predictor90.pt on slot 6+7 angular labels (includes
the platinum d7 atlas — continuous-rotation ground truth on the
one drum that genuinely sweeps every angle).digit_classifier.pt (10-way digit, pooled across slots 4-7)../weights/. The per-head recipe (epoch counts, learning rates) was
swept upstream and baked into train.py's RECIPE dict — override with
--epochs N only if you want to experiment.1./venv/bin/python3 train.py --epochs 120 # override default recipe
2./venv/bin/python3 train.py --skip-classifier # angular heads only
3./venv/bin/python3 train.py --device cpu # CPU-only (much slower)
4./venv/bin/python3 train.py --local-parquet PATH # skip HF, use a local slots.parquet./venv/bin/python3 demo.py --image sample_midsnap_d6.jpg--image:./venv/bin/python3 demo.py--image, the demo pulls captures.parquet (~1 GB, also bytes
embedded) and picks a clean test-split frame to decode. Writes
demo_output.jpg showing:| File | Expected reading | What it stresses |
|---|---|---|
sample_midsnap_d6.jpg | 06672429 (667,242.9 gal) | d6 sits at θ=97° — just past park-2; the v0-contamination stress case |
sample_rollover_d4.jpg | 06629789 (662,978.9 gal) | d4 = 9 mid-roll (single rollover edge) |
sample_rollover_d6d7.jpg | 06615699 (661,569.9 gal) | d6 = 9 and d7 = 9 (double rollover, hardest case) |
max(p). When they disagree:| Slot | Tiebreak | Why |
|---|---|---|
| d0 | classifier | The source meter's d0 was always 0; the classifier learned a hard constant and the angular head on a constant has no real θ signal. |
| d1–d7 | predictor90 | The angular head's θ disambiguates mid-snap and mid-roll cases the classifier wobbles on. P90 trained on slots {4,5,6,7} generalizes to the upper drums cleanly when those drums visually sweep through digit faces (e.g. mid-snap). |
gold_d0/gold_d1/
gold_d2/gold_d3 pool. The classifier is trained on the d4-d7 pool and
then applied to d0-d4 at inference. It works for d4 (in-distribution)
and d0 (hard constant), but d1-d3 are near-OOD generalization. P90's θ
direction resolves those slots more reliably.badger-55-meterreader. Three, 2026.