# `@huggingface/lerobot`

Hugging Face Hub上でホストされている[LeRobot](https://github.com/huggingface/lerobot)ロボティクスデータセット用のパーサーです。

すべては範囲指定HTTPリクエストによって読み取られるため、ブラウザでもNodeでも変更なしで動作し、データセット全体をダウンロードせずにそのデータセットに関する質問に答えることができます。

## なぜ

LeRobotのデータセットには2つのレイアウトがあります。`v2.0` / `v2.1`では、各エピソードに専用の動画ファイルとparquetファイルがあり、エピソードのインデックスはJSON Linesファイルです。一方、`v3.0`ではエピソードが共有ファイルに連結され、インデックスはparquet形式となるため、エピソードはより大きなファイル内のバイト範囲と時間範囲として定義されます。

このパッケージは、その違いを`LeRobotEpisode`という単一の形状の背後に隠しています。

## インストール

```bash
npm install @huggingface/lerobot
```

[hyparquet](https://github.com/hyparam/hyparquet) が同梱されており、`v3.0` のエピソードインデックスおよびフレームデータの読み取りに使用されます。[fzstd](https://github.com/101arrowz/fzstd) は、hyparquet に組み込まれている SNAPPY に加えて ZSTD を追加します。どちらもオンデマンドでインポートされるため、`info()` または `v2` のエピソード一覧のみが必要な呼び出し元では、これらが読み込まれることはありません。

## 使用方法

```ts
import { LeRobotDataset } from "@huggingface/lerobot";

const dataset = new LeRobotDataset("lerobot/svla_so101_pickplace");

const info = await dataset.info();
// {
//   codebaseVersion: "v3.0",
//   robotType: "so100_follower",
//   fps: 30,
//   totalEpisodes: 50,
//   totalFrames: 11939,
//   cameras: [{ key: "observation.images.up", width: 640, height: 480, codec: "av1" }, ...],
//   joints: { "observation.state": ["shoulder_pan.pos", "shoulder_lift.pos", ...] },
//   ...
// }

const episodes = await dataset.episodes({ limit: 10 });
// [
//   {
//     index: 0,
//     length: 303,
//     durationSec: 10.1,
//     tasks: ["pink lego brick into the transparent box"],
//     videos: [{ cameraKey: "observation.images.up", url: "https://...", fromSec: 0, toSec: 10.1 }, ...],
//     data: { url: "https://.../data/chunk-000/file-000.parquet", fromRow: 0, toRow: 303 },
//   },
//   ...
// ]

const episode = await dataset.episode(42);
// The episode whose `index` is 42, or undefined when there is none
```

`offset` はインデックス内の位置を数えますが、データセットがエピソードを 0, 1, 2, ... と番号付けしている場合のみ、エピソードのインデックスと一致します。10 から始まるものや、削除されたエピソードをスキップするものもあるため、特定のエピソードを開くには `episode(index)` を使用してください。0, 1, 2, ... と番号付けされたデータセットでは、`episodes({ offset: index, limit: 1 })` と同じコストです。

`videos[].fromSec` / `toSec` は、`url` 内のエピソードのオフセットです。`v3.0` では複数のエピソードが 1 つのファイルを共有するため、`fromSec` までシークして `toSec` で停止します。`v2.x` では各エピソードが独自のファイルを持ち、範囲は単にそのファイル全体をカバーします。

`data` は、そのエピソードのフレーム行を指します。`observation.state` または `action` を呼び出し側でプロットしたい場合に使用します。`fromRow` は開始行を含み、`toRow` は終了行を含まない範囲です。両方とも `data.url` 内の行オフセットであり、データセット全体でのインデックスではありません。v3 では、ページがデータファイルの途中から始まる場合、エピソードの一覧を取得するために、そのファイルの parquet フッターを読み取り、開始位置を特定します。ファイルを読み取れない場合、またはエピソードのメタデータと一致しない場合は、`data` は存在しません。

エピソードをページングする間、`LeRobotDataset`を1つ保持します。`info()`と同様に、`v3.0`のインデックスファイルを走査した内容を記憶しているため、後のページや`episode()`の参照では、該当するデータを保持するファイルに直接アクセスできます。

### リビジョンの固定

結果がどこかに保存またはキャッシュされる場合は、後からのpushで解析した内容が変更されないように、常にcommit shaを渡してください。

```ts
const dataset = new LeRobotDataset("lerobot/svla_so101_pickplace", {
	revision: "f641879e22172be7e8161d5e6c1503c2d2feb657",
});
```

### プライベートおよびゲート付きデータセット

認証情報を追加するには、独自の`fetch`を渡します:

```ts
const dataset = new LeRobotDataset("my-org/private-dataset", {
	additionalFetchHeaders: { Authorization: `Bearer ${accessToken}` },
});
```

`huggingface.co`のブラウザでは、セッションクッキーだけで十分です:

```ts
const dataset = new LeRobotDataset(repoId, {
	fetch: (url, init) => fetch(url, { ...init, credentials: "same-origin" }),
});
```

## フェッチせずにパースする

`parseInfo` は純粋関数であり、すでにバイト列を保持している呼び出し元向けです。

```ts
import { parseInfo } from "@huggingface/lerobot";

const info = parseInfo(await readFile("meta/info.json", "utf8"));
```

利用可能な情報ファイルではないもの、LFS ポインター、切り詰められた読み取り、このパッケージが実装していない`codebase_version`などに対して例外をスローします。

