FACT / fact /runtime_stats.py
zhicao's picture
Add FACT training code
af8c481 verified
Raw History Blame Contribute Delete
4 kB
"""Dataset normalisation statistics and delta-base inversion for online execution."""
from __future__ import annotations
import json
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from .data_utils import matrix_to_rotation_6d as _matrix_to_rotation_6d
from .data_utils import rotation_6d_to_matrix
def delta_base_to_absolute(
reference_state: np.ndarray, delta_base_action: np.ndarray
) -> np.ndarray:
"""Invert T-Rex delta-base arm poses; hand targets are already absolute."""
reference = np.asarray(reference_state, dtype=np.float64)
delta = np.asarray(delta_base_action, dtype=np.float64)
if reference.shape != (62,) or delta.shape[-1] != 62:
raise ValueError("expected reference [62] and delta action [...,62]")
output = np.empty_like(delta)
for pose_slice, hand_slice in (
(slice(0, 9), slice(9, 31)),
(slice(31, 40), slice(40, 62)),
):
reference_pose = reference[pose_slice]
delta_pose = delta[..., pose_slice]
reference_rotation = rotation_6d_to_matrix(reference_pose[3:9])
delta_rotation = rotation_6d_to_matrix(delta_pose[..., 3:9])
target_translation = (
reference_pose[:3]
+ np.einsum("ij,...j->...i", reference_rotation, delta_pose[..., :3])
)
target_rotation = np.einsum(
"ij,...jk->...ik", reference_rotation, delta_rotation
)
output[..., pose_slice] = np.concatenate(
(target_translation, _matrix_to_rotation_6d(target_rotation)), axis=-1
)
output[..., hand_slice] = delta[..., hand_slice]
return output.astype(np.float32)
@dataclass(frozen=True)
class TrexRuntimeStatistics:
action_q01: np.ndarray
action_q99: np.ndarray
state_q01: np.ndarray
state_q99: np.ndarray
force_q01: np.ndarray
force_q99: np.ndarray
@classmethod
def from_dataset(cls, dataset_root: str | Path) -> "TrexRuntimeStatistics":
root = Path(dataset_root)
with (root / "meta" / "relative_stats_dreamzero.json").open() as handle:
action = json.load(handle)["eef62"]
with (root / "meta" / "stats.json").open() as handle:
stats = json.load(handle)
force = stats["observation.tactile_force"]
state = stats["observation.state_eef62"]
return cls(
action_q01=np.asarray(action["q01"], dtype=np.float32),
action_q99=np.asarray(action["q99"], dtype=np.float32),
state_q01=np.asarray(state["q01"], dtype=np.float32),
state_q99=np.asarray(state["q99"], dtype=np.float32),
force_q01=np.asarray(force["q01"], dtype=np.float32).reshape(10, 6),
force_q99=np.asarray(force["q99"], dtype=np.float32).reshape(10, 6),
)
def normalize_force(self, force: np.ndarray) -> np.ndarray:
values = np.asarray(force, dtype=np.float32)
scale = self.force_q99 - self.force_q01
normalized = np.where(
scale != 0,
2.0 * (values - self.force_q01) / np.where(scale == 0, 1.0, scale)
- 1.0,
values,
)
return np.clip(normalized, -1.0, 1.0).astype(np.float32)
def normalize_state(self, state: np.ndarray) -> np.ndarray:
values = np.asarray(state, dtype=np.float32)
scale = self.state_q99 - self.state_q01
normalized = np.where(
scale != 0,
2.0 * (values - self.state_q01) / np.where(scale == 0, 1.0, scale)
- 1.0,
values,
)
return np.clip(normalized, -1.0, 1.0).astype(np.float32)
def denormalize_action(self, normalized_action: np.ndarray) -> np.ndarray:
values = np.asarray(normalized_action, dtype=np.float32)[..., :62]
return (
(values + 1.0) * 0.5 * (self.action_q99 - self.action_q01)
+ self.action_q01
).astype(np.float32)
__all__ = ["TrexRuntimeStatistics", "delta_base_to_absolute"]