CoolFace
Datasetpublic

HaptalAI/so100-curated

HaptalAI / SO-100 Curated Manipulation Pack A quality-filtered, failure-labelled merge of SO-100 community manipulation datasets. Built for imitation learning, RL from demonstrations, and failure-aware training. What This Is The SO-100 open-source robot arm has generated dozens of small community datasets on HuggingFace, each contributed by independent researchers and hobbyists. Individually these datasets are too small and inconsistent to train on reliably.… See the full description on the dataset page: https://huggingface.co/datasets/HaptalAI/so100-curated.

sourceHugging Faceapache-2.0updated 4mo agoView on Hugging Face
0likes59downloads
Dataset Card

HaptalAI / SO-100 Curated Manipulation Pack

A quality-filtered, failure-labelled merge of SO-100 community manipulation datasets. Built for imitation learning, RL from demonstrations, and failure-aware training.

What This Is

The SO-100 open-source robot arm has generated dozens of small community datasets on HuggingFace, each contributed by independent researchers and hobbyists. Individually these datasets are too small and inconsistent to train on reliably. This pack:

  • Merges 13 community SO-100/SO-101 datasets into a single parquet file
  • Classifies every episode as CLEAN (47) or FAILURE (48); removes CORRUPTED episodes (1) entirely
  • Keeps failure episodes — labelled but not discarded — because failure data is valuable for reinforcement learning and anomaly-aware imitation
  • Provides full provenance via source_dataset and original_episode_id columns

Curation Methodology

Why We Keep Failure Episodes

Most curated datasets silently discard failures. We believe this is a mistake:

  • RL from demonstrations needs to distinguish successful from failed rollouts
  • Failure-conditioned imitation (DAgger, HG-DAgger) requires negative examples
  • Anomaly detection for safety systems needs real failure signatures
  • Contrastive training — failure episodes teach the policy what not to do

Failures are flagged with use_for_training=false and a failure_type tag so downstream users can include or exclude them with a single filter.

Classification Rules

All classification streams parquet data only — no video is downloaded.

LabelConditionAction
CORRUPTEDNaN/Inf >50% frames; all channels frozen (std=0); empty episodeRemoved entirely
FAILUREVelocity spike (z-score > 6.5); action-state mismatch (>50% frames off by >20% of range)Kept, `use_for_training=false`
CLEANPasses all checksKept, `use_for_training=true`

Episode boundaries inferred from episode_index column; fallback to fixed 200-frame windows.

Column Schema

ColumnTypeDescription
source_datasetstringHuggingFace dataset ID this episode came from
original_episode_idstringEpisode key as it appeared in the source dataset
frame_indexint320-based timestep index within the episode
use_for_trainingbooltrue = clean; false = failure (keep for RL)
failure_typestringnone \velocity_spike \action_state_mismatch
quality_scorefloat32Quality score \[0,1\]; 1.0 = perfect
episode_lengthint32Total frames in this episode (same for all rows of an episode)
state_0state_6float64Joint position / observation state (7 dims for SO-100)
action_0action_6float64Commanded action targets (7 dims for SO-100)
Layout: one row per timestep. Episode metadata (use_for_training, failure_type, etc.) is repeated on every row, enabling direct pandas filtering without joins.

How to Load

python
from datasets import load_dataset
import pandas as pd, numpy as np

# Load everything (one row = one timestep)
ds = load_dataset("HaptalAI/so100-curated", split="train")
df = ds.to_pandas()

# Filter: only clean timesteps for BC / imitation learning
clean_df = df[df["use_for_training"] == True]

# Filter: only failure timesteps for RL / anomaly work
fail_df = df[df["use_for_training"] == False]

# Reconstruct one full episode as a trajectory tensor
ep_df = df[(df["source_dataset"] == "cadene/so100_test") &
           (df["original_episode_id"] == "ep_0000")]
state  = ep_df[[f'state_{i}'  for i in range(7)]].to_numpy()   # (T, 7)
action = ep_df[[f'action_{i}' for i in range(7)]].to_numpy()   # (T, 7)

# Or load directly from parquet
df = pd.read_parquet("episodes.parquet")

Stats

MetricValue
Source datasets13
Total episodes kept95
Clean episodes (use_for_training=true)47
Failure episodes (use_for_training=false)48
Corrupted episodes removed1
Total timestep rows in parquet49,073
Parquet file size0.8 MB
Generated2026-05-24

Attribution

See sources.md for the full per-dataset breakdown with episode counts, authors, and licenses.

All original data remains under its original license. This curated pack adds quality labels and is released under Apache 2.0.

Limitations

  • Episode classification is heuristic — some edge cases may be mislabelled
  • Trajectory columns vary by source; not all episodes have all columns
  • Video observations are not included (parquet-only streaming)
  • Gated / authentication-required datasets are excluded
  • This is a community effort; verify episodes before production training

Contact

Questions, corrections, or additional source datasets: aarav@haptal.ai

Built with ❤️ for the open-source robotics community.