# `@huggingface/lerobot`

Парсер для наборов данных робототехники [LeRobot](https://github.com/huggingface/lerobot), размещённых на Hugging Face Hub.

Все данные читаются через HTTP-запросы с диапазоном, поэтому код работает без изменений как в браузере, так и в Node, и при этом не требуется скачивать весь датасет, чтобы ответить на вопрос о нём.

## Почему

Наборы данных LeRobot существуют в двух форматах. В `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) добавляет ZSTD в дополнение к встроенному в hyparquet SNAPPY. Оба импортируются по требованию, поэтому вызывающие стороны, которым нужны только `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` несколько эпизодов разделяют один файл, поэтому нужно перейти к `fromSec` и остановиться на `toSec`; в `v2.x` у каждого эпизода свой файл, и диапазон просто охватывает его.

`data` указывает на строки кадров для данного эпизода, для вызывающих сторон, которые хотят самостоятельно построить графики `observation.state` или `action`. `fromRow` включительно, а `toRow` исключительно; оба являются смещениями строк внутри `data.url`, а не индексами по всему набору данных. Для v3, если страница начинается посередине файла данных, при перечислении эпизодов также читается футер parquet этого файла, чтобы определить, с чего он начинается. `data` отсутствует, если этот файл не может быть прочитан или не соответствует метаданным эпизода.

Держите один `LeRobotDataset` при листании эпизодов: как и `info()`, он запоминает файлы индекса `v3.0`, по которым прошёл, поэтому последующая страница или запрос `episode()` сразу обращаются к нужному файлу.

### Фиксация ревизии

Передавайте commit sha каждый раз, когда результат сохраняется или кэшируется где-либо, чтобы последующий push не мог изменить то, что вы проанализировали:

```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` достаточно сессионных cookie:

```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`, не поддерживаемый этим пакетом.

