# LeRobotDataset v3.0

`LeRobotDataset v3.0` adalah format standar untuk data pembelajaran robot. Format ini menyediakan akses terpadu ke data deret waktu multi-modal, sinyal sensorimotor, dan video multi-kamera, serta metadata yang kaya untuk indeks, pencarian, dan visualisasi di Hugging Face Hub.

Untuk melihat apa yang dipegang oleh dataset dalam format ini sebelum membaca tentang tata letaknya, buka salah satu di [LeRobot Dataset Visualizer](https://huggingface.co/spaces/lerobot/visualize_dataset?path=/lerobot/svla_so101_pickplace/episode_0): rekaman kamera, grafik state dan action yang tersinkronisasi, serta setiap episode, di browser. Lihat [Explore a Dataset in the Browser](./using_dataset_tools#explore-a-dataset-in-the-browser).

Dokumentasi ini akan memandu Anda untuk:

- Memahami desain dan tata letak direktori v3.0
- Rekam dataset dan unggah ke Hub
- Muat dataset untuk pelatihan dengan `LeRobotDataset`
- Stream dataset tanpa mengunduh menggunakan `StreamingLeRobotDataset`
- Terapkan transformasi gambar untuk augmentasi data selama pelatihan
- Migrasikan dataset `v2.1` yang ada ke `v3.0`
- Eksperimen dengan format dan implementasi `LeRobotDataset` lainnya seperti Lance

## Apa yang baru di `v3`

- **Penyimpanan berbasis file**: Banyak episode per file Parquet/MP4 (v2 menggunakan satu file per episode).
- **Metadata relasional**: Batas episode dan pencarian diselesaikan melalui metadata, bukan nama file.
- **Streaming native Hub**: Konsumsi dataset langsung dari Hub dengan `StreamingLeRobotDataset`.
- **Mengurangi tekanan pada sistem file**: Jumlah file yang lebih sedikit dan lebih besar ⇒ inisialisasi lebih cepat dan lebih sedikit masalah pada skala besar.
- **Organisasi terpadu**: Tata letak direktori yang rapi dengan templat jalur yang konsisten di seluruh data dan video.
- **Backend penyimpanan dataset**: Dataset juga dapat disimpan sebagai tabel [LanceDB](https://lancedb.com) untuk akses acak dan indeks langsung dari penyimpanan objek (lihat [backend dataset LanceDB](#lancedb-dataset-backend)).

## Instalasi

`LeRobotDataset v3.0` disertakan dalam `lerobot >= 0.4.0`. Ikuti [Panduan Instalasi](./installation) untuk menginstalnya.

## Rekam dataset

Jalankan perintah di bawah ini untuk merekam dataset dengan SO-101 dan mendorongnya ke Hub:

```bash
lerobot-record \
  --robot.type=so101_follower \
  --robot.port=/dev/tty.usbmodem585A0076841 \
  --robot.id=my_awesome_follower_arm \
  --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 1920, height: 1080, fps: 30}}" \
  --teleop.type=so101_leader \
  --teleop.port=/dev/tty.usbmodem58760431551 \
  --teleop.id=my_awesome_leader_arm \
  --display_data=true \
  --dataset.repo_id=${HF_USER}/record-test \
  --dataset.num_episodes=5 \
  --dataset.single_task="Grab the black cube" \
  --dataset.streaming_encoding=true \
  --dataset.encoder_threads=2
# Optional: --dataset.rgb_encoder.vcodec=auto
```

Lihat [panduan rekaman](./il_robots#record-a-dataset) untuk detail lebih lanjut.

## Desain format

Prinsip inti v3 adalah **memisahkan penyimpanan dari API pengguna**: data disimpan secara efisien (beberapa file besar), sementara API publik menyediakan akses tingkat episode yang intuitif.

`v3` memiliki tiga pilar:

1. **Data tabular**: Sinyal berdimensi rendah dan frekuensi tinggi (state, action, timestamp) yang disimpan dalam **Apache Parquet**. Akses dilakukan melalui memory-mapped atau streaming via stack `datasets`.
2. **Data visual**: Bingkai kamera yang digabungkan dan dikodekan menjadi **MP4**. Bingkai dari episode yang sama dikelompokkan; video dipecah per kamera untuk ukuran yang praktis.
3. **Metadata**: rekaman JSON/Parquet yang menjelaskan skema (nama fitur, dtype, bentuk), frame rate, statistik normalisasi, dan **segmentasi episode** (offset awal/akhir ke dalam file Parquet/MP4 bersama).

> Untuk berskala hingga jutaan episode, baris tabular dan bingkai video dari beberapa episode **digabungkan** menjadi file yang lebih besar. Tampilan spesifik episode direkonstruksi **melalui metadata**, bukan batas file.

  
    <img
      src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobotdataset-v3/asset1datasetv3.png"
      alt="LeRobotDataset v3 diagram"
      width="220"
    />
    
      Dari dataset berbasis episode ke dataset berbasis file
    
  

### Struktur direktori (disederhanakan)

- **`meta/info.json`**: skema kanonik (fitur, bentuk/dtype), FPS, versi codebase, dan **template path** untuk menemukan shard data/video.
- **`meta/stats.json`**: statistik fitur global (rata-rata/standar deviasi/min/maks) yang digunakan untuk normalisasi; diekspos sebagai `dataset.meta.stats`.
- **`meta/tasks.parquet`**: deskripsi tugas bahasa alami yang dipetakan ke ID integer untuk kebijakan yang dikondisikan tugas.
- **`meta/episodes/`**: catatan per episode (panjang, tugas, offset) yang disimpan sebagai **Parquet chunked** untuk skalabilitas.
- **`data/`**: shard **Parquet** per bingkai; setiap file biasanya berisi **banyak episode**.
- **`videos/`**: shard **MP4** per kamera; setiap file biasanya berisi **banyak episode**.

## Muat dataset untuk pelatihan

`LeRobotDataset` mengembalikan kamus Python berisi tensor PyTorch dan terintegrasi dengan `torch.utils.data.DataLoader`. Berikut adalah contoh kode yang menunjukkan penggunaannya:

```python
import torch
from lerobot.datasets import LeRobotDataset

repo_id = "yaak-ai/L2D-v3"

# 1) Load from the Hub (cached locally)
dataset = LeRobotDataset(repo_id)

# 2) Random access by index
sample = dataset[100]
print(sample)
# {
#   'observation.state': tensor([...]),
#   'action': tensor([...]),
#   'observation.images.front_left': tensor([C, H, W]),
#   'timestamp': tensor(1.234),
#   ...
# }

# 3) Temporal windows via delta_timestamps (seconds relative to t)
delta_timestamps = {
    "observation.images.front_left": [-0.2, -0.1, 0.0]  # 0.2s and 0.1s before current frame
}

dataset = LeRobotDataset(repo_id, delta_timestamps=delta_timestamps)

# Accessing an index now returns a stack for the specified key(s)
sample = dataset[100]
print(sample["observation.images.front_left"].shape)  # [T, C, H, W], where T=3

# 4) Wrap with a DataLoader for training
batch_size = 16
data_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size)

device = "cuda" if torch.cuda.is_available() else "cpu"
for batch in data_loader:
    observations = batch["observation.state"].to(device)
    actions = batch["action"].to(device)
    images = batch["observation.images.front_left"].to(device)
    # model.forward(batch)
```

## Stream dataset selama pelatihan

Set `--dataset.streaming=true` untuk melatih tanpa terlebih dahulu mengunduh file Parquet dan video dataset. Episode diambil secara langsung ke dalam pool memori terbatas, dan setiap frame yang dipilih tetap dikunjungi tepat sekali per epoch:

```bash
lerobot-train \
  --dataset.repo_id=yaak-ai/L2D-v3 \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming
```

Reader yang sama tersedia di Python, untuk dataset Hub dan untuk dataset yang disimpan di [HF Storage Bucket](https://huggingface.co/docs/hub/storage-buckets):

```python
from lerobot.datasets import StreamingLeRobotDataset

dataset = StreamingLeRobotDataset("yaak-ai/L2D-v3")
bucket_dataset = StreamingLeRobotDataset("my-org/my-bucket", repo_type="bucket")
```

Dari CLI, pilih bucket dengan `--dataset.repo_type=bucket` atau `--dataset.root=hf://buckets/my-org/my-bucket`.

Lihat [Streaming Dataset Pelatihan](./training_dataset_streaming) untuk cara worker dan shard dipetakan ke konkurensi streaming, indeks sidecar MP4, pengaturan memori, resume, dan pemecahan masalah.

  
    <img
      src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobotdataset-v3/streaming-lerobot.png"
      alt="StreamingLeRobotDataset"
      width="520"
    />
    
      Stream langsung dari Hub untuk pelatihan on-the-fly.
    
  

## Transformasi gambar

Transformasi gambar adalah augmentasi data yang diterapkan pada bingkai kamera selama pelatihan untuk meningkatkan ketahanan dan generalisasi model. LeRobot mendukung berbagai transformasi, termasuk penyesuaian kecerahan, kontras, saturasi, rona, dan ketajaman.

### Menggunakan transformasi saat pembuatan/perekaman dataset

Saat ini, transformasi diterapkan hanya saat **pelatihan**, bukan saat perekaman. Saat Anda membuat atau merekam dataset, gambar mentah disimpan tanpa transformasi. Hal ini memungkinkan Anda untuk bereksperimen dengan augmentasi yang berbeda di kemudian hari tanpa perlu merekam ulang data.

### Menambahkan transformasi ke dataset yang sudah ada (API)

Gunakan parameter `image_transforms` saat memuat dataset untuk pelatihan:

```python
from lerobot.datasets import LeRobotDataset
from lerobot.transforms import ImageTransforms, ImageTransformsConfig, ImageTransformConfig

# Option 1: Use default transform configuration (disabled by default)
transforms_config = ImageTransformsConfig(
    enable=True,  # Enable transforms
    max_num_transforms=3,  # Apply up to 3 transforms per frame
    random_order=False,  # Apply in standard order
)
transforms = ImageTransforms(transforms_config)

dataset = LeRobotDataset(
    repo_id="your-username/your-dataset",
    image_transforms=transforms
)

# Option 2: Create custom transform configuration
custom_transforms_config = ImageTransformsConfig(
    enable=True,
    max_num_transforms=2,
    random_order=True,
    tfs={
        "brightness": ImageTransformConfig(
            weight=1.0,
            type="ColorJitter",
            kwargs={"brightness": (0.7, 1.3)}  # Adjust brightness range
        ),
        "contrast": ImageTransformConfig(
            weight=2.0,  # Higher weight = more likely to be selected
            type="ColorJitter",
            kwargs={"contrast": (0.8, 1.2)}
        ),
        "sharpness": ImageTransformConfig(
            weight=0.5,  # Lower weight = less likely to be selected
            type="SharpnessJitter",
            kwargs={"sharpness": (0.3, 2.0)}
        ),
    }
)

dataset = LeRobotDataset(
    repo_id="your-username/your-dataset",
    image_transforms=ImageTransforms(custom_transforms_config)
)

# Option 3: Use pure torchvision transforms
from torchvision.transforms import v2

torchvision_transforms = v2.Compose([
    v2.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
    v2.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),
])

dataset = LeRobotDataset(
    repo_id="your-username/your-dataset",
    image_transforms=torchvision_transforms
)
```

### Jenis transformasi yang tersedia

`type` dari sebuah transform dapat berupa kelas apa pun dari `torchvision.transforms.v2`, misalnya:

- **`ColorJitter`**: Menyesuaikan kecerahan, kontras, saturasi, dan rona
- **`SharpnessJitter`**: Menyesuaikan ketajaman gambar secara acak
- **`RandomAffine`**, **`RandomRotation`**: jitter geometris
- **`GaussianNoise`**, **`MotionBlur`**, **`JPEGCompression`**, **`GaussianPatchBrightness`**, **`RandomShadow`**, **`CoarseDropout`**, **`GammaCorrection`**, **`PlanckianJitter`**: Efek kamera dan pencahayaan yang terlihat pada robot sungguhan
- **`Identity`**: Tidak ada transformasi (berguna untuk pengujian)

Kelas `torchvision.transforms.v2` lainnya juga dapat ditunjuk sebagai `type` (misalnya `RandomResizedCrop` atau `GaussianBlur`); transformasi ini diterapkan satu per satu pada setiap sampel. Anda juga dapat meneruskan transformasi torchvision secara langsung ke parameter `image_transforms`.

Setiap transform kini di-batch, dan transform yang di-batch sama dijalankan di kedua backend. Kelas per-sampel (`SharpnessJitter`, `GaussianNoise`, `MotionBlur`, `GaussianPatchBrightness`, `RandomShadow`, `CoarseDropout`, `GammaCorrection`, `PlanckianJitter`, `RandomSubsetApply`) dan `make_transform_from_config` tetap dapat diimpor sebagai front end yang tidak lagi didukung di atas pengganti `Batched*` mereka dan memperingatkan saat digunakan; mereka akan dihapus dalam rilis mendatang. `ImageTransforms` adalah `torch.nn.Module` biasa yang menerima tensor `(C, H, W)` atau `(T, C, H, W)`. Konfigurasi yang menamai transform berdasarkan `type` tidak terpengaruh.

**Perubahan perilaku untuk resep yang sudah ada.** Pelatihan membaca frame sebagai `uint8`, dan transformasi LeRobot selain `ColorJitter`, `RandomAffine`, dan `RandomRotation` sebelumnya mengembalikan input non-float tanpa perubahan. Oleh karena itu, resep yang mengaktifkan `GaussianNoise`, `MotionBlur`, `GaussianPatchBrightness`, `RandomShadow`, `CoarseDropout`, `GammaCorrection`, atau `PlanckianJitter` sebelumnya dilatih tanpa transformasi tersebut. Sekarang transformasi ini diterapkan di kedua backend, sehingga resep seperti itu akan dilatih dengan cara yang berbeda dari sebelumnya: ia mendapatkan augmentasi yang diminta. Transformasi yang sudah diterapkan sebelumnya tidak berubah, kecuali pembulatan, karena jalur batched menghitung dalam float dan membulatkan sekali di akhir, bukan setelah setiap transformasi.

### Opsi konfigurasi

- **`enable`**: Mengaktifkan/menonaktifkan transformasi (default: `False`)
- **`max_num_transforms`**: Jumlah maksimum transformasi yang diterapkan per frame (default: `3`)
- **`random_order`**: Terapkan transformasi dalam urutan acak vs. urutan standar (default: `False`)
- **`weight`**: Probabilitas sampling untuk setiap transform (lebih tinggi = lebih mungkin, jika jumlah bobot tidak sama dengan 1, bobot akan dinormalisasi)
- **`kwargs`**: Parameter spesifik transformasi (misalnya, rentang kecerahan)
- **`backend`**: Tempat transformasi dijalankan selama pelatihan, `"dataloader"` (default) atau `"gpu"`; lihat di bawah
- **`gpu_compile`**: Pada backend `"gpu"`, `torch.compile` transform yang di-batch (default: `True`)
- **`gpu_chunk_size`**: Pada backend `"gpu"`, tingkatkan sejumlah sampel ini sekaligus untuk membatasi memori transien (default: `32`)

### Menjalankan transformasi di GPU

Secara default, transformasi dijalankan per sampel di dalam worker DataLoader. Dengan beberapa kamera, hal ini sering menjadi bagian paling mahal yang dilakukan worker, sehingga GPU akhirnya menunggu data. Atur `backend=gpu` agar worker hanya melakukan decode dan menerapkan transformasi yang sama ke setiap batch pada perangkat policy:

```bash
lerobot-train \
  --dataset.repo_id=your-username/your-dataset \
  --dataset.image_transforms.enable=true \
  --dataset.image_transforms.backend=gpu \
  --policy.type=act \
  --policy.device=cuda
```

Randomisasi tidak berubah: setiap sampel menarik transformasi dan parameternya sendiri, dan frame dari riwayat satu kamera membaginya. Augmentasi hanya diterapkan pada batch pelatihan, sebelum preprocessor kebijakan; batch evaluasi, rollout, dan checkpoint yang disimpan tidak diubah.

Hal yang perlu diketahui:

- Memerlukan akselerator; `--policy.device=cpu` ditolak. Dataset yang dibuat secara manual dengan callable `image_transforms` miliknya sendiri juga ditolak, karena callable tersebut dijalankan di dalam worker.
- Mendukung setiap jenis transformasi yang didukung oleh backend dataloader. Jenis yang tidak memiliki implementasi batched (`JPEGCompression`, dan kelas torchvision selain `ColorJitter`, `RandomAffine`, `RandomRotation`, dan `Identity`) diterapkan satu per satu di dalam batch, sehingga tidak mendapatkan percepatan; `JPEGCompression` juga melakukan round-trip melalui CPU. Transformasi yang mengubah ukuran frame ditolak pada batch yang berisi lebih dari satu sampel.
- Dapat direproduksi dari `--seed`, dengan setiap proses diberi seed yang berbeda. Augmentation tidak mengonsumsi stream acak dari policy, dan `gpu_compile` serta `gpu_chunk_size` tidak mengubah apa yang diterapkan.
- Kedua backend menjalankan transform batched yang sama dalam float dan melakukan pembulatan sekali di akhir, sehingga sampel yang ditingkatkan pada satu backend sama dengan sampel yang sama yang ditingkatkan pada backend lainnya, asalkan parameter yang diberikan sama.
- `gpu_compile` memakan waktu 15-30 detik sekali per bentuk gambar. Memori puncak sekitar dua salinan float dari gambar batch ditambah beberapa salinan dari satu chunk; turunkan `gpu_chunk_size` jika memori terbatas. `random_order=true` mengalikan waktu transformasi dengan `max_num_transforms`.

## Migrasi `v2.1` → `v3.0`

Konverter mengagregasi file per episode menjadi shard yang lebih besar dan menulis offset/metadata episode. Konversi dataset Anda menggunakan instruksi di bawah ini.

```bash
# Convert an existing v2.1 dataset hosted on the Hub:
python -m lerobot.scripts.convert_dataset_v21_to_v30 --repo-id=<HF_USER/DATASET_ID>
```

**Fungsinya**

- Mengagregasi file parquet: `episode_000000.parquet`, `episode_000001.parquet`, … → **`file-000.parquet`**, …
- Mengagregasi file mp4: `episode_000000.mp4`, `episode_000001.mp4`, … → **`file-000.mp4`**, …
- Perbarui `meta/episodes/*` (Parquet berchunk) dengan panjang per episode, tugas, dan offset byte/frame.

## Masalah Umum

### Selalu panggil `finalize()` sebelum mendorong

Saat membuat atau merekam dataset, Anda **harus** memanggil `dataset.finalize()` untuk menutup penulis parquet dengan benar. Lihat [PR #1903](https://github.com/huggingface/lerobot/pull/1903) untuk detail lebih lanjut.

```python
from lerobot.datasets import LeRobotDataset

# Create dataset and record episodes
dataset = LeRobotDataset.create(...)

for episode in range(num_episodes):
    # Record frames
    for frame in episode_data:
        dataset.add_frame(frame)
    dataset.save_episode()

# Call finalize() when done recording and before push_to_hub()
dataset.finalize()  # Closes parquet writers, writes metadata footers
dataset.push_to_hub()
```

**Mengapa ini diperlukan?**

Dataset v3.0 menggunakan penulisan parquet inkremental dengan metadata yang dibuffer untuk efisiensi. Metode `finalize()`:

- Membuang semua metadata episode yang dibuffer ke disk
- Menutup penulis parquet untuk menulis metadata footer, jika tidak file parquet akan rusak
- Memastikan dataset valid untuk dimuat

Tanpa memanggil `finalize()`, file parquet Anda akan tidak lengkap dan dataset tidak akan dimuat dengan benar.

## Format dan implementasi lainnya

### Backend dataset LanceDB

Dataset `v3` juga dapat disimpan sebagai tabel [LanceDB](https://lancedb.com). LanceDB adalah data lakehouse multimodal yang dibangun di atas format Lance untuk pola akses AI: akses acak cepat ke baris mana pun beserta byte videonya, serta indeks skalar, vektor, dan teks penuh pada tabel yang sama, sehingga EDA, penambangan, kurasi, dan pelatihan dapat dijalankan langsung dari object storage. Tata letak ini mempertahankan `meta/` tanpa perubahan dan menambahkan tabel `frames` untuk data tabular serta tabel `videos` yang menyimpan MP4 sebagai blob. `"storage_format": "lance"` di `meta/info.json` memilih pembaca; API `LeRobotDataset`, item, dan pipeline pelatihan tidak berubah.

- **Latih langsung dari Hub atau HF Storage Buckets dengan GPU yang sudah terpasang.** Frame acak dibaca langsung di tempatnya, hanya byte yang dibutuhkan oleh setiap batch, sehingga setiap batch merupakan pengacakan global dan tidak ada yang diunduh terlebih dahulu.
- **Konversi dengan biaya sekitar salinan file.** Video tidak di-encode ulang, sehingga item identik bit-for-bit dengan sumber. DROID mengonversi dalam 34 menit.
- **Rekayasa fitur tanpa salinan (zero-copy).** Tambahkan kolom seperti skor, embedding, atau label yang dikoreksi ke tabel yang dibaca oleh trainer, tanpa menulis ulang video atau menyalin dataset, lalu saring dan cari di atasnya menggunakan indeks. Pembaca mengabaikan kolom yang tidak dikenalnya.

Untuk cara kerjanya, benchmark, dan contoh, lihat [panduan LanceDB LeRobot](https://docs.lancedb.com/training/lerobot).

Fitur kamera harus didukung oleh video. Dataset yang menyimpan bingkai kamera sebagai gambar tidak didukung oleh pembaca dan perlu dikode ulang sebagai video sebelum konversi.

Membaca dataset Lance memerlukan ekstra `lancedb` (`pip install "lerobot[lancedb]"`). Format ini hanya dapat dibaca: rekam dan edit dataset dalam format default, lalu konversikan dengan paket [`lerobot-lancedb`](https://pypi.org/project/lerobot-lancedb/):

```bash
pip install "lerobot-lancedb"
lerobot-lance-convert --repo-id lerobot/pusht --out ./pusht-lance  # or --root <local dataset dir>
```

Loading menggunakan kelas yang sama:

```python
from lerobot.datasets import LeRobotDataset

# Hub dataset repo
dataset = LeRobotDataset("lance-format/pusht-lance")

# HF Storage Bucket, read in place
dataset = LeRobotDataset("my-org/pusht-lance", repo_type="bucket")

# the same bucket as a root URI (other object store URIs work the same way)
dataset = LeRobotDataset("lerobot/pusht", root="hf://buckets/my-org/pusht-lance")
```

Opsi HF Storage Bucket berfungsi di `lerobot-train`:

```bash
lerobot-train \
  --dataset.repo_id=my-org/pusht-lance \
  --dataset.repo_type=bucket \
  --policy.type=act
```

Visualisasi berfungsi dengan kedua viewer. `lerobot-dataset-viz --repo-id lance-format/pusht-lance --episode-index 0` membuka episode di Rerun seperti biasa, dan `--display-mode foxglove` menyajikannya ke [Foxglove](https://foxglove.dev) melalui WebSocket. Dalam kedua cara tersebut, hanya frame yang ada di layar yang diambil, tanpa ekspor atau unduhan MCAP.

