Local fullstack tool for curating LeRobot datasets. Play synchronized camera streams in the browser, assign grades/tags, and edit task instructions — all persisted directly to the dataset's parquet files.
┌─────────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ React SPA │────>│ FastAPI │────>│ Local Dataset │
│ Native video player │ │ :8000 │ │ parquet + video │
│ :5173 │ └──────────────┘ └─────────────────┘
└─────────────────────┘
| Component | Tech | Port |
|---|---|---|
| Backend | FastAPI + PyArrow | 8000 |
| Frontend | React + TypeScript + Vite | 5173 |
| Visualization | Browser-native synchronized video playback | Frontend |
- Python 3.12+
- Node.js 18+
- uv (recommended) or pip
- ffmpeg — for serving episode-bounded video clips in the native curation viewer
- hf-mount — for mounting HF datasets
- nfs-common (
sudo apt install nfs-common -y)
# Clone with submodule
git clone --recurse-submodules https://github.com/weedmo/robodata.git
cd robodata
# If already cloned without submodules
git submodule update --init --recursive
# The converter flow depends on the bundled rosbag2lerobot-svt submodule# Python environment
uv venv .venv
source .venv/bin/activate
uv pip install -e .# Frontend dependenciescd frontend && npm install &&cd .../start.shThis starts PostgreSQL in Docker plus the backend and frontend development servers. Open http://localhost:5173 in your browser.
# Terminal 1: Backendsource .venv/bin/activate
uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload
# Terminal 2: Frontendcd frontend && npm run devdocker compose -f docker/compose.yml up --build -dOpen http://localhost:18080.
Notes:
nginxserves the frontend bundle and proxies/api/*to the FastAPI app.appruns FastAPI only; it is not exposed directly on the host.- The
converterandcuration-workercontainers stay up 24/7. Job lifecycle is decoupled from container lifecycle — see "Operating the converter / curation-worker" below. - If
18080is already in use, run withCURATION_UI_PORT=28080 docker compose -f docker/compose.yml up --build -d.
컨테이너는 compose 기동 시 1회 up되고 이후 24/7 상주합니다. 작업 라이프사이클은 컨테이너 라이프사이클과 분리됩니다.
- 변환 시작: UI의 "Convert" 버튼 또는
curl -X POST /api/jobs -H 'Content-Type: application/json' -d '{"type":"convert","payload":{"cell":"<cell_path>"}}' - 작업 취소: UI의 "현재 작업 취소" 버튼 또는
curl -X POST /api/jobs/<id>/cancel - 워커 일시정지/재개: UI의 Pause/Resume pill 또는
curl -X PATCH /api/workers/converter -H 'Content-Type: application/json' -d '{"desired_state":"paused"}' - 코드 배포로 컨테이너 재기동이 필요할 때:
docker compose -f docker/compose.yml restart converter(운영자 전용. 일상 흐름에서는 사용하지 않음.)
CURATION_CONVERTER_CONTROL_MODE env 와 convert_runtime.json/convert_stop.flag/
convert_requests.json/convert_events.jsonl 시그널 파일들은 더 이상 사용되지 않습니다.
잔존 파일이 있으면 삭제해도 안전합니다 (NAS 의 lerobot/ 루트).
- Load Dataset — Enter the local path to a LeRobot v3.0 dataset and click "Load"
- Browse Episodes — The episode list appears in the left sidebar with grade badges
- Visualize — Click an episode to play its synchronized camera streams in the native viewer (center panel)
- Grade — Select a grade (A/B/C/D/F) from the dropdown in the right panel
- Tag — Add tags to categorize episodes (e.g., "good_grasp", "collision", "slow")
- Edit Task — Modify the task instruction text if needed
- Save — Click "Save" to persist changes to the parquet files
All changes are written directly to the dataset's parquet files and survive application restarts.
This tool works with LeRobot v3.0 datasets:
dataset/
├── meta/
│ ├── info.json # Dataset metadata (fps, features, robot_type)
│ ├── tasks.parquet # Task descriptions (task_index, task)
│ └── episodes/
│ └── chunk-000/
│ └── file-000.parquet # Episode metadata (+ grade, tags after curation)
├── data/
│ └── chunk-000/
│ └── file-000.parquet # Observation/action data
└── videos/
└── observation.images.*/
└── chunk-000/
└── file-000.mp4 # Camera recordings
| File | Changes | How |
|---|---|---|
meta/episodes/chunk-*/file-*.parquet | grade and tags columns added | New columns appended, original data untouched |
meta/tasks.parquet | task column updated | Existing row modified in-place |
Original observation/action data in data/ and videos/ is never modified.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/datasets/load | Load dataset from local path |
| GET | /api/datasets/info | Get current dataset metadata |
| GET | /api/episodes | List all episodes with grade/tags |
| GET | /api/episodes/{index} | Get single episode |
| PATCH | /api/episodes/{index} | Update grade and/or tags |
| GET | /api/tasks | List all tasks |
| PATCH | /api/tasks/{index} | Update task instruction |
| GET | /api/health | Health check |
| POST | /api/jobs | Enqueue a job (type: convert/split/merge/delete/...) |
| GET | /api/jobs | List jobs (filter by type/status/dataset_id/since) |
| GET | /api/jobs/{id} | Fetch one job (status, payload, progress, result, error) |
| POST | /api/jobs/{id}/cancel | Cooperative cancel of a queued/running job |
| GET | /api/workers | List workers (control + heartbeat join) |
| GET | /api/workers/{id} | Fetch one worker |
| PATCH | /api/workers/{id} | Change desired_state (running/paused/draining/stopped) |
Environment variables (prefix CURATION_):
| Variable | Default | Description |
|---|---|---|
CURATION_FASTAPI_PORT | 8000 | Backend API port |
에이전트와 사람이 GitHub Issue/Project/PR로 작업을 조율하는 방법은 에이전트 GitHub 운영 규칙을 참고하세요.
# Run backend with auto-reloadsource .venv/bin/activate
uvicorn backend.main:app --reload
# Run frontend with HMRcd frontend && npm run dev
# Build frontend for productioncd frontend && npm run buildAuto-mount all HuggingFace repos (models, datasets, spaces) from the Phy-lab organization to the local filesystem.
/tmp/hf-mounts/Phy-lab/
├── model/<model-name>/
├── dataset/<dataset-name>/
└── space/<space-name>/
sudo python3 scripts/hf_auto_mount.py
# For private repos
sudo HF_TOKEN=<token> python3 scripts/hf_auto_mount.pyAlready-mounted repos are automatically skipped.
sudo cp scripts/hf-auto-mount.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now hf-auto-mount.service| Command | Description |
|---|---|
hf-mount status | Check current mount status |
sudo hf-mount stop <mount-path> | Unmount a specific repo |
sudo systemctl restart hf-auto-mount | Restart service (remount all) |
- CPU idle at 0%, active only on file access
- ~20MB memory per mounted repo
- Auto-remounts on reboot when systemd service is enabled
- Run the script again to pick up newly added repos
- Data integrity: All parquet writes are atomic (temp file + rename). Per-file asyncio locks prevent concurrent write corruption.
- Episode lookup: O(1) via episode-to-file index built from
meta/episodes/metadata on dataset load. - Video playback: FastAPI serves the dataset MP4 files while the React player synchronizes native browser video elements.
MIT