"""Fetch the PPE dataset into work/ so train/val/test resolve for Ultralytics. The dataset is NOT committed to git (it's ~5k images). This script reconstructs it locally / in Colab / in CI. From repo root: uv run --with gdown python scripts/download_data.py DATA_URL="https://drive.google.com/uc?id=" uv run --with gdown python scripts/download_data.py `--with gdown` installs gdown just for this run without touching the project deps. Source options (set DATA_URL or --url): * A Google Drive ZIP (recommended) — a single file, no download caps. Zip your `data/` folder once, share it, and pass its `https://drive.google.com/uc?id=...` URL. * A Google Drive FOLDER — works only if it has <50 files (gdown's folder limit), so not suitable for the full image set. Kept as a fallback for small mirrors. * Roboflow — the original source; `pip install roboflow` then use the versioned download snippet from your Roboflow project for the most reproducible pull. Expected final layout (what work/data.yaml points at): work/train/ work/valid/ work/test/ """ from __future__ import annotations import argparse import os import sys import zipfile from pathlib import Path # Documented Drive *folder* mirror (see README). Prefer a zip via DATA_URL. DEFAULT_URL = "https://drive.google.com/drive/folders/1Vhf9QP4WG9yF11EeFUtmFKDU7sKut69f" DEST = Path("work") def _require_gdown(): try: import gdown # noqa: PLC0415 except ImportError: sys.exit( "gdown is not installed. Re-run with uv's ephemeral install:\n" " uv run --with gdown python scripts/download_data.py" ) return gdown def _extract_and_flatten(zip_path: Path) -> None: with zipfile.ZipFile(zip_path) as zf: zf.extractall(DEST) # If the zip wrapped everything in a single top dir, surface train/valid/test. for split in ("train", "valid", "test"): if (DEST / split).exists(): continue matches = list(DEST.glob(f"*/{split}")) if matches: matches[0].rename(DEST / split) def main() -> None: p = argparse.ArgumentParser(description=__doc__) p.add_argument("--url", default=os.environ.get("DATA_URL", DEFAULT_URL)) args = p.parse_args() gdown = _require_gdown() DEST.mkdir(exist_ok=True) if "/folders/" in args.url: print("⚠️ Downloading a Drive FOLDER — gdown caps this at 50 files, so the") print(" full image set will be truncated. Prefer a zip via DATA_URL.\n") gdown.download_folder(args.url, output=str(DEST), quiet=False, use_cookies=False) else: zip_path = DEST / "dataset.zip" gdown.download(args.url, str(zip_path), quiet=False, fuzzy=True) print(f"Extracting {zip_path} → {DEST}/ ...") _extract_and_flatten(zip_path) zip_path.unlink(missing_ok=True) missing = [s for s in ("train", "valid", "test") if not (DEST / s).exists()] if missing: print(f"\n⚠️ Done, but these splits are missing under work/: {missing}") print(" Check the archive layout — work/data.yaml expects work/train, work/valid, work/test.") else: print("\n✅ Dataset ready: work/train, work/valid, work/test") if __name__ == "__main__": main()