rerunrobot/rsl_rl/utils/motion_loader_g1.py

389 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from os.path import join as pjoin
import glob
import json
import logging
import torch
import numpy as np
from pybullet_utils import transformations
from rsl_rl.utils import motion_util
_EPS = np.finfo(float).eps * 4.0
def quaternion_slerp(q0, q1, fraction, spin=0, shortestpath=True):
"""Batch quaternion spherical linear interpolation."""
out = torch.zeros_like(q0)
zero_mask = torch.isclose(fraction, torch.zeros_like(fraction)).squeeze()
ones_mask = torch.isclose(fraction, torch.ones_like(fraction)).squeeze()
out[zero_mask] = q0[zero_mask]
out[ones_mask] = q1[ones_mask]
d = torch.sum(q0 * q1, dim=-1, keepdim=True)
dist_mask = (torch.abs(torch.abs(d) - 1.0) < _EPS).squeeze()
out[dist_mask] = q0[dist_mask]
if shortestpath:
d_old = torch.clone(d)
d = torch.where(d_old < 0, -d, d)
q1 = torch.where(d_old < 0, -q1, q1)
angle = torch.acos(d) + spin * torch.pi
angle_mask = (torch.abs(angle) < _EPS).squeeze()
out[angle_mask] = q0[angle_mask]
final_mask = torch.logical_or(zero_mask, ones_mask)
final_mask = torch.logical_or(final_mask, dist_mask)
final_mask = torch.logical_or(final_mask, angle_mask)
final_mask = torch.logical_not(final_mask)
isin = 1.0 / angle
q0 *= torch.sin((1.0 - fraction) * angle) * isin
q1 *= torch.sin(fraction * angle) * isin
q0 += q1
out[final_mask] = q0[final_mask]
return out
class G1_AMPLoader:
def __init__(
self,
device,
time_between_frames,
motion_files,
preload_transitions=False,
num_preload_transitions=1000000,
num_frames=5,
):
"""Expert dataset provides AMP observations from Dog mocap dataset.
time_between_frames: Amount of time in seconds between transition.
"""
self.device = device
self.time_between_frames = time_between_frames
self.num_frames = num_frames
# Values to store for each trajectory.
self.trajectories = []
self.trajectories_full = []
self.trajectory_names = []
self.trajectory_idxs = []
self.trajectory_lens = [] # Traj length in seconds.
self.trajectory_weights = []
self.trajectory_frame_durations = []
self.trajectory_num_frames = []
self.motion_dir = motion_files
# import ipdb; ipdb.set_trace()
for i, motion_file in enumerate(os.listdir(motion_files)):
self.trajectory_names.append(motion_file)
motion_path = pjoin(motion_files, motion_file)
motion_data = np.load(motion_path, allow_pickle=True)
motion_data_processed = np.zeros((motion_data.shape[0],36))
for f_i in range(motion_data.shape[0]):
motion_data_processed[f_i, :3] = motion_data[f_i, :3] # base pos
motion_data_processed[f_i, 3:7] = motion_data[f_i, 3:7] # base quat (wxyz)
motion_data_processed[f_i, 7:35] = motion_data[f_i, 7:35] # base vel
'''
NOTE The order of motion_data_processed is
base pos 0:3,
base quat 3:7, wxyz
dof pos 7:36, (mujoco joint order)
'''
self.trajectories.append(torch.tensor(
motion_data_processed[:, 7:],
dtype=torch.float32,
device=self.device
))
self.trajectories_full.append(torch.tensor(
motion_data_processed,
dtype=torch.float32,
device=self.device
))
self.trajectory_idxs.append(i)
self.trajectory_weights.append(1 / len(os.listdir(motion_files)))
frame_duration = 1 / 50
self.trajectory_frame_durations.append(frame_duration)
traj_len = (motion_data_processed.shape[0] - 1) * frame_duration # seconds
self.trajectory_lens.append(traj_len)
self.trajectory_num_frames.append(float(motion_data_processed.shape[0]))
print(f"Loaded {traj_len}s. motion from {motion_file}.")
# Trajectory weights are used to sample some trajectories more than others.
self.trajectory_weights = np.array(self.trajectory_weights) / np.sum(self.trajectory_weights)
self.trajectory_frame_durations = np.array(self.trajectory_frame_durations)
self.trajectory_lens = np.array(self.trajectory_lens)
self.trajectory_num_frames = np.array(self.trajectory_num_frames)
# Preload transitions.
self.preload_transitions = preload_transitions
if self.preload_transitions:
print(f'Preloading {num_preload_transitions} transitions')
traj_idxs = self.weighted_traj_idx_sample_batch(num_preload_transitions)
times = self.traj_time_sample_batch(traj_idxs)
self.preloaded_s_prior = self.get_full_frame_at_time_batch(traj_idxs, times - self.time_between_frames)
self.preloaded_s = self.get_full_frame_at_time_batch(traj_idxs, times)
self.preloaded_s_next = self.get_full_frame_at_time_batch(traj_idxs, times + self.time_between_frames)
print(f'Finished preloading')
# 预加载多帧数据
self.preloaded_frames = []
for i in range(self.num_frames):
frame_time = times + (i - (self.num_frames - 2)) * self.time_between_frames
full_frame = self.get_full_frame_at_time_batch(traj_idxs, frame_time)
# 预处理提前提取并连接需要的列7:26 和 29:33避免每次生成时重复切片
processed_frame = torch.cat([
full_frame[:, 7:26],
full_frame[:, 29:33]
], dim=-1)
self.preloaded_frames.append(processed_frame)
print(f'Finished preloading multiple frames')
self.all_trajectories_full = torch.vstack(self.trajectories_full)
def weighted_traj_idx_sample(self):
"""Get traj idx via weighted sampling."""
return np.random.choice(
self.trajectory_idxs, p=self.trajectory_weights)
def weighted_traj_idx_sample_batch(self, size):
"""Batch sample traj idxs."""
return np.random.choice(
self.trajectory_idxs, size=size, p=self.trajectory_weights,
replace=True)
def traj_time_sample(self, traj_idx):
"""Sample random time for traj."""
subst = self.time_between_frames + self.trajectory_frame_durations[traj_idx]
return max(
0, (self.trajectory_lens[traj_idx] * np.random.uniform() - subst))
def traj_time_sample_batch(self, traj_idxs):
"""Sample random time for multiple trajectories."""
subst = self.time_between_frames + self.trajectory_frame_durations[traj_idxs]
time_samples = self.trajectory_lens[traj_idxs] * np.random.uniform(size=len(traj_idxs)) - subst
return np.maximum(np.zeros_like(time_samples), time_samples)
def slerp(self, val0, val1, blend):
return (1.0 - blend) * val0 + blend * val1
def get_trajectory(self, traj_idx):
"""Returns trajectory of AMP observations."""
return self.trajectories_full[traj_idx]
def get_frame_at_time(self, traj_idx, time):
"""Returns frame for the given trajectory at the specified time."""
p = float(time) / self.trajectory_lens[traj_idx]
n = self.trajectories[traj_idx].shape[0]
idx_low, idx_high = int(np.floor(p * n)), int(np.ceil(p * n))
frame_start = self.trajectories[traj_idx][idx_low]
frame_end = self.trajectories[traj_idx][idx_high]
blend = p * n - idx_low
return self.slerp(frame_start, frame_end, blend)
def get_frame_at_time_batch(self, traj_idxs, times):
"""Returns frame for the given trajectory at the specified time."""
p = times / self.trajectory_lens[traj_idxs]
n = self.trajectory_num_frames[traj_idxs]
idx_low, idx_high = np.floor(p * n).astype(np.int32), np.ceil(p * n).astype(np.int32)
all_frame_starts = torch.zeros(len(traj_idxs), self.observation_dim, device=self.device)
all_frame_ends = torch.zeros(len(traj_idxs), self.observation_dim, device=self.device)
for traj_idx in set(traj_idxs):
trajectory = self.trajectories[traj_idx]
traj_mask = traj_idxs == traj_idx
all_frame_starts[traj_mask] = trajectory[idx_low[traj_mask]]
all_frame_ends[traj_mask] = trajectory[idx_high[traj_mask]]
blend = torch.tensor(p * n - idx_low, device=self.device, dtype=torch.float32).unsqueeze(-1)
return self.slerp(all_frame_starts, all_frame_ends, blend)
def get_full_frame_at_time(self, traj_idx, time):
"""Returns full frame for the given trajectory at the specified time."""
p = float(time) / self.trajectory_lens[traj_idx]
n = self.trajectories_full[traj_idx].shape[0]
idx_low, idx_high = int(np.floor(p * n)), int(np.ceil(p * n))
frame_start = self.trajectories_full[traj_idx][idx_low]
frame_end = self.trajectories_full[traj_idx][idx_high]
blend = p * n - idx_low
print(idx_low, idx_high)
return self.blend_frame_pose(frame_start, frame_end, blend)
def get_full_frame_at_time_batch(self, traj_idxs, times):
p = times / self.trajectory_lens[traj_idxs]
n = self.trajectory_num_frames[traj_idxs]
idx_low, idx_high = np.floor(p * n).astype(np.int32), np.ceil(p * n).astype(np.int32)
all_frame_pos_starts = torch.zeros(len(traj_idxs), 3, device=self.device)
all_frame_pos_ends = torch.zeros(len(traj_idxs), 3, device=self.device)
all_frame_rot_starts = torch.zeros(len(traj_idxs), 4, device=self.device)
all_frame_rot_ends = torch.zeros(len(traj_idxs), 4, device=self.device)
all_frame_amp_starts = torch.zeros(len(traj_idxs), 29, device=self.device)
all_frame_amp_ends = torch.zeros(len(traj_idxs), 29, device=self.device)
for traj_idx in set(traj_idxs):
trajectory = self.trajectories_full[traj_idx]
traj_mask = traj_idxs == traj_idx
all_frame_pos_starts[traj_mask] = G1_AMPLoader.get_root_pos_batch(trajectory[idx_low[traj_mask]])
all_frame_pos_ends[traj_mask] = G1_AMPLoader.get_root_pos_batch(trajectory[idx_high[traj_mask]])
all_frame_rot_starts[traj_mask] = G1_AMPLoader.get_root_rot_batch(trajectory[idx_low[traj_mask]])
all_frame_rot_ends[traj_mask] = G1_AMPLoader.get_root_rot_batch(trajectory[idx_high[traj_mask]])
all_frame_amp_starts[traj_mask] = trajectory[idx_low[traj_mask]][:, 7:36] # base vel3+ang3, dof vel23+ang23
all_frame_amp_ends[traj_mask] = trajectory[idx_high[traj_mask]][:, 7:36] # base vel3+ang3, dof vel23+ang23
blend = torch.tensor(p * n - idx_low, device=self.device, dtype=torch.float32).unsqueeze(-1)
pos_blend = self.slerp(all_frame_pos_starts, all_frame_pos_ends, blend)
rot_blend = quaternion_slerp(all_frame_rot_starts, all_frame_rot_ends, blend)
amp_blend = self.slerp(all_frame_amp_starts, all_frame_amp_ends, blend)
return torch.cat([pos_blend, rot_blend, amp_blend], dim=-1)
def get_frame(self):
"""Returns random frame."""
traj_idx = self.weighted_traj_idx_sample()
sampled_time = self.traj_time_sample(traj_idx)
return self.get_frame_at_time(traj_idx, sampled_time)
def get_full_frame(self):
"""Returns random full frame."""
traj_idx = self.weighted_traj_idx_sample()
sampled_time = self.traj_time_sample(traj_idx)
return self.get_full_frame_at_time(traj_idx, sampled_time)
def get_full_frame_batch(self, num_frames):
if self.preload_transitions:
idxs = np.random.choice(
self.preloaded_s.shape[0], size=num_frames)
return self.preloaded_s[idxs]
else:
traj_idxs = self.weighted_traj_idx_sample_batch(num_frames)
times = self.traj_time_sample_batch(traj_idxs)
return self.get_full_frame_at_time_batch(traj_idxs, times)
def blend_frame_pose(self, frame0, frame1, blend):
"""Linearly interpolate between two frames, including orientation.
Args:
frame0: First frame to be blended corresponds to (blend = 0).
frame1: Second frame to be blended corresponds to (blend = 1).
blend: Float between [0, 1], specifying the interpolation between
the two frames.
Returns:
An interpolation of the two frames.
"""
root_pos0, root_pos1 = G1_AMPLoader.get_root_pos(frame0), G1_AMPLoader.get_root_pos(frame1)
root_rot0, root_rot1 = G1_AMPLoader.get_root_rot(frame0), G1_AMPLoader.get_root_rot(frame1)
joints0, joints1 = G1_AMPLoader.get_joint_pose(frame0), G1_AMPLoader.get_joint_pose(frame1)
# tar_toe_pos_0, tar_toe_pos_1 = G1_AMPLoader.get_tar_toe_pos_local(frame0), G1_AMPLoader.get_tar_toe_pos_local(frame1)
linear_vel_0, linear_vel_1 = G1_AMPLoader.get_linear_vel(frame0), G1_AMPLoader.get_linear_vel(frame1)
angular_vel_0, angular_vel_1 = G1_AMPLoader.get_angular_vel(frame0), G1_AMPLoader.get_angular_vel(frame1)
joint_vel_0, joint_vel_1 = G1_AMPLoader.get_joint_vel(frame0), G1_AMPLoader.get_joint_vel(frame1)
blend_root_pos = self.slerp(root_pos0, root_pos1, blend)
blend_root_rot = transformations.quaternion_slerp(root_rot0.cpu().numpy(), root_rot1.cpu().numpy(), blend)
blend_root_rot = torch.tensor(motion_util.standardize_quaternion(blend_root_rot),dtype=torch.float32, device=self.device)
blend_joints = self.slerp(joints0, joints1, blend)
# blend_tar_toe_pos = self.slerp(tar_toe_pos_0, tar_toe_pos_1, blend)
blend_linear_vel = self.slerp(linear_vel_0, linear_vel_1, blend)
blend_angular_vel = self.slerp(angular_vel_0, angular_vel_1, blend)
blend_joints_vel = self.slerp(joint_vel_0, joint_vel_1, blend)
# return
# torch.cat([
# blend_root_pos, blend_root_rot, blend_linear_vel, blend_angular_vel, blend_joints, blend_joints_vel])
return torch.cat([blend_root_pos, blend_root_rot, blend_linear_vel, blend_angular_vel, blend_joints])
def feed_forward_generator_23dof_multi(self, num_mini_batch, mini_batch_size):
"""Generates a batch of AMP transitions."""
# import ipdb; ipdb.set_trace()
for _ in range(num_mini_batch):
if self.preload_transitions:
idxs = np.random.choice(self.preloaded_s.shape[0], size=mini_batch_size)
frames = []
for i in range(self.num_frames):
# 数据已在预加载时预处理,直接索引即可
s = self.preloaded_frames[i][idxs]
frames.append(s)
else:
NotImplementedError('preload transition')
yield torch.stack(frames, dim=1) # [batch, num_frames, 16]
def quaternion_to_euler_array(self, quat):
# Ensure quaternion is in the correct format [x, y, z, w]
x, y, z, w =quat
# Roll (x-axis rotation)
t0 = +2.0 * (w * x + y * z)
t1 = +1.0 - 2.0 * (x * x + y * y)
roll_x = np.arctan2(t0, t1)
# Pitch (y-axis rotation)
t2 = +2.0 * (w * y - z * x)
t2 = np.clip(t2, -1.0, 1.0)
pitch_y = np.arcsin(t2)
# Yaw (z-axis rotation)
t3 = +2.0 * (w * z + x * y)
t4 = +1.0 - 2.0 * (y * y + z * z)
yaw_z = np.arctan2(t3, t4)
# Returns roll, pitch, yaw in a NumPy array in radians
return np.array([roll_x, pitch_y, yaw_z])
def euler_to_quaternion(self, root_rot):
roll, pitch, yaw = root_rot[0], root_rot[1], root_rot[2]
cy = np.cos(yaw * 0.5)
sy = np.sin(yaw * 0.5)
cp = np.cos(pitch * 0.5)
sp = np.sin(pitch * 0.5)
cr = np.cos(roll * 0.5)
sr = np.sin(roll * 0.5)
qw = cy * cp * cr + sy * sp * sr
qx = cy * cp * sr - sy * sp * cr
qy = sy * cp * sr + cy * sp * cr
qz = sy * cp * cr - cy * sp * sr
return np.array([qx, qy, qz, qw])
@property
def observation_dim(self):
"""Size of AMP observations."""
return self.trajectories[0].shape[1] + 1
@property
def num_motions(self):
return len(self.trajectory_names)
@staticmethod
def get_root_pos(pose):
return pose[0:3]
@staticmethod
def get_root_pos_batch(poses):
return poses[:, 0:3]
@staticmethod
def get_root_rot(pose):
return pose[3:7]
@staticmethod
def get_root_rot_batch(poses):
return poses[:, 3:7]
@staticmethod
def get_joint_pose_batch_12dof(poses):
return poses[:, 13:25]
@staticmethod
def get_tar_toe_pos_local(pose):
return pose[G1_AMPLoader.TAR_TOE_POS_LOCAL_START_IDX:G1_AMPLoader.TAR_TOE_POS_LOCAL_END_IDX]
@staticmethod
def get_tar_toe_pos_local_batch(poses):
return poses[:, G1_AMPLoader.TAR_TOE_POS_LOCAL_START_IDX:G1_AMPLoader.TAR_TOE_POS_LOCAL_END_IDX]