Download GR00T-WholeBodyControl/gear_sonic/utils/data_collection/transforms.py from Timsty/groot_deployment: direct link, hf CLI and curl.
- Browser
- Download file 3.41 kB
-
https://huggingface.co/Timsty/groot_deployment/resolve/main/GR00T-WholeBodyControl/gear_sonic/utils/data_collection/transforms.py
- Command line
-
hf download hf://Timsty/groot_deployment/GR00T-WholeBodyControl/gear_sonic/utils/data_collection/transforms.py
-
curl -L -o transforms.py https://huggingface.co/Timsty/groot_deployment/resolve/main/GR00T-WholeBodyControl/gear_sonic/utils/data_collection/transforms.py
3.41 kB
| """Rotation and gravity transform utilities for data collection.""" | |
| import numpy as np | |
| from scipy.spatial.transform import Rotation as R | |
| def quat_to_rot6d(q): | |
| """Convert scalar-first quaternion(s) (wxyz) to 6D rotation representation. | |
| The 6D representation consists of the first two columns of the rotation | |
| matrix, flattened (Zhou et al., CVPR 2019). | |
| Accepted input shapes: | |
| * ``(4,)`` -- single quaternion -> returns ``(6,)`` | |
| * ``(N, 4)`` -- batch of quats -> returns ``(N, 6)`` | |
| * ``(N*4,)`` -- flat concatenated -> returns ``(N*6,)`` | |
| """ | |
| q = np.asarray(q) | |
| if q.ndim == 1 and q.shape[0] > 4: | |
| assert q.shape[0] % 4 == 0, f"Flat quat length {q.shape[0]} is not divisible by 4" | |
| q = q.reshape(-1, 4) | |
| rot_6d = quat_to_rot6d(q) | |
| return rot_6d.ravel() | |
| single = q.ndim == 1 | |
| q = np.atleast_2d(q) | |
| q_xyzw = q[:, [1, 2, 3, 0]] | |
| rot_mat = R.from_quat(q_xyzw).as_matrix() # (N, 3, 3) | |
| rot_6d = rot_mat[:, :, :2].transpose(0, 2, 1).reshape(-1, 6) # (N, 6) | |
| if single: | |
| return rot_6d[0].astype(q.dtype) | |
| return rot_6d.astype(q.dtype) | |
| def rot6d_to_quat(r): | |
| """Convert 6D rotation representation to scalar-first quaternion(s) (wxyz). | |
| Accepted input shapes: | |
| * ``(6,)`` -- single rot6d -> returns ``(4,)`` | |
| * ``(N, 6)`` -- batch -> returns ``(N, 4)`` | |
| * ``(N*6,)`` -- flat concat -> returns ``(N*4,)`` | |
| (length must be divisible by 6) | |
| Args: | |
| r: 6D rotation array (first two columns of rotation matrix, row-major). | |
| Returns: | |
| Quaternion array in wxyz order. | |
| """ | |
| r = np.asarray(r, dtype=np.float64) | |
| if r.ndim == 1 and r.shape[0] > 6: | |
| assert r.shape[0] % 6 == 0, f"Flat rot6d length {r.shape[0]} is not divisible by 6" | |
| r = r.reshape(-1, 6) | |
| quats = rot6d_to_quat(r) | |
| return quats.ravel() | |
| single = r.ndim == 1 | |
| r = np.atleast_2d(r) # (N, 6) | |
| col0 = r[:, :3] | |
| col1 = r[:, 3:] | |
| col0 = col0 / (np.linalg.norm(col0, axis=1, keepdims=True) + 1e-8) | |
| dot = np.sum(col0 * col1, axis=1, keepdims=True) | |
| col1 = col1 - dot * col0 | |
| col1 = col1 / (np.linalg.norm(col1, axis=1, keepdims=True) + 1e-8) | |
| col2 = np.cross(col0, col1) | |
| rot_mat = np.stack([col0, col1, col2], axis=-1) # (N, 3, 3) | |
| q_xyzw = R.from_matrix(rot_mat).as_quat() # (N, 4) xyzw | |
| q_wxyz = q_xyzw[:, [3, 0, 1, 2]] | |
| if single: | |
| return q_wxyz[0].astype(np.float32) | |
| return q_wxyz.astype(np.float32) | |
| def compute_projected_gravity(base_quat: np.ndarray) -> np.ndarray: | |
| """Compute projected gravity vector in robot's body frame from base quaternion. | |
| Projects the world gravity vector [0, 0, -1] into the robot's body frame by | |
| rotating it by the inverse of the base quaternion. | |
| Args: | |
| base_quat: Base quaternion [qw, qx, qy, qz] of shape (4,) | |
| Returns: | |
| Projected gravity vector [gx, gy, gz] of shape (3,) in robot's body frame | |
| """ | |
| base_quat = np.asarray(base_quat, dtype=np.float64) | |
| if base_quat.shape != (4,): | |
| raise ValueError(f"base_quat must have shape (4,), got {base_quat.shape}") | |
| gravity_vec_world = np.array([0.0, 0.0, -1.0]) | |
| base_rotation = R.from_quat(base_quat, scalar_first=True) | |
| projected_gravity = base_rotation.inv().apply(gravity_vec_world) | |
| return projected_gravity.astype(np.float32) | |