mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 18:26:11 +00:00
more fixes
This commit is contained in:
@@ -33,6 +33,18 @@ from lerobot.utils.constants import OBS_ENV_STATE, OBS_IMAGE, OBS_IMAGES, OBS_ST
|
|||||||
from lerobot.utils.utils import get_channel_first_image_shape
|
from lerobot.utils.utils import get_channel_first_image_shape
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_nested_dict(d):
|
||||||
|
result = {}
|
||||||
|
for k, v in d.items():
|
||||||
|
if isinstance(v, dict):
|
||||||
|
result[k] = _convert_nested_dict(v)
|
||||||
|
elif isinstance(v, np.ndarray):
|
||||||
|
result[k] = torch.from_numpy(v)
|
||||||
|
else:
|
||||||
|
result[k] = v
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def preprocess_observation(observations: dict[str, np.ndarray]) -> dict[str, Tensor]:
|
def preprocess_observation(observations: dict[str, np.ndarray]) -> dict[str, Tensor]:
|
||||||
# TODO(aliberts, rcadene): refactor this to use features from the environment (no hardcoding)
|
# TODO(aliberts, rcadene): refactor this to use features from the environment (no hardcoding)
|
||||||
"""Convert environment observation to LeRobot format observation.
|
"""Convert environment observation to LeRobot format observation.
|
||||||
@@ -85,11 +97,7 @@ def preprocess_observation(observations: dict[str, np.ndarray]) -> dict[str, Ten
|
|||||||
return_observations[OBS_STATE] = agent_pos
|
return_observations[OBS_STATE] = agent_pos
|
||||||
|
|
||||||
if "robot_state" in observations:
|
if "robot_state" in observations:
|
||||||
# simply copy nested dict as-is
|
return_observations[f"{OBS_STR}.robot_state"] = _convert_nested_dict(observations["robot_state"])
|
||||||
return_observations[f"{OBS_STR}.robot_state"] = {
|
|
||||||
k: torch.from_numpy(v) if isinstance(v, np.ndarray) else v
|
|
||||||
for k, v in observations["robot_state"].items()
|
|
||||||
}
|
|
||||||
return return_observations
|
return return_observations
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -273,42 +273,47 @@ class LiberoProcessorStep(ObservationProcessorStep):
|
|||||||
def observation(self, observation):
|
def observation(self, observation):
|
||||||
return self._process_observation(observation)
|
return self._process_observation(observation)
|
||||||
|
|
||||||
def _quat2axisangle(self, quat):
|
def _quat2axisangle(self, quat: torch.Tensor) -> torch.Tensor:
|
||||||
"""
|
"""
|
||||||
Converts quaternion to axis-angle format (vectorized for batches).
|
Convert batched quaternions to axis-angle format.
|
||||||
Returns a unit vector direction scaled by its angle in radians.
|
Only accepts torch tensors of shape (B, 4).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
quat (np.array): (B, 4) or (4,) array of quaternions in (x,y,z,w) format
|
quat (Tensor): (B, 4) tensor of quaternions in (x, y, z, w) format
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.array: (B, 3) or (3,) axis-angle exponential coordinates
|
Tensor: (B, 3) axis-angle vectors
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: if input is not a torch tensor
|
||||||
|
ValueError: if shape is not (B, 4)
|
||||||
"""
|
"""
|
||||||
# Handle both batched and single quaternion inputs
|
|
||||||
if quat.ndim == 1:
|
|
||||||
quat = quat[np.newaxis, :] # (1, 4)
|
|
||||||
single_input = True
|
|
||||||
else:
|
|
||||||
single_input = False
|
|
||||||
|
|
||||||
# clip quaternion w component to [-1, 1]
|
if not isinstance(quat, torch.Tensor):
|
||||||
quat = quat.copy()
|
raise TypeError(
|
||||||
quat[:, 3] = np.clip(quat[:, 3], -1.0, 1.0)
|
f"_quat2axisangle expected a torch.Tensor, got {type(quat)}"
|
||||||
|
|
||||||
# compute denominator - sqrt(1 - w^2)
|
|
||||||
den = np.sqrt(1.0 - quat[:, 3] ** 2)
|
|
||||||
|
|
||||||
# for near-zero rotations, return zeros
|
|
||||||
result = np.zeros((quat.shape[0], 3))
|
|
||||||
|
|
||||||
# only compute for non-zero rotations
|
|
||||||
non_zero_mask = den > 1e-10
|
|
||||||
if np.any(non_zero_mask):
|
|
||||||
result[non_zero_mask] = (
|
|
||||||
quat[non_zero_mask, :3]
|
|
||||||
* (2.0 * np.arccos(quat[non_zero_mask, 3]) / den[non_zero_mask])[:, np.newaxis]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if single_input:
|
if quat.ndim != 2 or quat.shape[1] != 4:
|
||||||
return result[0]
|
raise ValueError(
|
||||||
|
f"_quat2axisangle expected shape (B, 4), got {tuple(quat.shape)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
quat = quat.to(dtype=torch.float32)
|
||||||
|
device = quat.device
|
||||||
|
B = quat.shape[0]
|
||||||
|
|
||||||
|
w = quat[:, 3].clamp(-1.0, 1.0)
|
||||||
|
|
||||||
|
den = torch.sqrt(torch.clamp(1.0 - w * w, min=0.0))
|
||||||
|
|
||||||
|
result = torch.zeros((B, 3), device=device)
|
||||||
|
|
||||||
|
mask = den > 1e-10
|
||||||
|
|
||||||
|
if mask.any():
|
||||||
|
angle = 2.0 * torch.acos(w[mask]) # (M,)
|
||||||
|
axis = quat[mask, :3] / den[mask].unsqueeze(1)
|
||||||
|
result[mask] = axis * angle.unsqueeze(1)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user