Skip to content

fix(droid): use extrinsic xyz euler convention for eef rot6d - #757

Open
XingxinHE wants to merge 1 commit into
NVIDIA:mainfrom
XingxinHE:fix/droid-euler-extrinsic-convention
Open

XingxinHE wants to merge 1 commit into
NVIDIA:mainfrom
XingxinHE:fix/droid-euler-extrinsic-convention

Conversation

@XingxinHE

Copy link
Copy Markdown

Summary

Rotation.from_euler("XYZ", ...) in scipy is the intrinsic (rotating-frame) convention R = Rx @ Ry @ Rz, but DROID stores euler angles in the extrinsic fixed-frame xyz convention R = Rz(yaw) @ Ry(pitch) @ Rx(roll) (see droid/misc/transformations.py, which uses R.from_euler("xyz", ...) / .as_euler("xyz") throughout). The extrinsic convention is also what tfg.rotation_matrix_3d.from_euler computes (R = Rz @ Ry @ Rx).

The current code used uppercase "XYZ" while documenting it as "extrinsic" and "equivalent to tfg" — the comment and the call contradicted each other. Since the two conventions produce different rotation matrices for the same angle triple (median ~46° of angular distortion on real DROID data), the pipeline reconstructed the wrong eef rotation per timestep, and it did not match the eef_9d statistics of the released pretrained checkpoints (nvidia/GR00T-N1.7-3B, nvidia/GR00T-N1.7-DROID — computed under the extrinsic/tfg convention).

Changes

  • gr00t/data/state_action/droid_frame.py: switch to scipy "xyz" (lowercase) and document why the spelling is load-bearing.
  • examples/DROID/main_gr00t.py: sync the vendored mirror (dataset builder / real-robot client).
  • scripts/verify_droid_rotation_correction.py: verify against the extrinsic convention.
  • scripts/download_droid_sample.py: clarify the convention comment.
  • demo_data/droid_sample: regenerate eef_9d columns and meta/stats.json / meta/relative_stats.json with the corrected convention (raw cartesian_position columns are untouched; non-eef_9d stats dimensions are byte-identical).
  • Tests: new tests/gr00t/data/state_action/test_droid_frame.py pins the extrinsic/tfg convention (and asserts the intrinsic one differs); tests/examples/test_droid.py gains an AST guard that the vendored mirror stays in sync.

Verification

  • pytest tests/gr00t/data/state_action/test_droid_frame.py tests/examples/test_droid.py -m "not gpu": 14 passed.
  • Regenerated demo eef_9d equals R.from_euler("xyz", euler) @ DROID_EEF_ROTATION_CORRECT exactly for all timesteps.
  • ruff check / ruff format clean on all touched files.

Note

This restores the convention used by the DROID example before commit c5cadf5 (which switched the inference client from "xyz" to "XYZ" based on the incorrect scipy/tfg equivalence claim). Anyone who has post-trained with the interim convention should regenerate their processed dataset with the updated scripts.

scipy Rotation.from_euler("XYZ", ...) is the intrinsic (rotating-frame)
convention R = Rx @ Ry @ Rz, while DROID stores euler angles in the
extrinsic fixed-frame "xyz" convention R = Rz(yaw) @ Ry(pitch) @ Rx(roll)
(see droid/misc/transformations.py in the DROID codebase). Only the
extrinsic convention matches tfg.rotation_matrix_3d.from_euler and the
eef_9d statistics of the released pretrained checkpoints, so the previous
code reconstructed a different rotation per timestep.

- gr00t/data/state_action/droid_frame.py: use scipy "xyz" (lowercase)
  and document the load-bearing convention difference.
- examples/DROID/main_gr00t.py: sync the vendored mirror.
- scripts/verify_droid_rotation_correction.py: verify against "xyz".
- scripts/download_droid_sample.py: clarify the euler convention comment.
- demo_data/droid_sample: regenerate eef_9d columns and stats with the
  corrected convention (raw cartesian_position columns untouched).
- tests: pin the extrinsic convention and guard the vendored mirror.
@XingxinHE

Copy link
Copy Markdown
Author

cc. @ryhalabi

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.

1 participant