639 lines
27 KiB
Python
639 lines
27 KiB
Python
import multiprocessing
|
|
import numpy
|
|
import os
|
|
import platform
|
|
import queue
|
|
import time
|
|
import sys
|
|
|
|
# This script depends on a SJSON parsing package:
|
|
# https://pypi.python.org/pypi/SJSON/1.1.0
|
|
# https://shelter13.net/projects/SJSON/
|
|
# https://bitbucket.org/Anteru/sjson/src
|
|
import sjson
|
|
|
|
|
|
def parse_argv():
|
|
options = {}
|
|
options['stats'] = ""
|
|
options['acl_stats'] = ""
|
|
options['ue4_stats'] = ""
|
|
options['dual_stat_inputs'] = False
|
|
options['csv_summary'] = False
|
|
options['csv_error'] = False
|
|
options['csv_kr'] = False
|
|
options['num_threads'] = 1
|
|
|
|
for i in range(1, len(sys.argv)):
|
|
value = sys.argv[i]
|
|
|
|
# TODO: Strip trailing '/' or '\'
|
|
if value.startswith('-stats='):
|
|
options['stats'] = value[7:].replace('"', '')
|
|
|
|
# TODO: Strip trailing '/' or '\'
|
|
if value.startswith('-acl='):
|
|
options['acl_stats'] = value[5:].replace('"', '')
|
|
|
|
# TODO: Strip trailing '/' or '\'
|
|
if value.startswith('-ue4='):
|
|
options['ue4_stats'] = value[5:].replace('"', '')
|
|
|
|
if value == '-csv_summary':
|
|
options['csv_summary'] = True
|
|
|
|
if value == '-csv_error':
|
|
options['csv_error'] = True
|
|
|
|
if value == '-csv_kr':
|
|
options['csv_kr'] = True
|
|
|
|
if value.startswith('-parallel='):
|
|
options['num_threads'] = int(value[len('-parallel='):].replace('"', ''))
|
|
|
|
has_stats_dir = (not options['stats'] == None) and (not len(options['stats']) == 0)
|
|
has_acl_stats_dir = (not options['acl_stats'] == None) and (not len(options['acl_stats']) == 0)
|
|
has_ue4_stats_dir = (not options['ue4_stats'] == None) and (not len(options['ue4_stats']) == 0)
|
|
if not has_stats_dir and not has_acl_stats_dir and not has_ue4_stats_dir:
|
|
print('A stats input directory must be provided either with `-stats=` or with both `-acl=` and `-ue4=`')
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
if has_stats_dir and (has_acl_stats_dir or has_ue4_stats_dir):
|
|
print('`-stats=` cannot be used with `-acl=` or `-ue4=`')
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
if not has_stats_dir and not (has_acl_stats_dir and has_ue4_stats_dir):
|
|
print('Both `-acl=` and `-ue4=` must be provided together')
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
options['dual_stat_inputs'] = has_acl_stats_dir and has_ue4_stats_dir
|
|
|
|
if has_acl_stats_dir and has_ue4_stats_dir:
|
|
if not os.path.exists(options['acl_stats']) or not os.path.isdir(options['acl_stats']):
|
|
print('ACL stats input directory not found: {}'.format(options['acl_stats']))
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
if not os.path.exists(options['ue4_stats']) or not os.path.isdir(options['ue4_stats']):
|
|
print('UE4 stats input directory not found: {}'.format(options['ue4_stats']))
|
|
print_usage()
|
|
sys.exit(1)
|
|
else:
|
|
if not os.path.exists(options['stats']) or not os.path.isdir(options['stats']):
|
|
print('Stats input directory not found: {}'.format(options['stats']))
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
if options['num_threads'] <= 0:
|
|
print('-parallel switch argument must be greater than 0')
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
return options
|
|
|
|
def print_usage():
|
|
print('Usage: python stat_parser.py [-stats=<path to input directory for stats>] [-acl=<path to acl stats>] [-ue4=<path to ue4 stats>] [-csv_summary] [-csv_error] [-csv_kr] [-parallel=<num threads>]')
|
|
|
|
def bytes_to_mb(size_in_bytes):
|
|
return size_in_bytes / (1024.0 * 1024.0)
|
|
|
|
def bytes_to_kb(size_in_bytes):
|
|
return size_in_bytes / 1024.0
|
|
|
|
def format_elapsed_time(elapsed_time):
|
|
hours, rem = divmod(elapsed_time, 3600)
|
|
minutes, seconds = divmod(rem, 60)
|
|
return '{:0>2}h {:0>2}m {:05.2f}s'.format(int(hours), int(minutes), seconds)
|
|
|
|
def sanitize_csv_entry(entry):
|
|
return entry.replace(', ', ' ').replace(',', '_')
|
|
|
|
def output_csv_summary(stat_dir, merged_stats):
|
|
csv_filename = os.path.join(stat_dir, 'stats_summary.csv')
|
|
print('Generating CSV file {} ...'.format(csv_filename))
|
|
file = open(csv_filename, 'w')
|
|
|
|
stat_acl, stat_auto = merged_stats[0]
|
|
header = 'Clip Name, Raw Size'
|
|
if 'ue4_auto' in stat_auto:
|
|
header += ', Auto Size, Auto Ratio, Auto UE4 Error, Auto ACL Error'
|
|
if 'ue4_acl' in stat_acl:
|
|
header += ', ACL Size, ACL Ratio, ACL UE4 Error, ACL ACL Error'
|
|
print(header, file = file)
|
|
|
|
for (stat_acl, stat_auto) in merged_stats:
|
|
clip_name = stat_acl['clip_name']
|
|
raw_size = stat_acl['acl_raw_size']
|
|
csv_line = '{}, {}'.format(clip_name, raw_size)
|
|
|
|
if 'ue4_auto' in stat_auto:
|
|
auto_size = stat_auto['ue4_auto']['compressed_size']
|
|
auto_ratio = stat_auto['ue4_auto']['acl_compression_ratio']
|
|
auto_ue4_error = stat_auto['ue4_auto']['ue4_max_error']
|
|
auto_acl_error = stat_auto['ue4_auto']['acl_max_error']
|
|
csv_line += ', {}, {}, {}, {}'.format(auto_size, auto_ratio, auto_ue4_error, auto_acl_error)
|
|
|
|
if 'ue4_acl' in stat_acl:
|
|
acl_size = stat_acl['ue4_acl']['compressed_size']
|
|
acl_ratio = stat_acl['ue4_acl']['acl_compression_ratio']
|
|
acl_ue4_error = stat_acl['ue4_acl']['ue4_max_error']
|
|
acl_acl_error = stat_acl['ue4_acl']['acl_max_error']
|
|
csv_line += ', {}, {}, {}, {}'.format(acl_size, acl_ratio, acl_ue4_error, acl_acl_error)
|
|
|
|
print(csv_line, file = file)
|
|
|
|
file.close()
|
|
|
|
def output_csv_error(stat_dir, merged_stats):
|
|
stat_acl, stat_auto = merged_stats[0]
|
|
if 'ue4_auto' in stat_auto and 'error_per_frame_and_bone' in stat_auto['ue4_auto']:
|
|
csv_filename = os.path.join(stat_dir, 'stats_ue4_auto_error.csv')
|
|
print('Generating CSV file {} ...'.format(csv_filename))
|
|
file = open(csv_filename, 'w')
|
|
|
|
print('Clip Name, Key Frame, Bone Index, Error', file = file)
|
|
|
|
for (_, stat_auto) in merged_stats:
|
|
name = stat_auto['clip_name']
|
|
key_frame = 0
|
|
for frame_errors in stat_auto['ue4_auto']['error_per_frame_and_bone']:
|
|
bone_index = 0
|
|
for bone_error in frame_errors:
|
|
print('{}, {}, {}, {}'.format(name, key_frame, bone_index, bone_error), file = file)
|
|
bone_index += 1
|
|
|
|
key_frame += 1
|
|
|
|
file.close()
|
|
|
|
if 'ue4_acl' in stat_acl and 'error_per_frame_and_bone' in stat_acl['ue4_acl']:
|
|
csv_filename = os.path.join(stat_dir, 'stats_ue4_acl_error.csv')
|
|
print('Generating CSV file {} ...'.format(csv_filename))
|
|
file = open(csv_filename, 'w')
|
|
|
|
print('Clip Name, Key Frame, Bone Index, Error', file = file)
|
|
|
|
for (stat_acl, _) in merged_stats:
|
|
name = stat_acl['clip_name']
|
|
key_frame = 0
|
|
for frame_errors in stat_acl['ue4_acl']['error_per_frame_and_bone']:
|
|
bone_index = 0
|
|
for bone_error in frame_errors:
|
|
print('{}, {}, {}, {}'.format(name, key_frame, bone_index, bone_error), file = file)
|
|
bone_index += 1
|
|
|
|
key_frame += 1
|
|
|
|
file.close()
|
|
|
|
def output_csv_kr(stat_dir, clip_drop_rates, pose_drop_rates, track_drop_rates):
|
|
csv_filename = os.path.join(stat_dir, 'stats_kr.csv')
|
|
print('Generating CSV file {} ...'.format(csv_filename))
|
|
file = open(csv_filename, 'w')
|
|
|
|
print('Dropped Per Clip, Dropped Per Pose, Dropped Per Track', file = file)
|
|
|
|
num_rows = max([len(clip_drop_rates), len(pose_drop_rates), len(track_drop_rates)])
|
|
values = [('', '', '')] * num_rows
|
|
|
|
for i in range(len(clip_drop_rates)):
|
|
clip_rate = clip_drop_rates[i]
|
|
_, pose_rate, track_rate = values[i]
|
|
values[i] = (clip_rate, pose_rate, track_rate)
|
|
|
|
for i in range(len(pose_drop_rates)):
|
|
pose_rate = pose_drop_rates[i]
|
|
clip_rate, _, track_rate = values[i]
|
|
values[i] = (clip_rate, pose_rate, track_rate)
|
|
|
|
for i in range(len(track_drop_rates)):
|
|
track_rate = track_drop_rates[i]
|
|
clip_rate, pose_rate, _ = values[i]
|
|
values[i] = (clip_rate, pose_rate, track_rate)
|
|
|
|
for (clip_rate, pose_rate, track_rate) in values:
|
|
print('{}, {}, {}'.format(clip_rate, pose_rate, track_rate), file = file)
|
|
|
|
file.close()
|
|
|
|
def print_progress(iteration, total, prefix='', suffix='', decimals = 1, bar_length = 40):
|
|
# Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
|
|
# With minor tweaks
|
|
"""
|
|
Call in a loop to create terminal progress bar
|
|
@params:
|
|
iteration - Required : current iteration (Int)
|
|
total - Required : total iterations (Int)
|
|
prefix - Optional : prefix string (Str)
|
|
suffix - Optional : suffix string (Str)
|
|
decimals - Optional : positive number of decimals in percent complete (Int)
|
|
bar_length - Optional : character length of bar (Int)
|
|
"""
|
|
str_format = "{0:." + str(decimals) + "f}"
|
|
percents = str_format.format(100 * (iteration / float(total)))
|
|
filled_length = int(round(bar_length * iteration / float(total)))
|
|
bar = '█' * filled_length + '-' * (bar_length - filled_length)
|
|
|
|
# We need to clear any previous line we might have to ensure we have no visual artifacts
|
|
# Note that if this function is called too quickly, the text might flicker
|
|
terminal_width = 80
|
|
sys.stdout.write('{}\r'.format(' ' * terminal_width))
|
|
sys.stdout.flush()
|
|
|
|
sys.stdout.write('%s |%s| %s%s %s\r' % (prefix, bar, percents, '%', suffix)),
|
|
sys.stdout.flush()
|
|
|
|
if iteration == total:
|
|
sys.stdout.write('\n')
|
|
|
|
def append_stats(permutation, clip_stats, run_stats, aggregate_results):
|
|
key = run_stats['desc']
|
|
if not key in aggregate_results:
|
|
run_total_stats = {}
|
|
run_total_stats['desc'] = key
|
|
run_total_stats['total_raw_size'] = 0
|
|
run_total_stats['total_compressed_size'] = 0
|
|
run_total_stats['total_compression_time'] = 0.0
|
|
run_total_stats['acl_max_error'] = 0.0
|
|
run_total_stats['ue4_max_error'] = 0.0
|
|
run_total_stats['num_runs'] = 0
|
|
aggregate_results[key] = run_total_stats
|
|
|
|
run_total_stats = aggregate_results[key]
|
|
run_total_stats['total_raw_size'] += clip_stats['acl_raw_size']
|
|
run_total_stats['total_compressed_size'] += run_stats['compressed_size']
|
|
run_total_stats['total_compression_time'] += run_stats['compression_time']
|
|
run_total_stats['acl_max_error'] = max(run_stats['acl_max_error'], run_total_stats['acl_max_error'])
|
|
run_total_stats['ue4_max_error'] = max(run_stats['ue4_max_error'], run_total_stats['ue4_max_error'])
|
|
run_total_stats['num_runs'] += 1
|
|
|
|
if not permutation in aggregate_results:
|
|
permutation_stats = {}
|
|
permutation_stats['total_raw_size'] = 0
|
|
permutation_stats['total_compressed_size'] = 0
|
|
permutation_stats['total_compression_time'] = 0.0
|
|
permutation_stats['acl_max_error'] = 0.0
|
|
permutation_stats['ue4_max_error'] = 0.0
|
|
permutation_stats['num_runs'] = 0
|
|
permutation_stats['worst_error'] = -1.0
|
|
permutation_stats['worst_entry'] = None
|
|
aggregate_results[permutation] = permutation_stats
|
|
|
|
permutation_stats = aggregate_results[permutation]
|
|
permutation_stats['total_raw_size'] += clip_stats['acl_raw_size']
|
|
permutation_stats['total_compressed_size'] += run_stats['compressed_size']
|
|
permutation_stats['total_compression_time'] += run_stats['compression_time']
|
|
permutation_stats['acl_max_error'] = max(run_stats['acl_max_error'], permutation_stats['acl_max_error'])
|
|
permutation_stats['ue4_max_error'] = max(run_stats['ue4_max_error'], permutation_stats['ue4_max_error'])
|
|
permutation_stats['num_runs'] += 1
|
|
if run_stats['acl_max_error'] > permutation_stats['worst_error']:
|
|
permutation_stats['worst_error'] = run_stats['acl_max_error']
|
|
permutation_stats['worst_entry'] = clip_stats
|
|
|
|
def do_parse_stats(options, stat_queue, result_queue):
|
|
try:
|
|
stats = []
|
|
acl_error_values = []
|
|
ue4_error_values = []
|
|
ue4_keyreduction_data = {}
|
|
ue4_keyreduction_data['drop_rates'] = []
|
|
ue4_keyreduction_data['pose_drop_rates'] = []
|
|
ue4_keyreduction_data['track_drop_rates'] = []
|
|
acl_compression_times = []
|
|
ue4_compression_times = []
|
|
|
|
while True:
|
|
stat_filename = stat_queue.get()
|
|
if stat_filename is None:
|
|
break
|
|
|
|
if platform.system() == 'Windows':
|
|
filename = '\\\\?\\{}'.format(stat_filename) # Long path prefix
|
|
else:
|
|
filename = stat_filename
|
|
|
|
with open(filename, 'r') as file:
|
|
try:
|
|
file_data = sjson.loads(file.read())
|
|
if 'error' in file_data:
|
|
print('{} [{}]'.format(file_data['error'], stat_filename))
|
|
continue
|
|
|
|
file_data['filename'] = stat_filename
|
|
file_data['clip_name'] = os.path.splitext(os.path.basename(stat_filename))[0].replace('_stats', '')
|
|
|
|
if not options['csv_error']:
|
|
# The sjson lib doesn't always return numbers as floats, sometimes as int but numpy doesn't like that
|
|
if 'ue4_acl' in file_data and 'error_per_frame_and_bone' in file_data['ue4_acl']:
|
|
for frame_error_values in file_data['ue4_acl']['error_per_frame_and_bone']:
|
|
acl_error_values.extend([float(v) for v in frame_error_values])
|
|
file_data['ue4_acl']['error_per_frame_and_bone'] = []
|
|
|
|
if 'ue4_auto' in file_data and 'error_per_frame_and_bone' in file_data['ue4_auto']:
|
|
for frame_error_values in file_data['ue4_auto']['error_per_frame_and_bone']:
|
|
ue4_error_values.extend([float(v) for v in frame_error_values])
|
|
file_data['ue4_auto']['error_per_frame_and_bone'] = []
|
|
|
|
if 'ue4_acl' in file_data:
|
|
acl_compression_times.append(file_data['ue4_acl']['compression_time'])
|
|
|
|
if 'ue4_auto' in file_data:
|
|
ue4_compression_times.append(file_data['ue4_auto']['compression_time'])
|
|
|
|
if 'ue4_keyreduction' in file_data:
|
|
num_animated_keys = float(file_data['ue4_keyreduction']['total_num_animated_keys'])
|
|
if num_animated_keys > 2.001:
|
|
drop_rate = float(file_data['ue4_keyreduction']['total_num_dropped_animated_keys']) / num_animated_keys
|
|
ue4_keyreduction_data['drop_rates'].append(drop_rate)
|
|
ue4_keyreduction_data['pose_drop_rates'].extend([float(v) for v in file_data['ue4_keyreduction']['dropped_pose_keys']])
|
|
ue4_keyreduction_data['track_drop_rates'].extend([float(v) for v in file_data['ue4_keyreduction']['dropped_track_keys']])
|
|
else:
|
|
ue4_keyreduction_data['drop_rates'].append(0.0)
|
|
ue4_keyreduction_data['pose_drop_rates'].extend([0.0 for v in file_data['ue4_keyreduction']['dropped_pose_keys']])
|
|
ue4_keyreduction_data['track_drop_rates'].extend([0.0 for v in file_data['ue4_keyreduction']['dropped_track_keys']])
|
|
|
|
stats.append(file_data)
|
|
except sjson.ParseException:
|
|
print('Failed to parse SJSON file: {}'.format(stat_filename))
|
|
|
|
result_queue.put(('progress', stat_filename))
|
|
|
|
results = {}
|
|
results['stats'] = stats
|
|
results['acl_error_values'] = acl_error_values
|
|
results['ue4_error_values'] = ue4_error_values
|
|
results['ue4_keyreduction_data'] = ue4_keyreduction_data
|
|
results['acl_compression_times'] = acl_compression_times
|
|
results['ue4_compression_times'] = ue4_compression_times
|
|
|
|
result_queue.put(('done', results))
|
|
except KeyboardInterrupt:
|
|
print('Interrupted')
|
|
|
|
def parallel_parse_stats(options, stat_files, label):
|
|
stat_queue = multiprocessing.Queue()
|
|
for stat_filename in stat_files:
|
|
stat_queue.put(stat_filename)
|
|
|
|
# Add a marker to terminate the jobs
|
|
for i in range(options['num_threads']):
|
|
stat_queue.put(None)
|
|
|
|
result_queue = multiprocessing.Queue()
|
|
|
|
jobs = [ multiprocessing.Process(target = do_parse_stats, args = (options, stat_queue, result_queue)) for _i in range(options['num_threads']) ]
|
|
for job in jobs:
|
|
job.start()
|
|
|
|
if options['dual_stat_inputs']:
|
|
label = ' {}'.format(label)
|
|
else:
|
|
label = '' # No need for a label if we parse both together
|
|
|
|
num_stat_files = len(stat_files)
|
|
num_stat_file_processed = 0
|
|
stats = []
|
|
print_progress(num_stat_file_processed, len(stat_files), 'Aggregating{} results:'.format(label), '{} / {}'.format(num_stat_file_processed, num_stat_files))
|
|
try:
|
|
while True:
|
|
try:
|
|
(msg, data) = result_queue.get(True, 1.0)
|
|
if msg == 'progress':
|
|
num_stat_file_processed += 1
|
|
print_progress(num_stat_file_processed, len(stat_files), 'Aggregating{} results:'.format(label), '{} / {}'.format(num_stat_file_processed, num_stat_files))
|
|
elif msg == 'done':
|
|
stats.append(data)
|
|
except queue.Empty:
|
|
all_jobs_done = True
|
|
for job in jobs:
|
|
if job.is_alive():
|
|
all_jobs_done = False
|
|
|
|
if all_jobs_done:
|
|
break
|
|
except KeyboardInterrupt:
|
|
sys.exit(1)
|
|
|
|
return stats
|
|
|
|
def get_stat_files(options):
|
|
if options['dual_stat_inputs']:
|
|
acl_stat_files = []
|
|
ue4_stat_files = []
|
|
|
|
for (dirpath, dirnames, filenames) in os.walk(options['acl_stats']):
|
|
for filename in filenames:
|
|
if not filename.endswith('.sjson'):
|
|
continue
|
|
|
|
stat_filename = os.path.join(dirpath, filename)
|
|
acl_stat_files.append(stat_filename)
|
|
|
|
for (dirpath, dirnames, filenames) in os.walk(options['ue4_stats']):
|
|
for filename in filenames:
|
|
if not filename.endswith('.sjson'):
|
|
continue
|
|
|
|
stat_filename = os.path.join(dirpath, filename)
|
|
ue4_stat_files.append(stat_filename)
|
|
|
|
acl_file_set = set([os.path.basename(file) for file in acl_stat_files])
|
|
ue4_file_set = set([os.path.basename(file) for file in ue4_stat_files])
|
|
if len(acl_file_set.intersection(ue4_file_set)) != len(acl_stat_files):
|
|
print('The input files for ACL and UE4 do not match, some are missing in one or the other')
|
|
sys.exit(1)
|
|
|
|
return (acl_stat_files, ue4_stat_files)
|
|
else:
|
|
stat_files = []
|
|
|
|
for (dirpath, dirnames, filenames) in os.walk(options['stats']):
|
|
for filename in filenames:
|
|
if not filename.endswith('.sjson'):
|
|
continue
|
|
|
|
stat_filename = os.path.join(dirpath, filename)
|
|
stat_files.append(stat_filename)
|
|
|
|
return (stat_files, stat_files)
|
|
|
|
def percentile_rank(values, value):
|
|
return (values < value).mean() * 100.0
|
|
|
|
if __name__ == "__main__":
|
|
options = parse_argv()
|
|
|
|
acl_stat_files, ue4_stat_files = get_stat_files(options)
|
|
|
|
if len(acl_stat_files) == 0:
|
|
print('No input clips found')
|
|
sys.exit(0)
|
|
|
|
aggregating_start_time = time.clock()
|
|
|
|
acl_stats = parallel_parse_stats(options, acl_stat_files, 'ACL')
|
|
if options['dual_stat_inputs']:
|
|
ue4_stats = parallel_parse_stats(options, ue4_stat_files, 'UE4 Auto')
|
|
else:
|
|
ue4_stats = acl_stats
|
|
|
|
acl_error_values = numpy.array([])
|
|
acl_compression_times = numpy.array([])
|
|
for result in acl_stats:
|
|
acl_error_values = numpy.append(acl_error_values, result['acl_error_values'])
|
|
acl_compression_times = numpy.append(acl_compression_times, result['acl_compression_times'])
|
|
|
|
ue4_error_values = numpy.array([])
|
|
ue4_compression_times = numpy.array([])
|
|
for result in ue4_stats:
|
|
ue4_error_values = numpy.append(ue4_error_values, result['ue4_error_values'])
|
|
ue4_compression_times = numpy.append(ue4_compression_times, result['ue4_compression_times'])
|
|
|
|
clip_drop_rates = numpy.array([])
|
|
pose_drop_rates = numpy.array([])
|
|
track_drop_rates = numpy.array([])
|
|
for result in acl_stats:
|
|
clip_drop_rates = numpy.append(clip_drop_rates, result['ue4_keyreduction_data']['drop_rates'])
|
|
pose_drop_rates = numpy.append(pose_drop_rates, result['ue4_keyreduction_data']['pose_drop_rates'])
|
|
track_drop_rates = numpy.append(track_drop_rates, result['ue4_keyreduction_data']['track_drop_rates'])
|
|
clip_drop_rates = numpy.sort(clip_drop_rates)
|
|
pose_drop_rates = numpy.sort(pose_drop_rates)
|
|
track_drop_rates = numpy.sort(track_drop_rates)
|
|
|
|
# Flatten our stats into a list and strip the error values
|
|
acl_stats = [ stat for result in acl_stats for stat in result['stats'] ]
|
|
ue4_stats = [ stat for result in ue4_stats for stat in result['stats'] ]
|
|
|
|
# Sort out stats by clip name so we can zip them in pairs
|
|
acl_stats.sort(key=lambda stat: stat['clip_name'])
|
|
ue4_stats.sort(key=lambda stat: stat['clip_name'])
|
|
|
|
merged_stats = list(zip(acl_stats, ue4_stats))
|
|
|
|
aggregating_end_time = time.clock()
|
|
print('Parsed stats in {}'.format(format_elapsed_time(aggregating_end_time - aggregating_start_time)))
|
|
|
|
if options['csv_summary']:
|
|
output_csv_summary(os.getcwd(), merged_stats)
|
|
|
|
if options['csv_error']:
|
|
output_csv_error(os.getcwd(), merged_stats)
|
|
|
|
if options['csv_kr'] and len(clip_drop_rates) > 0:
|
|
output_csv_kr(os.getcwd(), clip_drop_rates, pose_drop_rates, track_drop_rates)
|
|
|
|
print()
|
|
print('Stats per run type:')
|
|
aggregate_results = {}
|
|
num_acl_size_wins = 0
|
|
num_acl_accuracy_wins = 0
|
|
num_acl_speed_wins = 0
|
|
num_acl_wins = 0
|
|
num_acl_auto_wins = 0
|
|
for (stat_acl, stat_auto) in merged_stats:
|
|
if 'ue4_auto' in stat_auto:
|
|
ue4_auto = stat_auto['ue4_auto']
|
|
ue4_auto['desc'] = '{} {} {}'.format(ue4_auto['algorithm_name'], ue4_auto['rotation_format'], ue4_auto['translation_format'])
|
|
append_stats('ue4_auto', stat_auto, ue4_auto, aggregate_results)
|
|
|
|
if 'ue4_acl' in stat_acl:
|
|
ue4_acl = stat_acl['ue4_acl']
|
|
ue4_acl['desc'] = ue4_acl['algorithm_name']
|
|
append_stats('ue4_acl', stat_acl, ue4_acl, aggregate_results)
|
|
|
|
if 'ue4_keyreduction' in stat_acl:
|
|
ue4_keyreduction = stat_acl['ue4_keyreduction']
|
|
ue4_keyreduction['desc'] = ue4_keyreduction['algorithm_name']
|
|
append_stats('ue4_keyreduction', stat_acl, ue4_keyreduction, aggregate_results)
|
|
|
|
if 'ue4_auto' in stat_auto and 'ue4_acl' in stat_acl:
|
|
ue4_auto = stat_auto['ue4_auto']
|
|
ue4_acl = stat_acl['ue4_acl']
|
|
if ue4_acl['compressed_size'] < ue4_auto['compressed_size']:
|
|
num_acl_size_wins += 1
|
|
if ue4_acl['ue4_max_error'] < ue4_auto['ue4_max_error']:
|
|
num_acl_accuracy_wins += 1
|
|
if ue4_acl['compression_time'] < ue4_auto['compression_time']:
|
|
num_acl_speed_wins += 1
|
|
if ue4_acl['compressed_size'] < ue4_auto['compressed_size'] and ue4_acl['ue4_max_error'] < ue4_auto['ue4_max_error'] and ue4_acl['compression_time'] < ue4_auto['compression_time']:
|
|
num_acl_wins += 1
|
|
|
|
lowers_error = ue4_acl['ue4_max_error'] < ue4_auto['ue4_max_error'];
|
|
saved_size = int(ue4_auto['compressed_size']) - int(ue4_acl['compressed_size'])
|
|
lowers_size = ue4_acl['compressed_size'] < ue4_auto['compressed_size'];
|
|
error_under_threshold = float(ue4_acl['ue4_max_error']) <= 0.1;
|
|
|
|
# keep it if it we want to force the error below the threshold and it reduces error
|
|
# or if has an acceptable error and saves space
|
|
# or if saves the same amount and an acceptable error that is lower than the previous best
|
|
reduces_error_below_threshold = lowers_error and error_under_threshold;
|
|
has_acceptable_error_and_saves_space = error_under_threshold and saved_size > 0;
|
|
lowers_error_and_saves_same_or_better = error_under_threshold and lowers_error and saved_size >= 0;
|
|
if reduces_error_below_threshold or has_acceptable_error_and_saves_space or lowers_error_and_saves_same_or_better:
|
|
num_acl_auto_wins += 1
|
|
|
|
print()
|
|
raw_size = 0.0
|
|
if 'ue4_auto' in aggregate_results:
|
|
ue4_auto = aggregate_results['ue4_auto']
|
|
raw_size = ue4_auto['total_raw_size']
|
|
ratio = float(ue4_auto['total_raw_size']) / float(ue4_auto['total_compressed_size'])
|
|
print('Total Automatic Compression:')
|
|
print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [UE4: {:.4f}, ACL: {:.4f}]'.format(bytes_to_mb(ue4_auto['total_compressed_size']), format_elapsed_time(ue4_auto['total_compression_time']), ratio, ue4_auto['ue4_max_error'], ue4_auto['acl_max_error']))
|
|
print('Least accurate: {} Ratio: {:.2f}, Error: {:.4f}'.format(ue4_auto['worst_entry']['clip_name'], ue4_auto['worst_entry']['ue4_auto']['acl_compression_ratio'], ue4_auto['worst_entry']['ue4_auto']['acl_max_error']))
|
|
print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(raw_size) / ue4_auto['total_compression_time']))
|
|
print('Compression time 50, 85, 99th percentile: {:.3f}, {:.3f}, {:.3f} seconds'.format(numpy.percentile(ue4_compression_times, 50.0), numpy.percentile(ue4_compression_times, 85.0), numpy.percentile(ue4_compression_times, 99.0)))
|
|
if len(ue4_error_values) > 0:
|
|
print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(ue4_error_values, 99.0)))
|
|
print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(ue4_error_values, 0.01)))
|
|
print()
|
|
|
|
if 'ue4_acl' in aggregate_results:
|
|
ue4_acl = aggregate_results['ue4_acl']
|
|
raw_size = ue4_acl['total_raw_size']
|
|
ratio = float(ue4_acl['total_raw_size']) / float(ue4_acl['total_compressed_size'])
|
|
print('Total ACL Compression:')
|
|
print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [UE4: {:.4f}, ACL: {:.4f}]'.format(bytes_to_mb(ue4_acl['total_compressed_size']), format_elapsed_time(ue4_acl['total_compression_time']), ratio, ue4_acl['ue4_max_error'], ue4_acl['acl_max_error']))
|
|
print('Least accurate: {} Ratio: {:.2f}, Error: {:.4f}'.format(ue4_acl['worst_entry']['clip_name'], ue4_acl['worst_entry']['ue4_acl']['acl_compression_ratio'], ue4_acl['worst_entry']['ue4_acl']['acl_max_error']))
|
|
print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(raw_size) / ue4_acl['total_compression_time']))
|
|
print('Compression time 50, 85, 99th percentile: {:.3f}, {:.3f}, {:.3f} seconds'.format(numpy.percentile(acl_compression_times, 50.0), numpy.percentile(acl_compression_times, 85.0), numpy.percentile(acl_compression_times, 99.0)))
|
|
if len(acl_error_values) > 0:
|
|
print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(acl_error_values, 99.0)))
|
|
print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(acl_error_values, 0.01)))
|
|
print()
|
|
|
|
if 'ue4_keyreduction' in aggregate_results:
|
|
ue4_keyreduction = aggregate_results['ue4_keyreduction']
|
|
raw_size = ue4_keyreduction['total_raw_size']
|
|
ratio = float(ue4_keyreduction['total_raw_size']) / float(ue4_keyreduction['total_compressed_size'])
|
|
print('Total Key Reduction Compression:')
|
|
print('Compressed {:.2f} MB, Elapsed {}, Ratio [{:.2f} : 1], Max error [UE4: {:.4f}, ACL: {:.4f}]'.format(bytes_to_mb(ue4_keyreduction['total_compressed_size']), format_elapsed_time(ue4_keyreduction['total_compression_time']), ratio, ue4_keyreduction['ue4_max_error'], ue4_keyreduction['acl_max_error']))
|
|
print('Least accurate: {} Ratio: {:.2f}, Error: {:.4f}'.format(ue4_keyreduction['worst_entry']['clip_name'], ue4_keyreduction['worst_entry']['ue4_keyreduction']['acl_compression_ratio'], ue4_keyreduction['worst_entry']['ue4_keyreduction']['acl_max_error']))
|
|
print('Compression speed: {:.2f} KB/sec'.format(bytes_to_kb(raw_size) / ue4_keyreduction['total_compression_time']))
|
|
#print('Bone error 99th percentile: {:.4f}'.format(numpy.percentile(acl_error_values, 99.0)))
|
|
#print('Error threshold percentile rank: {:.2f} (0.01)'.format(percentile_rank(acl_error_values, 0.01)))
|
|
print()
|
|
|
|
num_clips = float(len(acl_stat_files))
|
|
print('Raw size: {:.2f} MB'.format(bytes_to_mb(raw_size)))
|
|
print('ACL was smaller for {} clips ({:.2f} %)'.format(num_acl_size_wins, float(num_acl_size_wins) / num_clips * 100.0))
|
|
print('ACL was more accurate for {} clips ({:.2f} %)'.format(num_acl_accuracy_wins, float(num_acl_accuracy_wins) / num_clips * 100.0))
|
|
print('ACL has faster compression for {} clips ({:.2f} %)'.format(num_acl_speed_wins, float(num_acl_speed_wins) / num_clips * 100.0))
|
|
print('ACL was smaller, better, faster for {} clips ({:.2f} %)'.format(num_acl_wins, float(num_acl_wins) / num_clips * 100.0))
|
|
print('ACL won with simulated auto {} clips ({:.2f} %)'.format(num_acl_auto_wins, float(num_acl_auto_wins) / num_clips * 100.0))
|
|
|
|
if len(clip_drop_rates) > 0:
|
|
print()
|
|
print('Key reduction clip avg drop rate: {:.2f} %'.format(numpy.average(clip_drop_rates) * 100.0))
|
|
print('Key reduction clip 50th percentile drop rate: {:.2f} %'.format(numpy.percentile(clip_drop_rates, 50.0) * 100.0))
|
|
print('Key reduction clip 90th percentile drop rate: {:.2f} %'.format(numpy.percentile(clip_drop_rates, 90.0) * 100.0))
|
|
print('Key reduction pose avg drop rate: {:.2f} %'.format(numpy.average(pose_drop_rates) * 100.0))
|
|
print('Key reduction pose 50th percentile drop rate: {:.2f} %'.format(numpy.percentile(pose_drop_rates, 50.0) * 100.0))
|
|
print('Key reduction pose 90th percentile drop rate: {:.2f} %'.format(numpy.percentile(pose_drop_rates, 90.0) * 100.0))
|
|
print('Key reduction track avg drop rate: {:.2f} %'.format(numpy.average(track_drop_rates) * 100.0))
|
|
print('Key reduction track 50th percentile drop rate: {:.2f} %'.format(numpy.percentile(track_drop_rates, 50.0) * 100.0))
|
|
print('Key reduction track 90th percentile drop rate: {:.2f} %'.format(numpy.percentile(track_drop_rates, 90.0) * 100.0))
|