Skip to content

Generalize LeRobot dataset converters to N-dof robots#165

Open
asierarranz wants to merge 1 commit into
LightwheelAI:mainfrom
asierarranz:aarranz/generalize-converter-n-dof
Open

Generalize LeRobot dataset converters to N-dof robots#165
asierarranz wants to merge 1 commit into
LightwheelAI:mainfrom
asierarranz:aarranz/generalize-converter-n-dof

Conversation

@asierarranz

Copy link
Copy Markdown

Description

Right now the dataset converters only really work for the SO-101: point them at any other arm and the extra joints are silently written as zeros. No error, no warning, just a corrupted dataset. That is exactly what #142 and #136 ran into, and what we hit while onboarding an 8-dof arm (reBot DevArm, 6 revolute + 2 prismatic joints). This PR makes the converters work for any arm.

What changed:

  • convert_leisaac_action_to_lerobot() and convert_lerobot_action_to_leisaac() take optional joint_limits / motor_limits and validate that every converted dimension has a configured limit, instead of silently zero-filling.
  • The task templates expose usd_joint_limits / motor_limits cfg fields and derive arm slicing from the configured joint count. A new robot only overrides its env cfg, same mechanism as default_feature_joint_names.
  • build_feature_from_env() checks that joint names are non-empty and unique.
  • isaaclab2lerobot.py and isaaclab2lerobotv3.py now raise if zero episodes were converted instead of writing an empty dataset. That silent empty output cost us an hour of debugging.
  • Short note in policy_support.md on how the converters pick up joint names, count and ranges from the task cfg.

Zero behavior change for existing SO-101, bi-SO101 and LeKiwi tasks: all defaults are the same SO-101 limit tables as before, and the converted values stay bit-identical (verified).

Test plan: converted 40 XR teleop demos of the 8-dof arm with isaaclab2lerobotv3.py and fine-tuned GR00T N1.7 on the result, end to end. The repo's black / isort / flake8 checks are clean on the touched files.

This could also give #156 a common base, instead of adding per-robot copies of the conversion functions.

The isaaclab2lerobot(v3) conversion path hardcoded the 6 SO-101 joints:
convert_leisaac_action_to_lerobot() always used the SO-101 limit tables
and silently zeroed any extra action dimensions, and the frame builders
sliced actions with a literal 6.

- robot_utils: convert_leisaac_action_to_lerobot() and
  convert_lerobot_action_to_leisaac() accept per-robot
  joint_limits/motor_limits (defaulting to the SO-101 follower) and
  validate that every converted dimension has a configured limit
  instead of silently dropping values.
- build_feature_from_env() validates that default_feature_joint_names
  is non-empty and unique; feature shapes already derive from it.
- Task templates expose usd_joint_limits/motor_limits cfg fields and
  derive arm slicing from the configured joint count.
- isaaclab2lerobot(v3).py fail loudly instead of writing an empty
  LeRobot dataset when every episode is skipped.
- Docs: note that the converters take joint names, joint count, and
  value ranges from the task's environment configuration.

Existing SO-101, bi-SO101, and LeKiwi tasks see no behavior change:
all defaults remain the SO-101 follower limits.

Validated by onboarding an 8-dof arm (6 revolute + 2 prismatic joints)
end to end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@EverNorif EverNorif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your PR. I’ve left a few comments.

if now_episode_index == 0:
raise RuntimeError(
"No episodes were converted: every episode was skipped because it was unsuccessful or"
" shorter than 10 frames. Refusing to write an empty LeRobot dataset."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this should be “shorter than 5 frames.” Alternatively, we could provide a global SKIP_FRAMES parameter to control this.

if now_episode_index == 0:
raise RuntimeError(
"No episodes were converted: every episode was skipped because it was unsuccessful or"
" shorter than 10 frames. Refusing to write an empty LeRobot dataset."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants