update_jpegli_global_scale.py (3090B)
1 #!/usr/bin/python 2 # Copyright (c) the JPEG XL Project Authors. All rights reserved. 3 # 4 # Use of this source code is governed by a BSD-style 5 # license that can be found in the LICENSE file. 6 7 """Script to update jpegli global scale after a change affecting quality. 8 9 start as ./update_jpegli_global_scale.py build <corpus-dir> 10 """ 11 12 import os 13 import re 14 import subprocess 15 import sys 16 17 def SourceFileName(): 18 return "lib/jpegli/quant.cc" 19 20 def ScalePattern(scale_type): 21 return "constexpr float kGlobalScale" + scale_type + " = "; 22 23 def CodecName(scale_type): 24 if scale_type == "YCbCr": 25 return "jpeg:enc-jpegli:q90" 26 elif scale_type == "XYB": 27 return "jpeg:enc-jpegli:xyb:q90" 28 else: 29 raise Exception("Unknown scale type %s" % scale_type) 30 31 def ReadGlobalScale(scale_type): 32 pattern = ScalePattern(scale_type) 33 with open(SourceFileName()) as f: 34 for line in f.read().splitlines(): 35 if line.startswith(pattern): 36 return float(line[len(pattern):-2]) 37 raise Exception("Global scale %s not found." % scale_type) 38 39 40 def UpdateGlobalScale(scale_type, new_val): 41 pattern = ScalePattern(scale_type) 42 found_pattern = False 43 fdata = "" 44 with open(SourceFileName()) as f: 45 for line in f.read().splitlines(): 46 if line.startswith(pattern): 47 fdata += pattern + "%.8ff;\n" % new_val 48 found_pattern = True 49 else: 50 fdata += line + "\n" 51 if not found_pattern: 52 raise Exception("Global scale %s not found." % scale_type) 53 with open(SourceFileName(), "w") as f: 54 f.write(fdata) 55 f.close() 56 57 def EvalPnorm(build_dir, corpus_dir, codec): 58 compile_args = ["ninja", "-C", build_dir, "tools/benchmark_xl"] 59 try: 60 subprocess.check_output(compile_args) 61 except: 62 subprocess.check_call(compile_args) 63 process = subprocess.Popen( 64 (os.path.join(build_dir, "tools/benchmark_xl"), 65 "--input", os.path.join(corpus_dir, "*.png"), 66 "--codec", codec), 67 stdout=subprocess.PIPE, 68 stderr=subprocess.PIPE) 69 (out, err) = process.communicate(input=None) 70 for line in out.splitlines(): 71 if line.startswith(codec): 72 return float(line.split()[8]) 73 raise Exception("Unexpected benchmark output:\n%sstderr:\n%s" % (out, err)) 74 75 76 if len(sys.argv) != 3: 77 print("usage: ", sys.argv[0], "build-dir corpus-dir") 78 exit(1) 79 80 build_dir = sys.argv[1] 81 corpus_dir = sys.argv[2] 82 83 jpeg_pnorm = EvalPnorm(build_dir, corpus_dir, "jpeg:q90") 84 85 print("Libjpeg pnorm: %.8f" % jpeg_pnorm) 86 87 for scale_type in ["YCbCr", "XYB"]: 88 scale = ReadGlobalScale(scale_type) 89 best_scale = scale 90 best_rel_error = 100.0 91 for i in range(10): 92 jpegli_pnorm = EvalPnorm(build_dir, corpus_dir, CodecName(scale_type)) 93 rel_error = abs(jpegli_pnorm / jpeg_pnorm - 1) 94 print("[%-5s] scale: %.8f pnorm: %.8f error: %.8f" % 95 (scale_type, scale, jpegli_pnorm, rel_error)) 96 if rel_error < best_rel_error: 97 best_rel_error = rel_error 98 best_scale = scale 99 if rel_error < 0.0001: 100 break 101 scale = scale * jpeg_pnorm / jpegli_pnorm 102 UpdateGlobalScale(scale_type, scale) 103 UpdateGlobalScale(scale_type, best_scale)