Skip to content

Commit 2aac2c5

Browse files
committed
Add evaluation code
1 parent 1473c0e commit 2aac2c5

13 files changed

Lines changed: 1446 additions & 0 deletions

Evaluation/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Evaluation
2+
3+
## Data and pre-trained encoders
4+
5+
We prepare all the comparison data with this [Onedrive](https://1drv.ms/u/s!AtagzSrg3_hppdxIvlkNf35Jxf-PBw?e=6cJV5H) link. You can download it and prepare this folder with following structure:
6+
7+
```
8+
data\
9+
---- exp1_mann+dp
10+
---- exp1_mann+lp
11+
---- exp1_moglow
12+
---- exp1_ours
13+
...
14+
feature_extractor\
15+
---- 100style_position_distribution.npz
16+
---- 100style_position_classifier.pth
17+
...
18+
utils\
19+
...
20+
result_recording\
21+
...
22+
```
23+
24+
## Commands
25+
26+
Then you can run our evaluation with following commands:
27+
``` shell
28+
# Produce the deep latent for each motion data
29+
python compute_feat.py
30+
31+
# Compute the metrics
32+
python eval_motion_FID.py
33+
python eval_motion_quality.py
34+
python eval_traj_alignment.py
35+
```

Evaluation/compute_feat.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import os
2+
import torch
3+
from tqdm import tqdm
4+
import numpy as np
5+
from feature_extractor.train_classifier import Classifier, window_size
6+
from feature_extractor.process_train_data import styleList, process_file
7+
import multiprocessing
8+
from multiprocessing import Pool
9+
10+
def make_data(bvh_paths):
11+
with Pool(multiprocessing.cpu_count()) as p:
12+
data_list = list(tqdm(p.imap(process_file, bvh_paths), total=len(bvh_paths)))
13+
data_list = [data for data in data_list if data is not None]
14+
return data_list
15+
16+
17+
if __name__ == '__main__':
18+
19+
clip_len = window_size
20+
21+
process_folder = [
22+
["data/exp1_mann+dp", "ybot_skeleton"],
23+
["data/exp1_mann+lp", "ybot_skeleton"],
24+
["data/exp1_moglow", "style_skeleton"],
25+
["data/exp1_ours", "style_skeleton"],
26+
["data/exp1_ours_mlp", "style_skeleton"],
27+
["data/exp2_mann+dp", "ybot_skeleton"],
28+
["data/exp2_mann+lp", "ybot_skeleton"],
29+
["data/exp2_moglow", "style_skeleton"],
30+
["data/exp2_ours", "style_skeleton"],
31+
]
32+
33+
bvh_paths = []
34+
for data_folder, skeleton_config in process_folder:
35+
for root, dirs, files in os.walk(data_folder):
36+
for file in files:
37+
if '.bvh' in file:
38+
bvh_paths.append([os.path.join(root, file), skeleton_config])
39+
40+
motion_data_list = make_data(bvh_paths)
41+
local_positions = [data['local_position'] for data in motion_data_list]
42+
43+
device = torch.device('cpu')
44+
classifier = Classifier(in_feat=45, class_num=100, device=device)
45+
save_dict = torch.load('feature_extractor/100style_position_classifier.pth', map_location=device)
46+
classifier.load_state_dict(save_dict['state_dict'])
47+
classifier.eval()
48+
style_names = save_dict['class_names']
49+
50+
for data in motion_data_list:
51+
save_path = data['file_path'].replace('.bvh', '_predicted.npz')
52+
if os.path.exists(save_path):
53+
continue
54+
local_position = data['local_position']
55+
frame_num = local_position.shape[0]
56+
local_position = local_position.reshape(frame_num, -1)
57+
clip_indices = np.arange(0, frame_num - clip_len + 1, clip_len)[:, None] + np.arange(clip_len)
58+
model_input = local_position[clip_indices]
59+
model_input = torch.tensor(model_input, dtype=torch.float32).to(device)
60+
with torch.no_grad():
61+
feats, out = classifier(model_input.permute(0, 2, 1))
62+
_, example_predicted = torch.max(out.data, 1)
63+
pre_style_name = [style_names[idx] for idx in example_predicted.cpu().numpy()]
64+
65+
feats_numpy = feats.cpu().numpy()
66+
np.savez(save_path, feats=feats_numpy, predicted=pre_style_name)
67+
print('Processed:', save_path)

Evaluation/eval_motion_FID.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import os
2+
import numpy as np
3+
from tqdm import tqdm
4+
import scipy
5+
6+
def calculate_metrics(args):
7+
bvh_path, gt_distribution, save_list = args
8+
feature_path = bvh_path.replace('.bvh', '_predicted.npz')
9+
feature = np.load(feature_path)['feats']
10+
if 'exp1' in bvh_path or 'diffusion_step' in bvh_path:
11+
style_name = bvh_path.split('/')[-1].split('_')[-1].split('.')[0]
12+
gt_mu, gt_sigma = gt_distribution[style_name]
13+
mu, sigma = np.mean(feature, axis=0), np.cov(feature, rowvar=False)
14+
ssd = np.sum((mu - gt_mu)**2.0)
15+
covmean = scipy.linalg.sqrtm(sigma.dot(gt_sigma))
16+
17+
if np.iscomplexobj(covmean):
18+
covmean = covmean.real
19+
20+
fid = ssd + np.trace(sigma + gt_sigma - 2.0 * covmean)
21+
save_list.append(fid/len(sigma))
22+
if 'exp2' in bvh_path:
23+
start_style_name = bvh_path.split('/')[-1].split('.')[0].split('_')[1]
24+
gt_mu, gt_sigma = gt_distribution[start_style_name]
25+
start_feature = feature[:len(feature)//2]
26+
mu, sigma = np.mean(start_feature, axis=0), np.cov(start_feature, rowvar=False)
27+
ssd = np.sum((mu - gt_mu)**2.0)
28+
covmean = scipy.linalg.sqrtm(sigma.dot(gt_sigma))
29+
if np.iscomplexobj(covmean):
30+
covmean = covmean.real
31+
start_fid = ssd + np.trace(sigma + gt_sigma - 2.0 * covmean)
32+
33+
target_style_name = bvh_path.split('/')[-1].split('.')[0].split('_')[3]
34+
gt_mu, gt_sigma = gt_distribution[target_style_name]
35+
target_feature = feature[len(feature)//2:]
36+
mu, sigma = np.mean(target_feature, axis=0), np.cov(target_feature, rowvar=False)
37+
ssd = np.sum((mu - gt_mu)**2.0)
38+
covmean = scipy.linalg.sqrtm(sigma.dot(gt_sigma))
39+
if np.iscomplexobj(covmean):
40+
covmean = covmean.real
41+
target_fid = ssd + np.trace(sigma + gt_sigma - 2.0 * covmean)
42+
save_list.append((start_fid + target_fid)/2/len(sigma))
43+
# print('Finish %s' % bvh_path)
44+
45+
if __name__ == '__main__':
46+
47+
test_folders = [
48+
"data/exp1_mann+dp",
49+
"data/exp1_mann+lp",
50+
"data/exp1_moglow",
51+
"data/exp1_ours",
52+
"data/exp1_ours_mlp",
53+
"data/exp2_mann+dp",
54+
"data/exp2_mann+lp",
55+
"data/exp2_moglow",
56+
"data/exp2_ours"
57+
]
58+
59+
result_path = os.path.join('./result_recording', 'motion_FID.txt')
60+
gt_distribution = np.load('feature_extractor/100style_position_distribution.npz', allow_pickle=True)['style_features_dic'].item()
61+
62+
if not os.path.exists(result_path):
63+
with open(result_path, 'w') as f:
64+
f.write('Metrics: \t\t FID\t\n')
65+
f.write('---------------------------------\n')
66+
67+
for test_folder in test_folders:
68+
test_file_list = [os.path.join(test_folder, f) for f in os.listdir(test_folder) if f.endswith('.bvh')]
69+
calculate_metrics((test_file_list[0], gt_distribution, []))
70+
71+
metric_list = []
72+
args = [(test_file, gt_distribution, metric_list) for test_file in test_file_list]
73+
for arg in tqdm(args):
74+
calculate_metrics(arg)
75+
76+
77+
# num_processes = multiprocessing.cpu_count()
78+
# with multiprocessing.Pool(processes=num_processes) as pool:
79+
# pool.map(calculate_metrics, args)
80+
81+
# metric_list = list(metric_list)
82+
avg_value = np.mean(metric_list)
83+
84+
with open(result_path, 'a') as f:
85+
f.write(test_folder + '\t' + '%.4f\t\n' % avg_value)
86+
f.write('\n')
87+
88+
print('Finish %s, metrics: %s' % (test_folder, avg_value))

Evaluation/eval_motion_quality.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import numpy as np
3+
from tqdm import tqdm
4+
import utils.motion_modules as motion_modules
5+
from utils.bvh_motion import Motion
6+
import multiprocessing
7+
8+
9+
def calculate_metrics(args):
10+
bvh_path, metric_names, save_dic = args
11+
motion = Motion.load_bvh(bvh_path)
12+
13+
foot_idx = []
14+
if 'RightToeBase' in motion.names:
15+
foot_idx.append(motion.names.index('LeftToeBase'))
16+
foot_idx.append(motion.names.index('RightToeBase'))
17+
if 'RightToe' in motion.names:
18+
foot_idx.append(motion.names.index('LeftToe'))
19+
foot_idx.append(motion.names.index('RightToe'))
20+
motion = motion_modules.on_ground(motion, foot_idx)
21+
metric_value = []
22+
for metric_name in metric_names:
23+
if metric_name == 'foot_sliding':
24+
metric_value.append(motion_modules.cal_metric_foot_sliding(motion, y_threshold=1)) # cm
25+
elif metric_name == 'cohenrence':
26+
metric_value.append(motion_modules.cal_metric_cohenrence(motion))
27+
save_dic[bvh_path] = metric_value
28+
29+
30+
if __name__ == '__main__':
31+
32+
test_folders = [
33+
"data/exp1_mann+dp",
34+
"data/exp1_mann+lp",
35+
"data/exp1_moglow",
36+
"data/exp1_ours",
37+
"data/exp1_ours_mlp",
38+
"data/exp2_mann+dp",
39+
"data/exp2_mann+lp",
40+
"data/exp2_moglow",
41+
"data/exp2_ours",
42+
]
43+
44+
select_metric = ['foot_sliding', 'cohenrence']
45+
result_path = os.path.join('./result_recording', 'motion_quality.txt')
46+
47+
if not os.path.exists(result_path):
48+
with open(result_path, 'w') as f:
49+
f.write('Metrics: \t\t')
50+
for metric_name in select_metric:
51+
f.write('%s\t\t' % metric_name)
52+
f.write('\n')
53+
f.write('---------------------------------\n')
54+
55+
for test_folder in test_folders:
56+
57+
test_file_list = [os.path.join(test_folder, f) for f in os.listdir(test_folder) if f.endswith('.bvh')]
58+
59+
calculate_metrics((test_file_list[0], select_metric, {}))
60+
61+
metric_dic = multiprocessing.Manager().dict()
62+
args = [(test_file, select_metric, metric_dic) for test_file in test_file_list]
63+
64+
num_processes = multiprocessing.cpu_count()
65+
with multiprocessing.Pool(processes=num_processes) as pool:
66+
pool.map(calculate_metrics, args)
67+
68+
metric_dic = dict(metric_dic)
69+
70+
avg_list = []
71+
for metric_name in select_metric:
72+
metric_value = []
73+
for test_file in test_file_list:
74+
metric_value.append(metric_dic[test_file][select_metric.index(metric_name)])
75+
# remove nan in metric_value
76+
metric_value = [value for value in metric_value if not np.isnan(value)]
77+
avg_list.append(np.mean(metric_value))
78+
79+
with open(result_path, 'a') as f:
80+
f.write(test_folder + '\t')
81+
for metric_name in select_metric:
82+
f.write('%.4f\t\t' % avg_list[select_metric.index(metric_name)])
83+
f.write('\n')
84+
85+
print('Finish %s, metrics: %s' % (test_folder, avg_list))

Evaluation/eval_traj_alignment.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import os
2+
import numpy as np
3+
from tqdm import tqdm
4+
import utils.motion_modules as motion_modules
5+
from utils.bvh_motion import Motion
6+
import multiprocessing
7+
8+
9+
def calculate_metrics(args):
10+
bvh_path, preset_path, metric_names, save_dic = args
11+
motion = Motion.load_bvh(bvh_path)
12+
13+
all_frame_idx = np.arange(motion.frame_num)
14+
if 'LeftCollar' in motion.names:
15+
forward_angle = motion_modules.extract_forward(motion, all_frame_idx, 'LeftCollar', 'RightCollar', 'LeftHip', 'RightHip')
16+
elif 'RightUpLeg' in motion.names:
17+
forward_angle = motion_modules.extract_forward(motion, all_frame_idx, 'LeftShoulder', 'RightShoulder', 'LeftUpLeg', 'RightUpLeg')
18+
19+
forward_angle = np.rad2deg(forward_angle)
20+
preset_traj, preset_orien = [], []
21+
with open(preset_path, 'r') as f:
22+
for line in f:
23+
values = line.strip().split(',')
24+
traj_x, traj_z = float(values[0]), float(values[2])
25+
traj_angle = np.arctan2(traj_z, traj_x)
26+
dirc_x, dirc_z = float(values[3]), float(values[5])
27+
dirc_angle = np.arctan2(dirc_z, -dirc_x)
28+
preset_traj.append(traj_angle)
29+
preset_orien.append(dirc_angle)
30+
preset_traj, preset_orien = np.rad2deg(np.array(preset_traj)), np.rad2deg(np.array(preset_orien))
31+
32+
if motion.frame_num > len(preset_traj):
33+
motion = motion_modules.temporal_scale(motion, 2)
34+
forward_angle = forward_angle[::2]
35+
36+
preset_traj = preset_traj[:motion.frame_num-1]
37+
preset_orien = preset_orien[:motion.frame_num]
38+
39+
traj_pos = motion.positions[:, 0, [0, 2]]
40+
traj_pos[:, 0] *= -1
41+
traj_pos_diff = np.diff(traj_pos, axis=0)
42+
traj_angle = np.rad2deg(np.arctan2(traj_pos_diff[:, 1], traj_pos_diff[:, 0]))
43+
44+
metric_value = []
45+
for metric_name in metric_names:
46+
if metric_name == 'traj_error':
47+
value = np.abs(np.mean(preset_traj - traj_angle))
48+
# value = np.min([value, 90 - value, 180 - value])
49+
metric_value.append(np.abs(value))
50+
elif metric_name == 'orien_error':
51+
value = np.abs(np.mean(preset_orien - forward_angle))
52+
# value = np.min([value, 90 - value, 180 - value])
53+
metric_value.append(np.abs(value))
54+
55+
save_dic[bvh_path] = metric_value
56+
57+
58+
if __name__ == '__main__':
59+
60+
test_folders = [
61+
["data/exp1_mann+dp", "data/preset.txt"],
62+
["data/exp1_mann+lp", "data/preset.txt"],
63+
["data/exp1_matching", "data/preset.txt"],
64+
["data/exp1_moglow", "data/preset.txt"],
65+
["data/exp1_ours", "data/preset.txt"],
66+
["data/exp1_ours_mlp", "data/preset.txt"],
67+
]
68+
69+
select_metric = ['traj_error', 'orien_error']
70+
result_path = os.path.join('./result_recording', 'trajectory_alignment.txt')
71+
72+
if not os.path.exists(result_path):
73+
with open(result_path, 'w') as f:
74+
f.write('Metrics: \t\t')
75+
for metric_name in select_metric:
76+
f.write('%s\t\t' % metric_name)
77+
f.write('\n')
78+
f.write('---------------------------------\n')
79+
80+
for test_folder, preset_path in test_folders:
81+
test_file_list = [os.path.join(test_folder, f) for f in os.listdir(test_folder) if f.endswith('.bvh')]
82+
83+
calculate_metrics((test_file_list[0], preset_path, select_metric, {}))
84+
85+
metric_dic = multiprocessing.Manager().dict()
86+
args = [(test_file, preset_path, select_metric, metric_dic) for test_file in test_file_list]
87+
88+
num_processes = multiprocessing.cpu_count()
89+
with multiprocessing.Pool(processes=num_processes) as pool:
90+
pool.map(calculate_metrics, args)
91+
92+
metric_dic = dict(metric_dic)
93+
94+
avg_list = []
95+
for metric_name in select_metric:
96+
metric_value = []
97+
for test_file in test_file_list:
98+
metric_value.append(metric_dic[test_file][select_metric.index(metric_name)])
99+
# remove nan in metric_value
100+
metric_value = [value for value in metric_value if not np.isnan(value)]
101+
avg_list.append(np.mean(metric_value))
102+
103+
with open(result_path, 'a') as f:
104+
f.write(test_folder + '\t')
105+
for metric_name in select_metric:
106+
f.write('%.4f\t\t' % avg_list[select_metric.index(metric_name)])
107+
f.write('\n')
108+
109+
print('Finish %s, metrics: %s' % (test_folder, avg_list))
110+
111+
112+

0 commit comments

Comments
 (0)