Aryan Sethi
Claude Opus 5 (1M context)
Drop barrel, add held-out stop-sign distance, add the GPU runbook
5d449ff Download src/hub/push.py from Aryan006/cone-distance: direct link, hf CLI and curl.
- Browser
- Download file 9.65 kB
-
https://huggingface.co/Aryan006/cone-distance/resolve/main/src/hub/push.py
- Command line
-
hf download hf://Aryan006/cone-distance/src/hub/push.py
-
curl -L -o push.py https://huggingface.co/Aryan006/cone-distance/resolve/main/src/hub/push.py
9.65 kB
| """Publish the unified dataset to the Hugging Face Hub. | |
| The local dataset is symlinks into four different dataset roots, which is right | |
| for building it and useless for moving it. This packs the real bytes into tar | |
| shards, adds the metadata phase 2 and phase 3 need, and uploads the lot as a | |
| dataset repo, so a fresh Colab runtime is one download away from training | |
| instead of one 30 GB nuScenes pull away. | |
| What lands on the Hub: | |
| manifest.parquet the source of truth -- previewable in the Hub viewer | |
| class_priors.yaml per-class physical size, for the known-size estimator | |
| calib/<sensor_id>.yaml K, camera height, ego<-cam rotation | |
| data.yaml portable (path: "."); pull.py rewrites it | |
| shards/{train,val}_NNN.tar | |
| README.md dataset card, generated from the manifest | |
| Images and labels are sharded rather than uploaded as individual files because | |
| the Hub handles a few large files far better than 40,000 small ones, and a | |
| ~1 GB shard is a resumable unit if the upload drops. | |
| Usage: | |
| huggingface-cli login # or pass --token | |
| python -m src.hub.push --repo-id <user>/cone-distance-v1 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import shutil | |
| import tarfile | |
| import tempfile | |
| from pathlib import Path | |
| import pandas as pd | |
| import yaml | |
| from src.common import paths, schema | |
| DEFAULT_SHARD_BYTES = 1_000_000_000 # ~1 GB | |
| def pair_files(yolo_root: Path, split: str) -> list[tuple[Path, Path]]: | |
| """(image, label) pairs for one split, in a stable order.""" | |
| image_dir = yolo_root / split / "images" | |
| label_dir = yolo_root / split / "labels" | |
| pairs = [] | |
| for image_path in sorted(image_dir.iterdir()): | |
| label_path = label_dir / f"{image_path.stem}.txt" | |
| if not label_path.exists(): | |
| raise SystemExit(f"{image_path} has no label file; re-run src.data.to_yolo") | |
| pairs.append((image_path, label_path)) | |
| return pairs | |
| def write_shards(yolo_root: Path, staging: Path, split: str, | |
| shard_bytes: int) -> int: | |
| """Pack one split into tar shards under staging/shards/. | |
| Members keep the local layout (`train/images/x.jpg`, `train/labels/x.txt`) | |
| so extracting every shard into one directory reproduces it exactly. | |
| Symlinks are dereferenced -- the tar holds real bytes. | |
| """ | |
| shard_dir = staging / "shards" | |
| shard_dir.mkdir(parents=True, exist_ok=True) | |
| pairs = pair_files(yolo_root, split) | |
| index, written, archive = 0, 0, None | |
| for image_path, label_path in pairs: | |
| if archive is None: | |
| archive = tarfile.open(shard_dir / f"{split}_{index:03d}.tar", "w") | |
| written = 0 | |
| # resolve() follows the symlink to the real image in the source dataset. | |
| archive.add(image_path.resolve(), arcname=f"{split}/images/{image_path.name}") | |
| archive.add(label_path, arcname=f"{split}/labels/{label_path.name}") | |
| written += image_path.resolve().stat().st_size | |
| if written >= shard_bytes: | |
| archive.close() | |
| archive, index = None, index + 1 | |
| if archive is not None: | |
| archive.close() | |
| index += 1 | |
| print(f" {split}: {len(pairs)} images in {index} shard(s)") | |
| return index | |
| def write_data_yaml(staging: Path) -> None: | |
| """A portable data.yaml. pull.py rewrites `path` to wherever it lands.""" | |
| with open(staging / "data.yaml", "w") as handle: | |
| yaml.safe_dump( | |
| { | |
| "path": ".", | |
| "train": "train/images", | |
| "val": "val/images", | |
| "names": {index: name for index, name in enumerate(schema.CLASSES)}, | |
| }, | |
| handle, | |
| sort_keys=False, | |
| ) | |
| def write_dataset_card(manifest: pd.DataFrame, staging: Path, repo_id: str) -> None: | |
| """Generate the card from the manifest, so it cannot drift from the data.""" | |
| objects = schema.objects_only(manifest) | |
| counts = pd.crosstab(objects["source"], objects["class"]) | |
| distances = ( | |
| objects[objects["gt_distance_m"].notna()] | |
| .groupby("class")["gt_distance_m"] | |
| .describe(percentiles=[0.5])[["count", "min", "50%", "max"]] | |
| ) | |
| card = f"""--- | |
| license: other | |
| task_categories: | |
| - object-detection | |
| tags: | |
| - autonomous-driving | |
| - monocular-depth | |
| - yolo | |
| --- | |
| # {repo_id} | |
| Cones, barriers and stop signs from nuScenes, Argoverse 2, COCO and | |
| BDD100K, merged into one schema with camera intrinsics and ground-truth distance | |
| attached. Built for monocular geometric distance estimation: distance comes from | |
| focal length and pixel geometry at inference time, and the LiDAR-derived | |
| `gt_distance_m` here is scoring ground truth only. | |
| ## Contents | |
| | File | What it is | | |
| | --- | --- | | |
| | `manifest.parquet` | one row per object; the source of truth | | |
| | `calib/<sensor_id>.yaml` | `K`, `cam_height_m`, `R_ego_from_cam` per camera | | |
| | `class_priors.yaml` | per-class physical size, derived from the 3D cuboids | | |
| | `data.yaml` | Ultralytics dataset config (`path` is relative; rewrite on download) | | |
| | `shards/*.tar` | images and YOLO labels, `{{split}}/images/` and `{{split}}/labels/` | | |
| ```python | |
| from huggingface_hub import snapshot_download | |
| snapshot_download("{repo_id}", repo_type="dataset", local_dir="data/hub") | |
| # then extract every shards/*.tar into data/hub/ and point Ultralytics at data.yaml | |
| ``` | |
| ## Instances per source and class | |
| ``` | |
| {counts.to_string()} | |
| ``` | |
| ## Ground-truth distance | |
| `gt_distance_m` is **forward depth along the camera optical axis** — the Z | |
| component of the object centre in the camera frame — not Euclidean range. | |
| Null for COCO and BDD100K, which have no depth ground truth. | |
| ``` | |
| {distances.to_string()} | |
| ``` | |
| ## Frames per split | |
| ``` | |
| {manifest.groupby("split")["image_path"].nunique().to_string()} | |
| ``` | |
| Split by **scene, not by frame**: adjacent video frames are near-duplicates, so a | |
| frame-level split leaks the val set into training and inflates val mAP a long | |
| way. Scenes are split independently within each source so every source reaches | |
| val. | |
| ## Notes | |
| - Argoverse 2's `CONSTRUCTION_BARREL` is not kept. An orange drum is a | |
| different object from a jersey barrier and is not an evaluation class. | |
| - Boxes shorter than 15 px, nuScenes visibility below 0.4, and boxes more than | |
| 50% truncated at the frame edge are filtered out. | |
| - BDD100K contributes negative frames only, excluding any frame containing a | |
| traffic sign — its generic sign class hides real stop signs. | |
| - Source datasets keep their own licences (nuScenes and Argoverse 2 are | |
| non-commercial research licences; COCO is CC BY 4.0). This repo redistributes | |
| derived crops and labels under those terms. | |
| """ | |
| (staging / "README.md").write_text(card) | |
| def push(root: Path, repo_id: str, private: bool, token: str | None, | |
| shard_bytes: int, staging_dir: Path | None) -> None: | |
| from huggingface_hub import HfApi | |
| manifest_path = root / "manifest.parquet" | |
| yolo_root = root / "yolo" | |
| if not yolo_root.exists(): | |
| raise SystemExit(f"{yolo_root} does not exist; run src.data.to_yolo first") | |
| manifest = schema.read_manifest(manifest_path) | |
| staging = Path(staging_dir) if staging_dir else Path(tempfile.mkdtemp(prefix="hub_")) | |
| staging.mkdir(parents=True, exist_ok=True) | |
| print(f"staging in {staging}") | |
| try: | |
| print("packing shards") | |
| for split in ("train", "val"): | |
| write_shards(yolo_root, staging, split, shard_bytes) | |
| shutil.copy(manifest_path, staging / "manifest.parquet") | |
| priors = root / "class_priors.yaml" | |
| if priors.exists(): | |
| shutil.copy(priors, staging / "class_priors.yaml") | |
| if (root / "calib").exists(): | |
| shutil.copytree(root / "calib", staging / "calib", dirs_exist_ok=True) | |
| write_data_yaml(staging) | |
| write_dataset_card(manifest, staging, repo_id) | |
| total = sum(p.stat().st_size for p in staging.rglob("*") if p.is_file()) | |
| print(f"\nuploading {total / 1e9:.2f} GB to {repo_id}") | |
| api = HfApi(token=token) | |
| api.create_repo(repo_id, repo_type="dataset", private=private, exist_ok=True) | |
| api.upload_folder(repo_id=repo_id, repo_type="dataset", folder_path=str(staging)) | |
| print(f"\nhttps://huggingface.co/datasets/{repo_id}") | |
| finally: | |
| if staging_dir is None: | |
| shutil.rmtree(staging, ignore_errors=True) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter) | |
| parser.add_argument("--repo-id", required=True, help="e.g. <user>/cone-distance-v1") | |
| parser.add_argument("--unified-root", type=Path, default=None) | |
| parser.add_argument("--private", action="store_true") | |
| parser.add_argument("--token", default=os.environ.get("HF_TOKEN"), | |
| help="defaults to $HF_TOKEN, then the cached " | |
| "huggingface-cli login. Prefer the environment: a " | |
| "token passed on the command line is visible to " | |
| "anyone who can run ps.") | |
| parser.add_argument("--shard-bytes", type=int, default=DEFAULT_SHARD_BYTES) | |
| parser.add_argument("--staging-dir", type=Path, default=None, | |
| help="keep the staging directory here instead of a temp dir") | |
| args = parser.parse_args() | |
| push( | |
| root=paths.unified_root(args.unified_root), | |
| repo_id=args.repo_id, | |
| private=args.private, | |
| token=args.token, | |
| shard_bytes=args.shard_bytes, | |
| staging_dir=args.staging_dir, | |
| ) | |
| if __name__ == "__main__": | |
| main() | |