|
| 1 | +#!/usr/bin/python3 |
| 2 | + |
| 3 | +import argparse |
| 4 | +import concurrent.futures |
| 5 | +import matplotlib.pyplot as plt |
| 6 | +import os |
| 7 | +import os.path |
| 8 | +import re |
| 9 | +import subprocess |
| 10 | + |
| 11 | +argp = argparse.ArgumentParser() |
| 12 | +argp.add_argument('--basisu', type=str, required=True) |
| 13 | +argp.add_argument('--graph', type=str, default="basisu.png") |
| 14 | +argp.add_argument('--etcbase', action='store_true') |
| 15 | +argp.add_argument('files', type=str, nargs='+') |
| 16 | +args = argp.parse_args() |
| 17 | + |
| 18 | +def compress(path, flags): |
| 19 | + temp_path = "/dev/null" if os.name == "posix" else "NUL" |
| 20 | + output = subprocess.check_output([args.basisu, "-file", path, "-output_file", temp_path, "-stats", "-ktx2"] + flags) |
| 21 | + for line in output.splitlines(): |
| 22 | + if m := re.match(r".*source image.*?(\d+)x(\d+)", line.decode()): |
| 23 | + pixels = int(m.group(1)) * int(m.group(2)) |
| 24 | + elif m := re.match(r".*Compression succeeded.*size (\d+) bytes", line.decode()): |
| 25 | + bytes = int(m.group(1)) |
| 26 | + elif m := re.match(r"\.basis RGB Avg:.*RMS: (\d+\.\d+) PSNR: (\d+\.\d+)", line.decode()): |
| 27 | + rms = float(m.group(1)) |
| 28 | + psnr = float(m.group(2)) |
| 29 | + |
| 30 | + return {'path': path, 'bpp': bytes * 8 / pixels, 'rms': rms, 'psnr': psnr} |
| 31 | + |
| 32 | +def stats(path): |
| 33 | + with concurrent.futures.ThreadPoolExecutor(16) as executor: |
| 34 | + futures = [] |
| 35 | + for i in range(0, 26): |
| 36 | + rdo_l = i / 5 |
| 37 | + flags = ["-uastc", "-uastc_level", "1", "-uastc_rdo_l", str(rdo_l), "-uastc_rdo_d", "1024"] |
| 38 | + futures.append((executor.submit(compress, path, flags), rdo_l)) |
| 39 | + concurrent.futures.wait([f for (f, r) in futures]) |
| 40 | + results = [] |
| 41 | + bppbase = 0 |
| 42 | + for future, rdo_l in futures: |
| 43 | + res = future.result() |
| 44 | + if rdo_l == 0: |
| 45 | + bppbase = res['bpp'] |
| 46 | + res['rdo_l'] = rdo_l |
| 47 | + res['ratio'] = res['bpp'] / bppbase |
| 48 | + results.append(res) |
| 49 | + return results |
| 50 | + |
| 51 | +fields = ['bpp', 'rms', 'psnr', 'ratio'] |
| 52 | +fig, axs = plt.subplots(1, len(fields) + 1, layout='constrained') |
| 53 | +fig.set_figwidth(5 * (len(fields) + 1)) |
| 54 | +lines = [] |
| 55 | + |
| 56 | +for path in args.files: |
| 57 | + print('Processing', path) |
| 58 | + results = stats(path) |
| 59 | + etcbase = compress(path, ["-q", "192"]) if args.etcbase else None |
| 60 | + |
| 61 | + for idx, field in enumerate(fields): |
| 62 | + line, = axs[idx].plot([r['rdo_l'] for r in results], [r[field] for r in results]) |
| 63 | + if etcbase and field in etcbase: |
| 64 | + axs[idx].axhline(etcbase[field], color=line.get_color(), linestyle='dotted') |
| 65 | + if idx == 0: |
| 66 | + lines.append(line) |
| 67 | + |
| 68 | + axs[len(fields)].scatter([r['ratio'] for r in results], [r['psnr'] for r in results], color=line.get_color()) |
| 69 | + |
| 70 | +for idx, field in enumerate(fields): |
| 71 | + axs[idx].set_title(field) |
| 72 | + |
| 73 | +axs[len(fields)].set_title('psnr vs ratio') |
| 74 | + |
| 75 | +fig.legend(lines, [os.path.basename(path) for path in args.files], loc='outside right upper') |
| 76 | + |
| 77 | +plt.savefig(args.graph) |
0 commit comments