7e4ef6f98b
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
639 lines
26 KiB
Python
639 lines
26 KiB
Python
import os
|
|
import glob
|
|
import json
|
|
import torch
|
|
import joblib
|
|
import random
|
|
import numpy as np
|
|
import pandas as pd
|
|
from scipy import linalg
|
|
from arguments import Arguments
|
|
from nemf.losses import GeodesicLoss
|
|
from nemf.generative import Architecture
|
|
from nemf.fk import ForwardKinematicsLayer
|
|
from scripts.train_gmm import MaxMixturePrior
|
|
from human_body_prior.tools.omni_tools import copy2cpu as c2c
|
|
from utils import FOOT_IDX, estimate_angular_velocity, estimate_linear_velocity
|
|
from rotations import axis_angle_to_matrix, matrix_to_rotation_6d, rotation_6d_to_matrix
|
|
|
|
|
|
def padding_prior_data(data):
|
|
N, T, J = data['pos'].shape[:3]
|
|
# pad inputs to match the training sequence length
|
|
zero_padding = torch.zeros(N, args.data.clip_length - T, J, 3).to(prior.device)
|
|
identity_padding = torch.zeros(N, args.data.clip_length - T, 3, 3).to(prior.device)
|
|
identity_padding[:, :, 0, 0] = 1
|
|
identity_padding[:, :, 1, 1] = 1
|
|
identity_padding[:, :, 2, 2] = 1
|
|
rotation_6d_padding = torch.zeros(N, args.data.clip_length - T, 6).to(prior.device)
|
|
rotation_6d_padding[..., 0] = 1
|
|
rotation_6d_padding[..., 4] = 1
|
|
|
|
pos = torch.cat((data['pos'], zero_padding), dim=1)
|
|
velocity = torch.cat((data['velocity'], zero_padding), dim=1)
|
|
angular = torch.cat((data['angular'], zero_padding), dim=1)
|
|
root_orient = torch.cat((data['root_orient'], rotation_6d_padding), dim=1)
|
|
root_vel = torch.cat((data['root_vel'], zero_padding[:, :, 0]), dim=1)
|
|
rotmat_padding = rotation_6d_padding.unsqueeze(2).expand(-1, -1, J, -1)
|
|
global_xform = torch.cat((data['global_xform'], rotmat_padding), dim=1)
|
|
|
|
data['pos'] = pos
|
|
data['velocity'] = velocity
|
|
data['global_xform'] = global_xform
|
|
data['angular'] = angular
|
|
data['root_orient'] = root_orient
|
|
data['root_vel'] = root_vel
|
|
|
|
return data
|
|
|
|
|
|
def load_data(files, padding=False, max_samples=400):
|
|
poses = []
|
|
trans = []
|
|
visibility = []
|
|
assert len(files) != 0, 'files not found'
|
|
|
|
max_samples = min(max_samples, len(files))
|
|
|
|
# import ipdb; ipdb.set_trace()
|
|
|
|
for f in files[:max_samples]:
|
|
bdata = dict(np.load(f, allow_pickle=True))
|
|
if 'r_wrist_orient' in bdata.keys() and 'pose_right_hand' in bdata.keys():
|
|
|
|
coef = len(bdata['r_wrist_orient']) // args.data.clip_length
|
|
|
|
for k_ in range(coef):
|
|
root_orient = bdata['r_wrist_orient'][k_*args.data.clip_length:(k_+1)*args.data.clip_length]
|
|
pose_body = bdata['pose_right_hand'][k_*args.data.clip_length:(k_+1)*args.data.clip_length]
|
|
trans.append(bdata['r_wrist_location'][k_*args.data.clip_length:(k_+1)*args.data.clip_length])
|
|
|
|
poses.append(np.concatenate((root_orient, pose_body), axis=-1))
|
|
|
|
elif 'pose_right_hand' in bdata.keys():
|
|
pass
|
|
# poses.append(bdata['pose_right_hand'][:, :72])
|
|
else:
|
|
raise RuntimeError(f'missing pose parameters in the file: {f}')
|
|
|
|
|
|
if 'visibility' in bdata.keys():
|
|
visibility.append(bdata['visibility'])
|
|
|
|
# if 'poses' in bdata.keys():
|
|
# poses.append(bdata['poses'][:, :72])
|
|
# elif 'root_orient' in bdata.keys() and 'pose_body' in bdata.keys():
|
|
# root_orient = bdata['root_orient']
|
|
# pose_body = bdata['pose_body']
|
|
# poses.append(np.concatenate((root_orient, pose_body), axis=-1))
|
|
# else:
|
|
# raise RuntimeError(f'missing pose parameters in the file: {f}')
|
|
# trans.append(bdata['trans'])
|
|
# if 'visibility' in bdata.keys():
|
|
# visibility.append(bdata['visibility'])
|
|
|
|
trans = torch.from_numpy(np.asarray(trans, np.float32)).cuda() # global translation (N, T, 3)
|
|
N, T = trans.shape[:2]
|
|
poses = torch.from_numpy(np.asarray(poses, np.float32)).cuda()
|
|
poses = poses.view(N, T, -1, 3) # axis-angle (N, T, J, 3)
|
|
|
|
|
|
# poses = poses.view(N, T, 24, 3) # axis-angle (N, T, J, 3)
|
|
|
|
root_orient = poses[:, :, 0].clone()
|
|
root_rotation = axis_angle_to_matrix(root_orient) # (N, T, 3, 3)
|
|
poses[:, :, 0] = 0
|
|
|
|
rotmat = axis_angle_to_matrix(poses) # (N, T, J, 3, 3)
|
|
angular = estimate_angular_velocity(rotmat.clone(), dt=1.0 / args.data.fps) # angular velocity of all the joints (N, T, J, 3)
|
|
pos, global_xform = fk(rotmat.view(-1, 16, 3, 3))
|
|
pos = pos.contiguous().view(N, T, 16, 3) # local joint positions (N, T, J, 3)
|
|
global_xform = global_xform.view(N, T, 16, 4, 4)
|
|
global_xform = global_xform[:, :, :, :3, :3] # global transformation matrix for each joint (N, T, J, 3, 3)
|
|
velocity = estimate_linear_velocity(pos, dt=1.0 / args.data.fps) # linear velocity of all the joints (N, T, J, 3)
|
|
|
|
root_vel = estimate_linear_velocity(trans, dt=1.0 / args.data.fps) # linear velocity of the root joint (N, T, 3)
|
|
|
|
global_pos = torch.matmul(root_rotation.unsqueeze(2), pos.unsqueeze(-1)).squeeze(-1) # (N, T, J, 3)
|
|
global_pos = global_pos + trans.unsqueeze(2)
|
|
|
|
data = {
|
|
'pos': pos,
|
|
'velocity': velocity,
|
|
'global_xform': matrix_to_rotation_6d(global_xform),
|
|
'angular': angular,
|
|
'root_orient': matrix_to_rotation_6d(root_rotation),
|
|
'root_vel': root_vel,
|
|
'global_pos': global_pos,
|
|
'rotmat': rotmat,
|
|
'trans': trans
|
|
}
|
|
|
|
if len(visibility) != 0:
|
|
visibility = torch.from_numpy(np.asarray(visibility, bool)).cuda()
|
|
padding = torch.zeros(visibility.shape[:2] + torch.Size([2]), dtype=torch.bool).cuda()
|
|
visibility = torch.cat((visibility, padding), dim=-1) # (T, 24)
|
|
print(f'visibility: {visibility.shape}')
|
|
data['visibility'] = visibility
|
|
|
|
if T < args.data.clip_length:
|
|
padding = True
|
|
|
|
if padding:
|
|
if T == args.data.clip_length:
|
|
data['pos'] = data['pos'].view(2 * N, T // 2, 24, 3)
|
|
data['velocity'] = data['velocity'].view(2 * N, T // 2, 24, 3)
|
|
data['global_xform'] = data['global_xform'].view(2 * N, T // 2, 24, 6)
|
|
data['angular'] = data['angular'].view(2 * N, T // 2, 24, 3)
|
|
data['root_orient'] = data['root_orient'].view(2 * N, T // 2, 6)
|
|
data['root_vel'] = data['root_vel'].view(2 * N, T // 2, 3)
|
|
data['global_pos'] = data['global_pos'].view(2 * N, T // 2, 24, 3)
|
|
data['rotmat'] = data['rotmat'].view(2 * N, T // 2, 24, 3, 3)
|
|
data['trans'] = data['trans'].view(2 * N, T // 2, 3)
|
|
|
|
padding_data = padding_prior_data(data)
|
|
for key in padding_data.keys():
|
|
data[key] = padding_data[key]
|
|
|
|
return data
|
|
|
|
|
|
def calculate_feature_statistics(feature):
|
|
feature = feature.cpu().numpy()
|
|
mu = np.mean(feature, axis=0)
|
|
sigma = np.cov(feature, rowvar=False)
|
|
|
|
return mu, sigma
|
|
|
|
|
|
def calculate_frechet_distance(mu1, sigma1, mu2, sigma2, eps=1e-6):
|
|
"""Numpy implementation of the Frechet Distance.
|
|
The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
|
|
and X_2 ~ N(mu_2, C_2) is
|
|
d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).
|
|
Stable version by Dougal J. Sutherland.
|
|
Params:
|
|
-- mu1 : Numpy array containing the activations of a layer of the
|
|
inception net (like returned by the function 'get_predictions')
|
|
for generated samples.
|
|
-- mu2 : The sample mean over activations, precalculated on an
|
|
representative data set.
|
|
-- sigma1: The covariance matrix over activations for generated samples.
|
|
-- sigma2: The covariance matrix over activations, precalculated on an
|
|
representative data set.
|
|
Returns:
|
|
-- : The Frechet Distance.
|
|
"""
|
|
|
|
mu1 = np.atleast_1d(mu1)
|
|
mu2 = np.atleast_1d(mu2)
|
|
|
|
sigma1 = np.atleast_2d(sigma1)
|
|
sigma2 = np.atleast_2d(sigma2)
|
|
|
|
assert mu1.shape == mu2.shape, \
|
|
'Training and test mean vectors have different lengths'
|
|
assert sigma1.shape == sigma2.shape, \
|
|
'Training and test covariances have different dimensions'
|
|
|
|
diff = mu1 - mu2
|
|
|
|
# Product might be almost singular
|
|
covmean, _ = linalg.sqrtm(sigma1.dot(sigma2), disp=False)
|
|
if not np.isfinite(covmean).all():
|
|
msg = ('fid calculation produces singular product; '
|
|
'adding %s to diagonal of cov estimates') % eps
|
|
print(msg)
|
|
offset = np.eye(sigma1.shape[0]) * eps
|
|
covmean = linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))
|
|
|
|
# Numerical error might give slight imaginary component
|
|
if np.iscomplexobj(covmean):
|
|
if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
|
|
m = np.max(np.abs(covmean.imag))
|
|
raise ValueError('Imaginary component {}'.format(m))
|
|
covmean = covmean.real
|
|
|
|
tr_covmean = np.trace(covmean)
|
|
|
|
return (diff.dot(diff) + np.trace(sigma1) + np.trace(sigma2) - 2 * tr_covmean)
|
|
|
|
|
|
def evaluate_fid(data, gt_data, num_motions=None, mask=None):
|
|
if num_motions is None:
|
|
index = list(range(data['pos'].shape[0]))
|
|
index_gt = list(range(gt_data['pos'].shape[0]))
|
|
else:
|
|
# randomly pick [num_motions] motions from the data
|
|
assert num_motions <= data['pos'].shape[0]
|
|
assert num_motions <= gt_data['pos'].shape[0]
|
|
|
|
index = list(range(data['pos'].shape[0]))
|
|
index = random.sample(index, num_motions)
|
|
index_gt = list(range(gt_data['pos'].shape[0]))
|
|
index_gt = random.sample(index_gt, num_motions)
|
|
|
|
data_selected = {}
|
|
gt_data_selected = {}
|
|
for key in data.keys():
|
|
data_selected[key] = data[key][index].clone()
|
|
gt_data_selected[key] = gt_data[key][index_gt].clone()
|
|
|
|
if mask is not None:
|
|
# replace with ground truth
|
|
for key in data.keys():
|
|
data_selected[key][:, mask] = gt_data[key][:, mask].clone()
|
|
|
|
with torch.no_grad():
|
|
prior.set_input(data_selected)
|
|
f_l, _, _ = prior.encode_local()
|
|
f_g, _, _ = prior.encode_global()
|
|
feature = torch.cat((f_l, f_g), dim=-1)
|
|
|
|
prior.set_input(gt_data_selected)
|
|
f_l_gt, _, _ = prior.encode_local()
|
|
f_g_gt, _, _ = prior.encode_global()
|
|
feature_gt = torch.cat((f_l_gt, f_g_gt), dim=-1)
|
|
|
|
statistics = calculate_feature_statistics(feature.view(feature.shape[0], -1))
|
|
statistics_gt = calculate_feature_statistics(feature_gt.view(feature_gt.shape[0], -1))
|
|
|
|
fid = calculate_frechet_distance(statistics[0], statistics[1], statistics_gt[0], statistics_gt[1])
|
|
print(f'FID: {fid:.4f}')
|
|
|
|
return fid
|
|
|
|
|
|
def evaluate_diversity(data):
|
|
index = list(range(data['pos'].shape[0]))
|
|
random.shuffle(index)
|
|
index1 = index[:data['pos'].shape[0] // 2]
|
|
index2 = index[data['pos'].shape[0] // 2:]
|
|
|
|
with torch.no_grad():
|
|
prior.set_input(data)
|
|
f_l, _, _ = prior.encode_local()
|
|
# f_g, _, _ = prior.encode_global()
|
|
# feature = torch.cat((f_l, f_g), dim=-1)
|
|
|
|
feature = f_l.clone()
|
|
|
|
|
|
diversity = 0
|
|
for first_idx, second_idx in zip(index1, index2):
|
|
diversity += torch.dist(feature[first_idx], feature[second_idx])
|
|
diversity /= len(index1)
|
|
|
|
print(f'Diversity: {diversity.item():.4f}')
|
|
|
|
return diversity.item()
|
|
|
|
|
|
def evaluate_recon_error(data, gt_data):
|
|
criterion_geo = GeodesicLoss()
|
|
|
|
root_orient = rotation_6d_to_matrix(data['root_orient'])
|
|
root_orient_gt = rotation_6d_to_matrix(gt_data['root_orient'])
|
|
orientation_error = criterion_geo(root_orient.view(-1, 3, 3), root_orient_gt.view(-1, 3, 3))
|
|
|
|
rotation_error = criterion_geo(data['rotmat'][:, :, 1:].reshape(-1, 3, 3), gt_data['rotmat'][:, :, 1:].reshape(-1, 3, 3))
|
|
position_error = torch.linalg.norm((data['pos'] - gt_data['pos']), dim=-1).mean()
|
|
translation_error = torch.linalg.norm((data['trans'] - gt_data['trans']), dim=-1).mean()
|
|
final_displacement_error = torch.linalg.norm(data['global_pos'][:, -1] - gt_data['global_pos'][:, -1], dim=-1).mean()
|
|
|
|
return {
|
|
'rotation': c2c(rotation_error) * 180.0 / np.pi,
|
|
'position': c2c(position_error) * 100.0,
|
|
'orientation': c2c(orientation_error) * 180.0 / np.pi,
|
|
'translation': c2c(translation_error) * 100.0,
|
|
'final_displacement': c2c(final_displacement_error) * 100.0
|
|
}
|
|
|
|
|
|
def synthetic_trajectory(N_motions):
|
|
|
|
z_l = torch.normal(mean=0, std=1, size=(N_motions, args.local_prior.z_dim)).to(prior.device)
|
|
z_g = torch.normal(mean=0, std=1, size=(N_motions, args.global_prior.z_dim)).to(prior.device)
|
|
# z_g = torch.zeros((N_motions, args.global_prior.z_dim)).to(prior.device)
|
|
|
|
|
|
with torch.no_grad():
|
|
output = prior.decode(z_l, z_g, length=args.data.clip_length, step=1)
|
|
|
|
rotmat = output["rotmat"] # (N, T, J, 3, 3)
|
|
angular = estimate_angular_velocity(rotmat.clone(), dt=1.0 / args.data.fps) # angular velocity of all the joints (N, T, J, 3)
|
|
pos, global_xform = fk(rotmat.view(-1, 16, 3, 3))
|
|
pos = pos.contiguous().view(N_motions, args.data.clip_length, 16, 3) # local joint positions (N, T, J, 3)
|
|
global_xform = global_xform.view(N_motions, args.data.clip_length, 16, 4, 4)
|
|
|
|
global_xform = global_xform[:, :, :, :3, :3] # global transformation matrix for each joint (N, T, J, 3, 3)
|
|
velocity = estimate_linear_velocity(pos, dt=1.0 / args.data.fps) # linear velocity of all the joints (N, T, J, 3)
|
|
|
|
root_rotation = rotmat[:, :, 0] # (N, T, 3, 3)
|
|
global_pos = torch.matmul(root_rotation.unsqueeze(2), pos.unsqueeze(-1)).squeeze(-1) # (N, T, J, 3)
|
|
|
|
output_ = {'pos': pos,
|
|
'velocity': velocity,
|
|
'global_xform': matrix_to_rotation_6d(global_xform),
|
|
'angular': angular,
|
|
'root_orient': matrix_to_rotation_6d(root_rotation),
|
|
'rotmat': rotmat}
|
|
|
|
return output_
|
|
|
|
|
|
def ablation_study(path, subtask):
|
|
|
|
num_motions = 100
|
|
print(os.path.join(path, 'TCDHands', '*.npz'))
|
|
|
|
test_data = load_data(sorted(glob.glob(os.path.join('./outputs/generative_copy/TCDHands', '*.npz')))) # test data TCDHands
|
|
val_data = load_data(sorted(glob.glob(os.path.join('./outputs/generative_copy/GRAB', '*.npz')))) # test data TCDHands
|
|
train_data = load_data(sorted(glob.glob(os.path.join('./outputs/generative_copy/SAMP', '*.npz')))) # test data TCDHands
|
|
synthetic_data = synthetic_trajectory(N_motions=num_motions)
|
|
|
|
pca_aa = joblib.load("data/pca.pkl")
|
|
pca_mean = torch.tensor(pca_aa.mean_).to("cuda")
|
|
pca_cov = torch.tensor(pca_aa.get_covariance()).to("cuda")
|
|
pca_sv = torch.tensor(pca_aa.singular_values_).to("cuda")
|
|
pca_pc = torch.tensor(pca_aa.components_).to("cuda")
|
|
|
|
random_samples_low_dim = np.random.randn(num_motions, pca_aa.n_components_)
|
|
pca_data = pca_aa.inverse_transform(random_samples_low_dim)
|
|
|
|
gmm_aa = MaxMixturePrior()
|
|
gmm_data = gmm_aa.gmm.sample(num_motions)
|
|
|
|
identity = torch.eye(3)
|
|
identity = identity.view(1, 1, 3, 3).repeat(num_motions, args.data.clip_length, 1, 1)
|
|
|
|
# convert to tensor
|
|
gmm_data = torch.tensor(gmm_data[0].reshape(num_motions, args.data.clip_length, -1, 3))
|
|
gmm_data = torch.cat((identity.unsqueeze(2), axis_angle_to_matrix(gmm_data)), dim=2)
|
|
gmm_rot6d = matrix_to_rotation_6d(gmm_data).reshape(num_motions * args.data.clip_length, -1, 6)
|
|
|
|
# convert to tensor
|
|
pca_data = torch.tensor(pca_data.reshape(num_motions, args.data.clip_length, -1, 3))
|
|
pca_data = torch.cat((identity.unsqueeze(2), axis_angle_to_matrix(pca_data)), dim=2)
|
|
pca_rot6d = matrix_to_rotation_6d(pca_data).reshape(num_motions * args.data.clip_length, -1, 6)
|
|
|
|
gmm_pos, gmm_xform = fk(gmm_rot6d.to(fk.device))
|
|
pca_pos, pca_xform = fk(pca_rot6d.to(fk.device))
|
|
|
|
gmm_xform = matrix_to_rotation_6d(gmm_xform[:, :, :3, :3])
|
|
pca_xform = matrix_to_rotation_6d(pca_xform[:, :, :3, :3])
|
|
|
|
gmm_velocity = estimate_linear_velocity(gmm_pos.unsqueeze(0), dt=1.0 / args.data.fps).squeeze(0)
|
|
pca_velocity = estimate_linear_velocity(pca_pos.unsqueeze(0), dt=1.0 / args.data.fps).squeeze(0)
|
|
|
|
gmm_angular = estimate_angular_velocity(gmm_data.unsqueeze(0), dt=1.0 / args.data.fps).squeeze(0)
|
|
pca_angular = estimate_angular_velocity(pca_data.unsqueeze(0), dt=1.0 / args.data.fps).squeeze(0)
|
|
|
|
gmm_dict = {'pos': gmm_pos.reshape(num_motions, args.data.clip_length, -1, 3),
|
|
'global_xform': gmm_xform.reshape(num_motions, args.data.clip_length, -1, 6),
|
|
'velocity': gmm_velocity.reshape(num_motions, args.data.clip_length, -1, 3),
|
|
'angular': gmm_angular}
|
|
|
|
pca_dict = {'pos': pca_pos.reshape(num_motions, args.data.clip_length, -1, 3),
|
|
'global_xform': pca_xform.reshape(num_motions, args.data.clip_length, -1, 6),
|
|
'velocity': pca_velocity.reshape(num_motions, args.data.clip_length, -1, 3),
|
|
'angular': pca_angular}
|
|
|
|
metrics = dict()
|
|
|
|
def f(data, num_motions, key):
|
|
div = evaluate_diversity(data)
|
|
if key in metrics.keys():
|
|
pass
|
|
else:
|
|
metrics[key] = {'DIV': [div]}
|
|
|
|
print('Motion Synthesis:')
|
|
div_test = f(test_data, num_motions, 'TEST_DATA (TDCHANDS)')
|
|
div_val = f(val_data, num_motions, 'VAL_DATA (SAMP)')
|
|
div_train = f(train_data, num_motions, 'TRAIN_DATA (GRAB)')
|
|
div_synt = f(synthetic_data, num_motions, 'SYNTHETIC')
|
|
div_gmm = f(gmm_dict, num_motions, 'GMM')
|
|
div_pca = f(pca_dict, num_motions, 'PCA')
|
|
|
|
df = pd.DataFrame.from_dict(metrics)
|
|
df.to_csv(os.path.join(path, f'diversity.csv'), index=False)
|
|
|
|
|
|
|
|
def comparison(path, subtask):
|
|
gt_data = load_data(sorted(glob.glob(os.path.join(path, 'gt', '*.npz'))))
|
|
|
|
file_path = os.path.join(path, subtask)
|
|
|
|
metrics = dict()
|
|
if subtask == 'recon':
|
|
humor_data = load_data(sorted(glob.glob(os.path.join(file_path, 'humor', '*.npz'))))
|
|
hm_vae_data = load_data(sorted(glob.glob(os.path.join(file_path, 'hm-vae', '*.npz'))))
|
|
nemf_data = load_data(sorted(glob.glob(os.path.join(file_path, 'nemf', '*.npz'))))
|
|
|
|
metrics['HUMOR'] = evaluate_recon_error(humor_data, gt_data)
|
|
metrics['HMVAE'] = evaluate_recon_error(hm_vae_data, gt_data)
|
|
metrics['NEMF'] = evaluate_recon_error(nemf_data, gt_data)
|
|
print('Motion Reconstruction:')
|
|
print(json.dumps(metrics, sort_keys=True, indent=4))
|
|
|
|
df = pd.DataFrame.from_dict(metrics)
|
|
df.to_csv(os.path.join(file_path, f'{subtask}.csv'))
|
|
elif subtask == 'synth':
|
|
humor_data = load_data(sorted(glob.glob(os.path.join(file_path, 'humor', '.*.npz'))), padding=True)
|
|
hm_vae_data = load_data(sorted(glob.glob(os.path.join(file_path, 'hm-vae', '*.npz'))), padding=True)
|
|
nemf_data = load_data(sorted(glob.glob(os.path.join(file_path, 'nemf', '*.npz'))), padding=True)
|
|
|
|
num_motions = 100
|
|
|
|
def f(data, num_motions, key):
|
|
fid = evaluate_fid(data, gt_data, num_motions)
|
|
div = evaluate_diversity(data)
|
|
index = list(range(data['global_pos'].shape[0]))
|
|
index = random.sample(index, num_motions)
|
|
s = foot_skating(data['global_pos'][index])
|
|
if key in metrics.keys():
|
|
metrics[key]['FID'].append(fid)
|
|
metrics[key]['DIV'].append(div)
|
|
metrics[key]['FOOT'].append(s)
|
|
else:
|
|
metrics[key] = {'FID': [fid], 'DIV': [div], 'FOOT': [s]}
|
|
|
|
print('Motion Synthesis:')
|
|
for replication in range(20):
|
|
print(f'==================== Replication {replication} ====================')
|
|
f(humor_data, num_motions, 'HUMOR')
|
|
f(hm_vae_data, num_motions, 'HMVAE')
|
|
f(nemf_data, num_motions, 'NEMF')
|
|
|
|
for key in metrics.keys():
|
|
df = pd.DataFrame.from_dict(metrics[key])
|
|
df.to_csv(os.path.join(file_path, f'{subtask}_{key}.csv'), index=False)
|
|
else:
|
|
raise RuntimeError(f'unsupported evaluation task for comparison: {subtask}')
|
|
|
|
|
|
def inbetween_benchmarks(path, keyframes=False):
|
|
trans_lengths = [5, 10, 15, 20] if keyframes else [10, 20, 30]
|
|
|
|
gt_data = load_data(sorted(glob.glob(os.path.join(path, 'gt', '*.npz'))))
|
|
s = foot_skating(gt_data['global_pos'])
|
|
print(f'average foot skating of the real motions: {s:.4f}')
|
|
|
|
def f(data, key, mask):
|
|
fid = evaluate_fid(data, gt_data, mask=mask)
|
|
s = foot_skating(data['global_pos'], mask=mask)
|
|
if key in statistics.keys():
|
|
statistics[key]['FID'].append(fid)
|
|
statistics[key]['FOOT'].append(s)
|
|
else:
|
|
statistics[key] = {'FID': [fid], 'FOOT': [s]}
|
|
|
|
file_path = os.path.join(path, 'keyframe') if keyframes else os.path.join(path, 'clip')
|
|
statistics = {}
|
|
for t in trans_lengths:
|
|
print(f'==================== Length {t} ====================')
|
|
if keyframes:
|
|
mask = None # don't mask data for keyframe inbetweening
|
|
else:
|
|
end_frame = (args.data.clip_length - t) // 2
|
|
mask = list(range(end_frame)) + list(range(end_frame + t, args.data.clip_length))
|
|
print(f'mask: {mask}')
|
|
|
|
slerp_data = load_data(sorted(glob.glob(os.path.join(file_path, 'slerp', f'{t}_frames', '*.npz'))))
|
|
f(slerp_data, 'SLERP', mask)
|
|
|
|
if not keyframes:
|
|
inertia_data = load_data(sorted(glob.glob(os.path.join(file_path, 'inertialization', f'{t}_frames', '*.npz'))))
|
|
f(inertia_data, 'INERTIALIZATION', mask)
|
|
|
|
rmi_data = load_data(sorted(glob.glob(os.path.join(file_path, 'rmi', f'{t}_frames', '*.npz'))))
|
|
f(rmi_data, 'RMI', mask)
|
|
|
|
hm_vae_data = load_data(sorted(glob.glob(os.path.join(file_path, 'hm-vae', f'{t}_frames', '*.npz'))))
|
|
f(hm_vae_data, 'HMVAE', mask)
|
|
|
|
nemf_data = load_data(sorted(glob.glob(os.path.join(file_path, 'nemf', f'{t}_frames', '*.npz'))))
|
|
f(nemf_data, 'NEMF', mask)
|
|
|
|
for key in statistics.keys():
|
|
df = pd.DataFrame.from_dict(statistics[key])
|
|
df.to_csv(os.path.join(file_path, f'inbetween_{key}.csv'))
|
|
|
|
|
|
def compute_joint_accel(joint_seq, fps):
|
|
''' Magnitude of joint accelerations for joint_seq : B x T x J x 3 '''
|
|
joint_accel = joint_seq[:, :-2] - (2 * joint_seq[:, 1:-1]) + joint_seq[:, 2:]
|
|
DATA_h = 1.0 / fps
|
|
joint_accel = joint_accel / ((DATA_h**2))
|
|
joint_accel_mag = torch.norm(joint_accel, dim=-1)
|
|
|
|
return joint_accel, joint_accel_mag
|
|
|
|
|
|
def evaluate_super_sampling(path):
|
|
fps_30_data = load_data(sorted(glob.glob(os.path.join(path, '30fps/*.npz'))))
|
|
fps_60_data = load_data(sorted(glob.glob(os.path.join(path, '60fps/*.npz'))))
|
|
fps_120_data = load_data(sorted(glob.glob(os.path.join(path, '120fps/*.npz'))))
|
|
fps_240_data = load_data(sorted(glob.glob(os.path.join(path, '240fps/*.npz'))))
|
|
|
|
smoothness = dict()
|
|
joint_vel = estimate_linear_velocity(fps_30_data['global_pos'], dt=1.0 / 30.0)
|
|
joint_vel_mag = torch.norm(joint_vel, dim=-1).mean()
|
|
smoothness['30FPS'] = [c2c(joint_vel_mag * 100.0)]
|
|
joint_vel = estimate_linear_velocity(fps_60_data['global_pos'], dt=1.0 / 60.0)
|
|
joint_vel_mag = torch.norm(joint_vel, dim=-1).mean()
|
|
smoothness['60FPS'] = [c2c(joint_vel_mag * 100.0)]
|
|
joint_vel = estimate_linear_velocity(fps_120_data['global_pos'], dt=1.0 / 120.0)
|
|
joint_vel_mag = torch.norm(joint_vel, dim=-1).mean()
|
|
smoothness['120FPS'] = [c2c(joint_vel_mag * 100.0)]
|
|
joint_vel = estimate_linear_velocity(fps_240_data['global_pos'], dt=1.0 / 240.0)
|
|
joint_vel_mag = torch.norm(joint_vel, dim=-1).mean()
|
|
smoothness['240FPS'] = [c2c(joint_vel_mag * 100.0)]
|
|
|
|
df = pd.DataFrame.from_dict(smoothness)
|
|
df.to_csv(os.path.join(path, f'smoothness.csv'))
|
|
|
|
|
|
def evaluate_smoothness_test(path):
|
|
smoothness = dict()
|
|
|
|
for fps in [30, 60]:
|
|
smoothness[f'{fps}FPS'] = dict()
|
|
for L in range(1, 23, 2):
|
|
data = load_data([os.path.join(path, f'L{L}', f'test_{fps}fps.npz')])
|
|
# _, joint_accel_mag = compute_joint_accel(data['global_pos'], fps=fps)
|
|
# smoothness[f'{fps}FPS'][L] = c2c(joint_accel_mag.mean() * 100.0)
|
|
joint_vel = estimate_linear_velocity(data['global_pos'], dt=1.0 / fps)
|
|
joint_vel_mag = torch.norm(joint_vel, dim=-1).mean()
|
|
smoothness[f'{fps}FPS'][L] = c2c(joint_vel_mag.mean() * 100.0)
|
|
|
|
df = pd.DataFrame.from_dict(smoothness)
|
|
df.to_csv(os.path.join(path, f'smoothness.csv'))
|
|
|
|
import matplotlib.pyplot as plt
|
|
plt.rcParams['text.usetex'] = True
|
|
|
|
smoothness_30fps = np.array(list(smoothness['30FPS'].values()))
|
|
smoothness_60fps = np.array(list(smoothness['60FPS'].values()))
|
|
|
|
plt.plot(np.arange(1, 23, 2), smoothness_30fps, marker='o', label='30fps')
|
|
plt.plot(np.arange(1, 23, 2), smoothness_60fps, marker='o', label='60fps')
|
|
plt.legend()
|
|
plt.grid(linestyle='--')
|
|
plt.xticks(np.arange(1, 23, 2))
|
|
plt.xlabel(r'$L$')
|
|
# plt.yscale('log')
|
|
plt.ylabel('Mean Per-Joint Velocity')
|
|
plt.savefig(os.path.join(path, 'smoothness.pdf'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
from argparse import ArgumentParser
|
|
|
|
parser = ArgumentParser()
|
|
parser.add_argument('--config', type=str, default='evaluation.yaml', help='name of the configutation file')
|
|
parser.add_argument('--task', type=str, help='name of application tasks', required=True)
|
|
parser.add_argument('--load_path', type=str, help='path to load results for evaluation', required=True)
|
|
|
|
opt = parser.parse_args()
|
|
args = Arguments('./configs', filename=opt.config)
|
|
|
|
torch.set_default_dtype(torch.float32)
|
|
|
|
torch.manual_seed(0)
|
|
np.random.seed(0)
|
|
random.seed(0)
|
|
|
|
ngpu = 1
|
|
if args.multi_gpu is True:
|
|
ngpu = torch.cuda.device_count()
|
|
if ngpu == 1:
|
|
args.multi_gpu = False
|
|
print(f'Number of GPUs: {ngpu}')
|
|
|
|
prior = Architecture(args, ngpu)
|
|
prior.load(optimal=True)
|
|
prior.eval()
|
|
fk = ForwardKinematicsLayer(args)
|
|
|
|
if opt.task == 'ablation_study':
|
|
print('Evaluating ablation study ...')
|
|
ablation_study(opt.load_path, subtask='synth')
|
|
elif opt.task == 'comparison':
|
|
print('Evaluating comparison with other methods ...')
|
|
comparison(opt.load_path, subtask='recon')
|
|
comparison(opt.load_path, subtask='synth')
|
|
elif opt.task == 'inbetween_benchmark':
|
|
print('Evaluating motion in-betweening ...')
|
|
inbetween_benchmarks(opt.load_path, keyframes=False)
|
|
inbetween_benchmarks(opt.load_path, keyframes=True)
|
|
elif opt.task == 'super_sampling':
|
|
print('Evaluating super sampling ...')
|
|
evaluate_super_sampling(opt.load_path)
|
|
elif opt.task == 'smoothness_test':
|
|
print('Evaluating smoothness testing ...')
|
|
evaluate_smoothness_test(opt.load_path)
|
|
else:
|
|
raise NotImplementedError(f'unknown evaluation: {opt.task}') |