zhicao's picture
Upload dreamzero source code (no model checkpoints)
fbd9366 verified
Raw
History Blame Contribute Delete
6.7 kB
"""Online 20 Hz / 5 Hz execution helpers for T-Rex Track-Force."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any
import numpy as np
import torch
from .force import FORCE_OFFSETS
from .policy import WANTrackForcePolicyHead
from .dataset import rotation_6d_to_matrix
def _matrix_to_rotation_6d(matrix: np.ndarray) -> np.ndarray:
return np.concatenate((matrix[..., :, 0], matrix[..., :, 1]), axis=-1)
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)
@dataclass
class TactileRefinementState:
coarse_action_at_split: torch.Tensor
coarse_memory: torch.Tensor
refined_action: torch.Tensor
latest_offset: int = 0
class TrexTrackForceOnlineController:
"""Cache coarse output and refresh only suffixes at steps 0/4/8/12."""
def __init__(self, policy: WANTrackForcePolicyHead) -> None:
self.policy = policy
self.state: TactileRefinementState | None = None
@torch.no_grad()
def start_chunk(
self,
model_inputs: dict[str, Any],
*,
seed: int | None = None,
) -> torch.Tensor:
result = self.policy.sample(
model_inputs, seed=seed, return_refinement_state=True
)
self.state = TactileRefinementState(
coarse_action_at_split=result["coarse_action_at_split"].detach(),
coarse_memory=result["coarse_memory"].detach(),
refined_action=result["action_pred"].detach(),
latest_offset=0,
)
return self.state.refined_action
@torch.no_grad()
def refresh_tactile(
self,
current_force: torch.Tensor,
tactile_history_or_codes: torch.Tensor,
*,
executed_steps: int,
) -> torch.Tensor:
if self.state is None:
raise RuntimeError("start_chunk must be called before tactile refresh")
if executed_steps not in FORCE_OFFSETS:
raise ValueError(f"executed_steps must be one of {FORCE_OFFSETS}")
if executed_steps < self.state.latest_offset:
raise ValueError("tactile refresh offsets must be monotonic")
refreshed = self.policy.refine_action_suffix(
self.state.coarse_action_at_split,
self.state.coarse_memory,
current_force,
tactile_history_or_codes,
update_offset=executed_steps,
)
# Preserve commands already emitted from earlier refinements.
refreshed[:, :executed_steps] = self.state.refined_action[:, :executed_steps]
self.state.refined_action = refreshed.detach()
self.state.latest_offset = executed_steps
return self.state.refined_action
def action_at(self, step: int) -> torch.Tensor:
if self.state is None:
raise RuntimeError("no active action chunk")
if not 0 <= step < self.state.refined_action.shape[1]:
raise IndexError(step)
return self.state.refined_action[:, step]
__all__ = [
"TactileRefinementState",
"TrexRuntimeStatistics",
"TrexTrackForceOnlineController",
"delta_base_to_absolute",
]