From: José Fonseca Date: Sat, 4 Jun 2011 21:51:45 +0000 (+0100) Subject: Script to run glretrace in parallel, comparing generated snapshots. X-Git-Tag: 2.0_alpha^2~815 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0b956fd72fee30dccb7c011450066bc8e47c2d66;p=tools%2Fapitrace.git Script to run glretrace in parallel, comparing generated snapshots. --- diff --git a/scripts/jsondiff.py b/scripts/jsondiff.py index 458fef3..680e558 100755 --- a/scripts/jsondiff.py +++ b/scripts/jsondiff.py @@ -60,11 +60,12 @@ class Visitor: class Dumper(Visitor): - def __init__(self): + def __init__(self, stream = sys.stdout): + self.stream = stream self.level = 0; def _write(self, s): - sys.stdout.write(s) + self.stream.write(s) def _indent(self): self._write(' '*self.level) @@ -166,8 +167,8 @@ comparer = Comparer() class Differ(Visitor): - def __init__(self): - self.dumper = Dumper() + def __init__(self, stream = sys.stdout): + self.dumper = Dumper(stream) def visit(self, a, b): if comparer.visit(a, b): @@ -227,13 +228,13 @@ class Differ(Visitor): self.dumper.visit(b) -def load(filename): - return json.load(open(filename, 'rt'), strict=False, object_hook = object_hook) +def load(stream): + return json.load(stream, strict=False, object_hook = object_hook) def main(): - a = load(sys.argv[1]) - b = load(sys.argv[2]) + a = load(open(sys.argv[1], 'rt')) + b = load(open(sys.argv[2], 'rt')) #dumper = Dumper() #dumper.visit(a) diff --git a/scripts/retracediff.py b/scripts/retracediff.py new file mode 100755 index 0000000..b4bad08 --- /dev/null +++ b/scripts/retracediff.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2011 Jose Fonseca +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +##########################################################################/ + +'''Run two retrace instances in parallel, comparing generated snapshots. +''' + + +import optparse +import os.path +import re +import shutil +import subprocess +import platform +import sys +import tempfile + +from snapdiff import Comparer +import jsondiff + + +# Null file, to use when we're not interested in subprocesses output +if platform.system() == 'Windows': + NULL = open('NUL:', 'wt') +else: + NULL = open('/dev/null', 'wt') + + +class Setup: + + def __init__(self, args, env=None): + self.args = args + self.env = env + + def retrace(self, snapshot_dir): + cmd = [ + options.retrace, + '-s', snapshot_dir + os.path.sep, + '-S', options.snapshot_frequency, + ] + self.args + p = subprocess.Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=NULL) + return p + + def dump_state(self, call_no): + '''Get the state dump at the specified call no.''' + + cmd = [ + options.retrace, + '-D', str(call_no), + ] + self.args + p = subprocess.Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=NULL) + state = jsondiff.load(p.stdout) + p.wait() + return state + + +def diff_state(setup, ref_call_no, src_call_no): + ref_state = setup.dump_state(ref_call_no) + src_state = setup.dump_state(src_call_no) + sys.stdout.flush() + differ = jsondiff.Differ(sys.stdout) + differ.visit(ref_state, src_state) + sys.stdout.write('\n') + + +def parse_env(optparser, entries): + '''Translate a list of NAME=VALUE entries into an environment dictionary.''' + + env = os.environ.copy() + for entry in entries: + try: + name, var = entry.split('=', 1) + except Exception: + optparser.error('invalid environment entry %r' % entry) + env[name] = var + return env + + +def main(): + '''Main program. + ''' + + global options + + # Parse command line options + optparser = optparse.OptionParser( + usage='\n\t%prog [options] -- [glretrace options] ', + version='%%prog') + optparser.add_option( + '-r', '--retrace', metavar='PROGRAM', + type='string', dest='retrace', default='glretrace', + help='retrace command [default: %default]') + optparser.add_option( + '--ref-env', metavar='NAME=VALUE', + type='string', action='append', dest='ref_env', default=[], + help='reference environment variable') + optparser.add_option( + '--src-env', metavar='NAME=VALUE', + type='string', action='append', dest='src_env', default=[], + help='reference environment variable') + optparser.add_option( + '--diff-prefix', metavar='PATH', + type='string', dest='diff_prefix', default='.', + help='reference environment variable') + optparser.add_option( + '-t', '--threshold', metavar='BITS', + type="float", dest="threshold", default=12.0, + help="threshold precision [default: %default]") + optparser.add_option( + '-S', '--snapshot-frequency', metavar='FREQUENCY', + type="string", dest="snapshot_frequency", default='draw', + help="snapshot frequency [default: %default]") + + (options, args) = optparser.parse_args(sys.argv[1:]) + ref_env = parse_env(optparser, options.ref_env) + src_env = parse_env(optparser, options.src_env) + if not args: + optparser.error("incorrect number of arguments") + + ref_setup = Setup(args, ref_env) + src_setup = Setup(args, src_env) + + image_re = re.compile('^Wrote (.*\.png)$') + + last_good = -1 + last_bad = -1 + ref_snapshot_dir = tempfile.mkdtemp() + try: + src_snapshot_dir = tempfile.mkdtemp() + try: + ref_proc = ref_setup.retrace(ref_snapshot_dir) + try: + src_proc = src_setup.retrace(src_snapshot_dir) + try: + for ref_line in ref_proc.stdout: + # Get the reference image + ref_line = ref_line.rstrip() + mo = image_re.match(ref_line) + if mo: + ref_image = mo.group(1) + for src_line in src_proc.stdout: + # Get the source image + src_line = src_line.rstrip() + mo = image_re.match(src_line) + if mo: + src_image = mo.group(1) + + root, ext = os.path.splitext(os.path.basename(src_image)) + call_no = int(root) + + # Compare the two images + comparer = Comparer(ref_image, src_image) + precision = comparer.precision() + + sys.stdout.write('%u %f\n' % (call_no, precision)) + + if precision < options.threshold: + if options.diff_prefix: + comparer.write_diff(os.path.join(options.diff_prefix, root + '.diff.png')) + if last_bad < last_good: + diff_state(src_setup, last_good, call_no) + last_bad = call_no + else: + last_good = call_no + + sys.stdout.flush() + + os.unlink(src_image) + break + os.unlink(ref_image) + finally: + src_proc.terminate() + finally: + ref_proc.terminate() + finally: + shutil.rmtree(ref_snapshot_dir) + finally: + shutil.rmtree(src_snapshot_dir) + + +if __name__ == '__main__': + main() diff --git a/scripts/snapdiff.py b/scripts/snapdiff.py index 97fbeb8..9650440 100755 --- a/scripts/snapdiff.py +++ b/scripts/snapdiff.py @@ -44,31 +44,48 @@ import ImageEnhance thumb_size = 320, 320 -def compare(ref_image, src_image, delta_image): - ref_im = Image.open(ref_image) - src_im = Image.open(src_image) - - ref_im = ref_im.convert('RGB') - src_im = src_im.convert('RGB') - - diff = ImageChops.difference(src_im, ref_im) - - # make a difference image similar to ImageMagick's compare utility - mask = ImageEnhance.Brightness(diff).enhance(1.0/options.fuzz) - mask = mask.convert('L') - - lowlight = Image.new('RGB', src_im.size, (0xff, 0xff, 0xff)) - highlight = Image.new('RGB', src_im.size, (0xf1, 0x00, 0x1e)) - delta_im = Image.composite(highlight, lowlight, mask) - - delta_im = Image.blend(src_im, delta_im, 0xcc/255.0) - delta_im.save(delta_image) - - # See also http://effbot.org/zone/pil-comparing-images.htm - # TODO: this is approximate due to the grayscale conversion - h = diff.convert('L').histogram() - ae = sum(h[int(255 * options.fuzz) + 1 : 256]) - return ae +class Comparer: + '''Image comparer.''' + + def __init__(self, ref_image, src_image, alpha = False): + self.ref_im = Image.open(ref_image) + self.src_im = Image.open(src_image) + + # Ignore + if not alpha: + self.ref_im = self.ref_im.convert('RGB') + self.src_im = self.src_im.convert('RGB') + + self.diff = ImageChops.difference(self.src_im, self.ref_im) + + def write_diff(self, diff_image, fuzz = 0.05): + # make a difference image similar to ImageMagick's compare utility + mask = ImageEnhance.Brightness(self.diff).enhance(1.0/fuzz) + mask = mask.convert('L') + + lowlight = Image.new('RGB', self.src_im.size, (0xff, 0xff, 0xff)) + highlight = Image.new('RGB', self.src_im.size, (0xf1, 0x00, 0x1e)) + diff_im = Image.composite(highlight, lowlight, mask) + + diff_im = Image.blend(self.src_im, diff_im, 0xcc/255.0) + diff_im.save(diff_image) + + def precision(self): + # See also http://effbot.org/zone/pil-comparing-images.htm + h = self.diff.histogram() + square_error = 0 + for i in range(1, 256): + square_error += sum(h[i : 3*256: 256])*i*i + rel_error = float(square_error*2 + 1) / float(self.diff.size[0]*self.diff.size[1]*3*255*255*2) + bits = -math.log(rel_error)/math.log(2.0) + return bits + + def ae(self): + # Compute absolute error + # TODO: this is approximate due to the grayscale conversion + h = self.diff.convert('L').histogram() + ae = sum(h[int(255 * fuzz) + 1 : 256]) + return ae def surface(html, image): @@ -122,7 +139,7 @@ def main(): help="output filename [default: %default]") optparser.add_option( '-f', '--fuzz', - type="float", dest="fuzz", default=.05, + type="float", dest="fuzz", default=0.05, help="fuzz ratio [default: %default]") optparser.add_option( '--overwrite', @@ -160,7 +177,9 @@ def main(): or not os.path.exists(delta_image) \ or (os.path.getmtime(delta_image) < os.path.getmtime(ref_image) \ and os.path.getmtime(delta_image) < os.path.getmtime(src_image)): - compare(ref_image, src_image, delta_image) + + comparer = Comparer(ref_image, src_image) + comparer.write_diff(delta_image, fuzz=options.fuzz) html.write(' \n') surface(html, ref_image)