Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pillbox-data

Training, validation and test data for the pillbox pill-presence detectors, plus the model registry. The Raspberry Pi pushes new photos and labels here automatically (hourly); people and training jobs pull.

Layout

raw/YYYY-MM-DD/photo_*.jpg      every capture, filed by date — the source of truth
labels/labels.json              per-cell ground truth: "<photo stem>/<DAY>_<SLOT>": "pill" | "empty"
splits/{train,valid,test}.txt   photo stems per split, assigned BY CAPTURE SCENE
references/<set-id>/            empty-box reference photos (for the 6-channel CNN)
models/<detector>/<version>/    trained models + card.json (see "Contributing a model")
legacy/<set-name>/              pre-existing datasets that can't be regenerated
                                from raw/ (e.g. ad-hoc-named crops); frozen as-is
export/                         generated train folders — gitignored, never committed

Only sources of truth are stored. Cropped cell images are never committed — they are derived from the raw photos and go stale whenever the crop calibration changes, so they get regenerated on demand (next section).

Quick start (Dylan): get a ready-to-train dataset

You need both repos side by side, plus opencv-python and numpy:

git clone https://github.com/tarun101/pillbox
git clone https://github.com/tarun101/pillbox-data
pip install opencv-python-headless numpy

python3 pillbox/detect/export_dataset.py --data pillbox-data --clean

That writes the familiar Ultralytics-classify layout (same shape as the Roboflow export you trained on) to pillbox-data/export/:

export/
  Train/Full/photo_20260713_144724_SAT_NIGHT.jpg   # <photo stem>_<DAY>_<SLOT>
  Train/Empty/…
  Valid/Full/…   Valid/Empty/…
  Test/Full/…    Test/Empty/…

Filenames keep provenance, so any crop traces back to its raw photo. Re-run the export any time — it always reflects the latest photos and label corrections. Train on it e.g. with:

yolo classify train data=pillbox-data/export model=yolov8n-cls.pt imgsz=224
yolo export model=runs/classify/train/weights/best.pt format=onnx imgsz=224

imgsz must match between train and export. The shipped YOLO is a 224 model — exporting a 224-trained model at 640 runs without error but returns near-random predictions. Keep both commands on the same size.

Reproducing the augmented training set

The original YOLO trained on a Roboflow export that expanded these ~1k cells to ~2,700 "training images" by augmentation (flips, rotations, exposure). That was never a separate dataset — it's the same labelled cells — so it isn't stored here; regenerate it with --augment N:

python3 pillbox/detect/export_dataset.py --data pillbox-data --clean --augment 3

--augment N writes N images per Train crop (original + N-1 augmented copies); Valid/Test always stay 1× so evaluation is never inflated. Every copy is seeded from its filename, so the export is byte-identical run to run. --augment 3 yields ~2.4k images (Roboflow's exact 2,700 sat between 3× and 4×). Omit the flag (default 1) for the plain, unaugmented crops.

Evaluating models (accuracy, camouflage, latency, power)

All three detectors — the classical DoG baseline, the reference-CNN, and the YOLO classifier — run through shared harnesses in pillbox/detect/, so their numbers are directly comparable. The DoG baseline is fully classical (no weights, no training) and lives in pillbox/detect/classify_cells.py (dog_response() is the difference-of-Gaussians; its one threshold is fitted by calibrate_dog.py). You never run a model by hand — use these:

Accuracy / F1 across all models on a labelled split:

python3 -m detect.paper_stats --data ~/pillbox-data --split test   # or: all

Writes the model-comparison table (accuracy / F1 / macro-F1 / RMSE / params) and the bar chart. Use --split test for the frozen held-out number, --split all to sanity-check on everything labelled.

Camouflage (Figure 5) — recall on Full cells binned by pill-to-lid colour difference (ΔE), one line per model:

python3 -m detect.camouflage_eval --images <photos_dir> \
    --labels <labels.json> --out dataset/camo

Writes figure5.png + bins.json. Each cell is scored against the empty-box reference, so include one empty-box reference photo when you shoot a new box or setup.

Latency + power — run ON the Pi:

python3 -m detect.paper_stats --data ~/pillbox-data --hardware

Latency (ms/photo) is measured on whatever device runs it, so run it on the actual Pi. --hardware also reads the Pi 5 PMIC for real power draw (net of idle baseline) → Figure 6. Power needs a Pi 5 (the Pi 4 has no PMIC to query — latency still works there, power does not). Add --latency-reps 20 for tighter timing averages.

Both harnesses need ground-truth labels for the cells you evaluate. Label new captures in the app's Analyze modal ("Your labels" grid); they sync here automatically, then the commands above pick them up.

Ground rules

  • Never train on Test/. The test split is frozen — photos are never moved between splits — so everyone's accuracy numbers stay comparable over time. Report test accuracy only for a final, chosen model.
  • Don't hand-edit splits/*.txt or file images into Train/Valid/Test yourself. Splits are assigned by capture scene (shots taken seconds apart are near-duplicates; letting them straddle splits inflates accuracy). pillbox/detect/make_splits.py handles it, and the Pi runs it automatically.
  • Don't commit export/ (it's gitignored). If you want to pin exactly what a model trained on, note this repo's commit hash in the model's card.json instead — the export is reproducible from any commit.
  • Labels come from the app. Corrections are made in the pillbox web app's Analyze modal ("Your labels" grid) and sync here hourly. If you spot a wrong label while training, say so / fix it in the app rather than editing labels.json by hand, so the two never diverge.

Contributing a model

Drop trained models into the registry with a small metadata card:

models/
  yolo/
    dylan-2026-07-16/
      best.onnx          # what the Pi runs (onnxruntime)
      best.pt            # the source checkpoint (kept for re-export)
      card.json
  cnn/
    2026-07-13-v1/
      pill_classifier.onnx
      card.json

card.json — a few lines so "which model is live and why" stays answerable:

{
  "trained_by": "Dylan P",
  "date": "2026-07-16",
  "data_commit": "<git rev-parse HEAD of this repo when exported>",
  "base_model": "yolov8n-cls",
  "imgsz": 640,
  "val_accuracy": 0.97,
  "test_accuracy": 0.95,
  "notes": "trained on Roboflow export + July 13 set"
}

Promotion (making a model live on the Pi): copy the winner into the app repo — detect/yolo/best.onnx for YOLO, detect/pill_classifier.onnx for the CNN — and open a PR there. Merging auto-deploys to the Pi within ~2 minutes; the PR diff is the audit log and rollback is git revert. Models must be ONNX to run on the Pi (onnxruntime only — no PyTorch there); classify models should name their classes so the "pill present" one contains full or pill.

Importing pre-existing datasets

If you have data from before this repo (e.g. your original Roboflow export): if the filenames still identify the source photo + cell, it can be absorbed into labels.json/splits losslessly; if not (Roboflow-renamed or augmented crops), it gets preserved as a frozen snapshot under legacy/<set-name>/ and concatenated at training time. Post a file listing (find <dataset> -type f | head -20) in the group chat and we'll wire it in.

How data flows

Pi camera  →  web app (capture + Analyze labeling)  →  ~/photos + labels.json
    →  hourly sync (deploy/sync-data.sh)  →  this repo (raw/, labels/, splits/)
    →  export_dataset.py  →  export/ (Train|Valid|Test / Full|Empty)
    →  training  →  models/<detector>/<version>/  →  PR to pillbox  →  Pi

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors