All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
Image-mode output for the LeRobot v3 writer.
LeRobotV3WriterConfig(video=False)PNG-encodes each camera frame inline into the data parquet (dtype: "image"features) instead of encoding MP4s — novideos/directory and no per-cameravideos/…pointer columns are written, andinfo.json'svideo_path/video_files_size_in_mbarenull(the keys stay present, as v3 loaders require). Set it from a conversion config withwriter_config: {video: false}. Needs Pillow (now part of the[lerobot]extra). Video mode stays the default and is unchanged. The LeRobot v3 reader decodes these inline PNG columns, so image-mode datasets round-trip through Forge. -
Streaming reads for cloud datasets (LeRobot-v3, Zarr).
forge inspectandforge ingestons3://…/gs://…now read over the network with range requests instead of downloading the whole dataset.- Inspect fetches metadata only — a 464 MB cloud LeRobot-v3 dataset yields
a full inspect from ~3 KB (its
info.json), a ~150,000× reduction, in a fraction of a second. - Ingest (register + quality-score) reads only the proprio parquet columns (state/action/timestamp) via column/range reads — never the videos. Ingesting the same 464 MB dataset streams ~3 MB and produces identical episodes and quality scores. Content hashes are identical whether an episode is streamed or downloaded, so cross-path re-ingest is idempotent.
- LeRobot-v3 gets a proprio-only streaming
read_episodes; Zarr streams via its native fsspec store.forge inspect --deep, embeddings, and video quality still fetch frames; other formats/commands are unchanged (download-to-temp). Newforge.io.DataSourcehelper; readers keep their local paths untouched.
- Inspect fetches metadata only — a 464 MB cloud LeRobot-v3 dataset yields
a full inspect from ~3 KB (its
-
Search- and selection-driven curation.
forge curatenow selects episodes three ways — a SQL--wherepredicate, an explicit--idslist, or a--from <selection.json>file — so semantic search and Forge Studio can drive curation, not just SQL.forge search --save sel.jsonwrites the result ids (with provenance) and Studio's dedup tab exports a matching decisions file;forge curate --from sel.jsonapplies them and records the source inlabeled_by. Explicit ids are filtered to episodes that exist (no dangling labels) and can be intersected with--where. -
Catalog dedup, curation + Forge Studio (Phase 3). Find near-duplicate episodes from the embeddings and curate a clean, labeled training set.
forge catalog dedup -c ./forge-catalog --threshold 0.97 forge curate -c ./forge-catalog --where "overall_score > 6" \ --dedup 0.97 --dedup-policy keep-higher-quality --label approved forge studio -c ./forge-catalog -o studio.html- Two new tables (bumps catalog
SCHEMA_VERSION2 → 3, additive):dedup_edges(near-dup pairs as facts — similarity, not verdicts) andcuration_labels(an append-log of approve/reject/hold decisions, latest-row-wins). forge catalog dedupcomputes near-dup pairs (cosine over episode embeddings, max over shared cameras; idempotent).forge curateapplies a WHERE filter + a dedup policy (keep-higher-quality/keep-longer/keep-first) and labels survivors approved, dedup losers rejected.- DuckDB views/macros:
v_curation(latest label per episode) andv_dup_losers(threshold, policy). forge studiogenerates a self-contained, themed HTML app (Overview · Corpus · Dedup review · Snapshot) from real catalog data and embedded video thumbnails — one shareable file, no server.- Reuses the Phase 2 vectors, the readers for thumbnails, and the catalog
writer/commit machinery. The per-dataset
forge dedup(perceptual-hash) is unchanged; catalog dedup lives underforge catalog dedup. - See forge/catalog/README.md.
- Two new tables (bumps catalog
-
Catalog embeddings + semantic search (Phase 2). Embed the episodes in a catalog and search them by natural language.
pip install "forge-robotics[embed]" forge embed --catalog ./forge-catalog forge search "picks up the red cup" -c ./forge-catalog --top 10 forge search --like <episode_id> -c ./forge-catalog
- New
embeddingstable (bumps catalogSCHEMA_VERSION1 → 2, additive — v1 catalogs open unchanged). One row per(episode_id, model_id, level, camera): episode-level vision vectors per camera + an instruction (text) vector. - New
forge/embed/engine — anEmbeddingModelregistry with a SigLIP (siglip-so400m, shared image–text space) implementation, so text queries match episode video. Device auto-selects CUDA → Apple MPS → CPU. forge embed(backfill) andforge ingest --embed(opt-in stage);forge search(text and--like) andCatalog.search(...). Brute-force cosine in DuckDB — sub-second at lab scale.- Vectors are versioned per model (
model_id = <name>@<ckpt-hash>, reproducible across machines); search enforces a singlemodel_id(never mixes vector spaces). - Reuses existing internals — the readers behind
forge inspectfor frames,forge.iofor cloud sources,[video]for decode, and the catalog writer/commit machinery. New deps behind the[embed]extra (torch, transformers); the base CLI never imports them. - See forge/embed/README.md.
- New
-
The catalog (Phase 1) — an append-only, queryable registry of episodes. Turns Forge from a per-dataset tool into a system of record: a set of append-only Parquet tables (
episodes,quality_scores) written with pyarrow and queried with embedded DuckDB, on a local directory or ans3:///gs://bucket.pip install "forge-robotics[catalog]" forge catalog init ./forge-catalog forge ingest ./my_dataset --catalog ./forge-catalog forge query "SELECT task, count(*) FROM episodes GROUP BY task" -c ./forge-catalog forge catalog stats --catalog ./forge-catalog
- New module
forge/catalog/with aCatalogclass as the single entry point (from forge.catalog import Catalog), plusforge/catalog/ingest.py. - New CLI commands:
forge catalog init,forge catalog stats,forge ingest,forge query(SQL overepisodes,quality_scores, and av_latest_qualityview;--format table|json|csv). - Ingestion reuses existing internals — the same format readers behind
forge inspectfor metadata andQualityAnalyzer.analyze_episode(the engine behindforge quality) for scoring. No metadata extraction or scoring logic was reimplemented, and no existing behavior changed. - Idempotent & crash-safe. Episodes are keyed by a content hash, so re-running an ingest over the same sources is a no-op. Each flush commits atomically via a manifest-last protocol; queries read only manifested part-files, so a crash never exposes a partial batch.
- New deps behind the
[catalog]extra:duckdb,xxhash(and an explicitpyarrow). The base CLI never imports them — they load only when a catalog command runs. - See forge/catalog/README.md and docs/forge_data_engine_design.md.
- New module
-
Cloud storage support (
s3://,gs://). Every command that accepts a dataset path now also accepts Amazon S3 and Google Cloud Storage URIs, in addition to local paths andhf://URLs. For example:forge inspect s3://my-bucket/datasets/run_0413 forge convert gs://lab-data/rosbags ./out --format lerobot-v3 forge quality s3://my-bucket/datasets/droid --report report.html
- Backed by fsspec (now a core
dependency), with
s3fs/gcsfsas optional extras:pip install "forge-robotics[s3]"or[gcs]. Passing ans3://URI withouts3fsinstalled fails with the exactpip installcommand to run. - All filesystem access is routed through a single utility,
forge.io.paths. Remote datasets are downloaded to a temporary directory on first access and cleaned up automatically at process exit, so every format (including video, HDF5, and rosbag, which need random file access) behaves identically to local paths. - Authentication uses each provider's default credential chain (AWS env vars / profiles / IAM roles; GCP Application Default Credentials). Forge never handles credentials itself.
- Backed by fsspec (now a core
dependency), with
writer_confignow reaches parallel conversion workers.ConversionConfigdroppedwriter_configwhen serialized, so with--workers > 1every writer-specific option (including the newvideo: false) was silently ignored by the worker processes.
fsspecis now a core dependency.
- Writing outputs directly to cloud URIs (e.g.
forge convert … s3://bucket/out) is not supported yet; commands fail fast with a clear message. Write to a local directory and upload it afterwards. - Remote datasets are downloaded in full before processing. True range-read
streaming (reading parquet/zarr remotely without a full download) is a
planned follow-up; the fsspec plumbing in
forge.io.pathsis in place for it.