File size: 4,713 Bytes
700dd75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# Coordinate Frame and Rotation Conventions

This page documents the coordinate frame, quaternion, and rotation conventions
used throughout the SONIC codebase. Getting these wrong causes silent bugs β€”
the robot will move but in the wrong direction or with wrong orientation.

## Coordinate Frames

### Isaac Lab / MuJoCo (simulation)

- **Z-up**: Gravity is along -Z. Ground plane is XY.
- **Right-handed**: X forward, Y left, Z up.
- This is the convention used during training and evaluation.

### SMPL / BVH (human motion data)

- **Y-up**: Gravity is along -Y. Ground plane is XZ.
- When loading SMPL or BVH data, set `smpl_y_up: true` in the motion library
  config. The motion library automatically converts Y-up to Z-up internally.

### Summary

| System | Up axis | Convention |
|--------|---------|------------|
| Isaac Lab | Z | Z-up, right-handed |
| MuJoCo | Z | Z-up, right-handed |
| SMPL body model | Y | Y-up |
| BVH motion files | Y | Y-up |
| Retargeted PKL data | Z | Z-up (already converted) |

## Quaternion Convention

### Scalar-first (wxyz) β€” default throughout SONIC

The SONIC codebase uses **scalar-first (wxyz)** quaternions everywhere:

```
q = [w, x, y, z]
```

This applies to:

- `gear_sonic/trl/utils/torch_transform.py` β€” all rotation utilities
- `gear_sonic/isaac_utils/rotations.py` β€” Isaac Lab rotation helpers (use `w_last=False`)
- Isaac Lab APIs (`body_quat_w`, `root_quat_w`, etc.)
- Motion library internal storage
- Retargeted PKL data (`root_rot` field)

### Scalar-last (xyzw) β€” scipy only

[SciPy's Rotation class](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.html)
uses **scalar-last (xyzw)** convention:

```
q = [x, y, z, w]
```

This is only used in the **data processing scripts** (`data_process/`) when
calling `scipy.spatial.transform.Rotation`. The scripts convert to wxyz
before saving:

```python
# In data processing (scipy xyzw β†’ wxyz for storage)
root_quat_xyzw = Rotation.from_euler("xyz", euler_angles).as_quat()  # scipy: xyzw
root_quat_wxyz = root_quat_xyzw[:, [3, 0, 1, 2]]                    # convert to wxyz
```

### The `w_last` parameter

Functions in `gear_sonic/isaac_utils/rotations.py` accept a `w_last` boolean:

```python
quat_rotate(q, v, w_last=False)   # q is wxyz (scalar-first) β€” this is the default
quat_rotate(q, v, w_last=True)    # q is xyzw (scalar-last)
```

**Always use `w_last=False`** unless you're interfacing with scipy or a system
that explicitly uses xyzw.

### Quick reference

| System | Convention | Order | Identity |
|--------|-----------|-------|----------|
| SONIC (torch_transform.py) | wxyz | `[w, x, y, z]` | `[1, 0, 0, 0]` |
| Isaac Lab | wxyz | `[w, x, y, z]` | `[1, 0, 0, 0]` |
| SciPy | xyzw | `[x, y, z, w]` | `[0, 0, 0, 1]` |
| MuJoCo | wxyz | `[w, x, y, z]` | `[1, 0, 0, 0]` |
| ROS | xyzw | `[x, y, z, w]` | `[0, 0, 0, 1]` |

### Converting between conventions

```python
# wxyz β†’ xyzw
q_xyzw = q_wxyz[..., [1, 2, 3, 0]]

# xyzw β†’ wxyz
q_wxyz = q_xyzw[..., [3, 0, 1, 2]]
```

## Rotation Representations

The codebase uses multiple rotation representations depending on context:

| Representation | Shape | Used in |
|---------------|-------|---------|
| Quaternion (wxyz) | `(..., 4)` | Simulation, motion library, observations |
| Axis-angle | `(..., 3)` | `pose_aa` field in motion PKLs |
| Rotation matrix | `(..., 3, 3)` | Forward kinematics, 6D rotation encoding |
| 6D rotation | `(..., 6)` | Some observation terms (first 2 columns of rotation matrix) |
| Euler angles | `(..., 3)` | CSV motion data input (converted immediately) |

### Axis-angle in motion data

The `pose_aa` field in retargeted PKL files stores per-body **local** rotations
as axis-angle vectors. The direction is the rotation axis, the magnitude is
the angle in radians:

```python
pose_aa  # (T, num_bodies, 3) β€” axis-angle per body, MuJoCo body order
```

## Joint Ordering

Isaac Lab and MuJoCo traverse the kinematic tree in different orders. The
codebase provides bidirectional index mappings per robot:

```python
from gear_sonic.envs.manager_env.robots.g1 import (
    G1_ISAACLAB_TO_MUJOCO_DOF,   # Reorder DOFs: IsaacLab β†’ MuJoCo
    G1_MUJOCO_TO_ISAACLAB_DOF,   # Reorder DOFs: MuJoCo β†’ IsaacLab
)

# Convert joint positions from IsaacLab order to MuJoCo order:
mujoco_joints = isaaclab_joints[..., G1_ISAACLAB_TO_MUJOCO_DOF]
```

Motion PKL data (`dof`, `pose_aa`) is stored in **MuJoCo order**. Isaac Lab
simulation uses **IsaacLab order**. The training pipeline handles the conversion
automatically via `order_converter.py`.

See [Training on New Embodiments](../user_guide/new_embodiments.md) for how to
define these mappings for a new robot.