389 lines
17 KiB
Python
389 lines
17 KiB
Python
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]
|