File size: 4,151 Bytes
f2ec79c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Hugging Face export for AngleForge image datasets.

Produces a standard ``imagefolder`` layout (``train/<label>/*.jpg``) plus a
``metadata.csv`` and a dataset card, and can push it to the Hub.
"""

from __future__ import annotations

import csv
import json
import shutil
from pathlib import Path
from typing import Dict, List

from .builder import BuildResult
from .config import ANGLE_PRESETS, DatasetConfig


def _dataset_card(config: DatasetConfig, result: BuildResult, repo_id: str) -> str:
    labels_table = "\n".join(
        f"| `{label}` | {count} |" for label, count in sorted(result.label_counts.items())
    )
    angles_list = "\n".join(
        f"- `{a}` — {ANGLE_PRESETS.get(a, (a, ''))[0]}" for a in config.angles
    )
    return f"""---
license: cc-by-4.0
pretty_name: {config.dataset_name} multi-angle robotic-arm image dataset
task_categories:
- image-classification
tags:
- image
- robotics
- robotic-arm
- synthetic-data
- multi-view
- qwen-image-edit
- edge-impulse
size_categories:
- n<1K
---

# {config.dataset_name} — Multi-Angle Robotic-Arm Image Dataset

Synthetic multi-viewpoint image dataset generated from real-world photos with
**Qwen Image Edit** ({result.backend_source}). Each source image is re-rendered
from several camera/gripper viewpoints to simulate a robotic arm inspecting an
object from multiple angles.

## Classes

| Label | Images |
|---|---|
{labels_table}

## Viewpoints (angles)

{angles_list}

## Layout

```text
train/<label>/<label>.<id>.jpg
test/<label>/<label>.<id>.jpg
metadata.csv
```

## Loading

```python
from datasets import load_dataset

ds = load_dataset("imagefolder", data_dir="{repo_id.split('/')[-1]}")
# or, once pushed to the Hub:
ds = load_dataset("{repo_id}")
print(ds)
```

## Edge Impulse

Filenames use the `label.<id>.jpg` convention, so they upload directly:

```bash
edge-impulse-uploader --category training train/**/*.jpg
```

## Notes

Synthetic multi-view images are a bootstrap for robotic-arm perception and
inspection models. Validate with real captures from the arm's own camera
before deployment.
"""


def export_hf_dataset(
    config: DatasetConfig,
    result: BuildResult,
    hf_dir: str,
    repo_id: str = "your-username/your-dataset",
) -> str:
    """Assemble an imagefolder dataset from a completed build. Returns its path."""
    source_dir = Path(result.out_dir)
    hf_path = Path(hf_dir).resolve()
    if hf_path.exists():
        shutil.rmtree(hf_path)
    hf_path.mkdir(parents=True, exist_ok=True)

    # Copy the imagefolder tree.
    src_imagefolder = source_dir / "hf_imagefolder"
    rows: List[Dict[str, str]] = []
    for split in ("train", "test"):
        split_src = src_imagefolder / split
        if not split_src.exists():
            continue
        for label_dir in sorted(p for p in split_src.iterdir() if p.is_dir()):
            for img in sorted(label_dir.glob("*.jpg")):
                rel = Path(split) / label_dir.name / img.name
                dst = hf_path / rel
                dst.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(img, dst)
                rows.append({"file_name": str(rel), "label": label_dir.name, "split": split})

    with (hf_path / "metadata.csv").open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=["file_name", "label", "split"])
        writer.writeheader()
        writer.writerows(rows)

    for name in ("dataset_summary.json",):
        src = source_dir / name
        if src.exists():
            shutil.copy2(src, hf_path / name)

    (hf_path / "README.md").write_text(_dataset_card(config, result, repo_id), encoding="utf-8")
    return str(hf_path)


def push_to_hub(hf_dir: str, repo_id: str, token: str, private: bool = False) -> str:
    """Upload the HF image dataset folder to the Hub. Returns the dataset URL."""
    from huggingface_hub import HfApi

    api = HfApi(token=token)
    api.create_repo(repo_id=repo_id, repo_type="dataset", exist_ok=True, private=private)
    api.upload_folder(folder_path=hf_dir, repo_id=repo_id, repo_type="dataset")
    return f"https://huggingface.co/datasets/{repo_id}"