Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(73)

Side by Side Diff: gm/rebaseline_server/compare_rendered_pictures.py

Issue 424263005: teach rebaseline_server to generate diffs of rendered SKPs (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2014 Google Inc. 4 Copyright 2014 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 Compare results of two render_pictures runs. 9 Compare results of two render_pictures runs.
10
11 EPOGER: Rename this module, because it is more generally useful now... it can do wnload and compare any types of images.
10 """ 12 """
11 13
12 # System-level imports 14 # System-level imports
13 import logging 15 import logging
14 import os 16 import os
17 import shutil
18 import tempfile
15 import time 19 import time
16 20
17 # Must fix up PYTHONPATH before importing from within Skia 21 # Must fix up PYTHONPATH before importing from within Skia
18 import fix_pythonpath # pylint: disable=W0611 22 import fix_pythonpath # pylint: disable=W0611
19 23
20 # Imports from within Skia 24 # Imports from within Skia
21 from py.utils import url_utils 25 from py.utils import url_utils
26 import buildbot_globals
22 import gm_json 27 import gm_json
23 import imagediffdb 28 import imagediffdb
24 import imagepair 29 import imagepair
25 import imagepairset 30 import imagepairset
26 import results 31 import results
27 32
28 # URL under which all render_pictures images can be found in Google Storage. 33 # URL under which all render_pictures images can be found in Google Storage.
29 # 34 #
30 # pylint: disable=C0301 35 # EPOGER: In order to allow live-view of GMs and other images, read this from th e input summary files, or allow the caller to set it within the GET_live_results call?
31 # TODO(epoger): Move this default value into 36 DEFAULT_IMAGE_BASE_GS_URL = 'gs://' + buildbot_globals.Get('skp_images_bucket')
32 # https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.j son
33 # pylint: enable=C0301
34 DEFAULT_IMAGE_BASE_URL = (
35 'http://chromium-skia-gm.commondatastorage.googleapis.com/'
36 'render_pictures/images')
37 37
38 38
39 class RenderedPicturesComparisons(results.BaseComparisons): 39 class RenderedPicturesComparisons(results.BaseComparisons):
40 """Loads results from two different render_pictures runs into an ImagePairSet. 40 """Loads results from multiple render_pictures runs into an ImagePairSet.
41 """ 41 """
42 42
43 def __init__(self, subdirs, actuals_root, 43 def __init__(self, actuals_dirs, expectations_dirs, image_diff_db,
44 generated_images_root=results.DEFAULT_GENERATED_IMAGES_ROOT, 44 image_base_gs_url=DEFAULT_IMAGE_BASE_GS_URL,
45 image_base_url=DEFAULT_IMAGE_BASE_URL, 45 diff_base_url=None, actuals_label='actuals',
46 diff_base_url=None): 46 expectations_label='expectations'):
47 """ 47 """
48 Args: 48 Args:
49 actuals_root: root directory containing all render_pictures-generated 49 actuals_dirs: list of root directories to copy all JSON summaries from,
50 JSON files 50 and to use as actual results
51 subdirs: (string, string) tuple; pair of subdirectories within 51 expectations_dirs: list of root directories to copy all JSON summaries
52 actuals_root to compare 52 from, and to use as expected results
53 generated_images_root: directory within which to create all pixel diffs; 53 image_diff_db: ImageDiffDB instance
54 if this directory does not yet exist, it will be created 54 image_base_gs_url: "gs://" URL pointing at the Google Storage bucket/dir
55 image_base_url: URL under which all render_pictures result images can 55 under which all render_pictures result images can
56 be found; this will be used to read images for comparison within 56 be found; this will be used to read images for comparison within
57 this code, and included in the ImagePairSet so its consumers know 57 this code, and included in the ImagePairSet (as an HTTP URL) so its
58 where to download the images from 58 consumers know where to download the images from
59 diff_base_url: base URL within which the client should look for diff 59 diff_base_url: base URL within which the client should look for diff
60 images; if not specified, defaults to a "file:///" URL representation 60 images; if not specified, defaults to a "file:///" URL representation
61 of generated_images_root 61 of image_diff_db's storage_root
62 actuals_label: description to use for actual results
63 expectations_label: description to use for expected results
62 """ 64 """
63 time_start = int(time.time()) 65 self._image_diff_db = image_diff_db
64 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root) 66 self._image_base_gs_url = image_base_gs_url
65 self._image_base_url = image_base_url
66 self._diff_base_url = ( 67 self._diff_base_url = (
67 diff_base_url or 68 diff_base_url or
68 url_utils.create_filepath_url(generated_images_root)) 69 url_utils.create_filepath_url(image_diff_db.storage_root))
69 self._load_result_pairs(actuals_root, subdirs) 70 self._actuals_label = actuals_label
70 self._timestamp = int(time.time()) 71 self._expectations_label = expectations_label
71 logging.info('Results complete; took %d seconds.' %
72 (self._timestamp - time_start))
73 72
74 def _load_result_pairs(self, actuals_root, subdirs): 73 tempdir = tempfile.mkdtemp()
75 """Loads all JSON files found within two subdirs in actuals_root, 74 try:
76 compares across those two subdirs, and stores the summary in self._results. 75 actuals_root = os.path.join(tempdir, 'actuals')
76 expectations_root = os.path.join(tempdir, 'expectations')
77 # Copy all summary files into actuals_root and expectations_root
78 # EPOGER: this won't work when *_dirs have more than one entry
79 for dir in actuals_dirs:
80 shutil.copytree(dir, actuals_root)
81 for dir in expectations_dirs:
82 shutil.copytree(dir, expectations_root)
83
84 time_start = int(time.time())
85 self._load_result_pairs(actuals_root, expectations_root)
86 self._timestamp = int(time.time())
87 logging.info('Results complete; took %d seconds.' %
88 (self._timestamp - time_start))
89 finally:
90 shutil.rmtree(tempdir)
91
92 def _load_result_pairs(self, actuals_root, expectations_root):
93 """Loads all JSON image summaries from 2 directory trees and compares them.
94
95 The summary of all image diff results is stored in in self._results.
96 EPOGER: we don't want to store the results in self._results anymore... now, we just want to return them
77 97
78 Args: 98 Args:
79 actuals_root: root directory containing all render_pictures-generated 99 actuals_root: root directory containing JSON summaries of actual results
80 JSON files 100 expectations_root: root dir containing JSON summaries of expected results
81 subdirs: (string, string) tuple; pair of subdirectories within
82 actuals_root to compare
83 """ 101 """
84 logging.info( 102 logging.info('Reading JSON image summaries from dirs %s and %s...' % (
85 'Reading actual-results JSON files from %s subdirs within %s...' % ( 103 actuals_root, expectations_root))
86 subdirs, actuals_root)) 104 actuals_dicts = self._read_dicts_from_root(actuals_root)
87 subdirA, subdirB = subdirs 105 expectations_dicts = self._read_dicts_from_root(expectations_root)
88 subdirA_dicts = self._read_dicts_from_root( 106 logging.info('Comparing summary dicts...')
89 os.path.join(actuals_root, subdirA))
90 subdirB_dicts = self._read_dicts_from_root(
91 os.path.join(actuals_root, subdirB))
92 logging.info('Comparing subdirs %s and %s...' % (subdirA, subdirB))
93 107
94 all_image_pairs = imagepairset.ImagePairSet( 108 all_image_pairs = imagepairset.ImagePairSet(
95 descriptions=subdirs, 109 descriptions=(self._actuals_label, self._expectations_label),
96 diff_base_url=self._diff_base_url) 110 diff_base_url=self._diff_base_url)
97 failing_image_pairs = imagepairset.ImagePairSet( 111 failing_image_pairs = imagepairset.ImagePairSet(
98 descriptions=subdirs, 112 descriptions=(self._actuals_label, self._expectations_label),
99 diff_base_url=self._diff_base_url) 113 diff_base_url=self._diff_base_url)
100 114
101 all_image_pairs.ensure_extra_column_values_in_summary( 115 all_image_pairs.ensure_extra_column_values_in_summary(
102 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ 116 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[
103 results.KEY__RESULT_TYPE__FAILED, 117 results.KEY__RESULT_TYPE__FAILED,
104 results.KEY__RESULT_TYPE__NOCOMPARISON, 118 results.KEY__RESULT_TYPE__NOCOMPARISON,
105 results.KEY__RESULT_TYPE__SUCCEEDED, 119 results.KEY__RESULT_TYPE__SUCCEEDED,
106 ]) 120 ])
107 failing_image_pairs.ensure_extra_column_values_in_summary( 121 failing_image_pairs.ensure_extra_column_values_in_summary(
108 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[ 122 column_id=results.KEY__EXTRACOLUMNS__RESULT_TYPE, values=[
109 results.KEY__RESULT_TYPE__FAILED, 123 results.KEY__RESULT_TYPE__FAILED,
110 results.KEY__RESULT_TYPE__NOCOMPARISON, 124 results.KEY__RESULT_TYPE__NOCOMPARISON,
111 ]) 125 ])
112 126
113 common_dict_paths = sorted(set(subdirA_dicts.keys() + subdirB_dicts.keys())) 127 # Every actuals_dict should be paired up with a corresponding
128 # expectations_dict.
129 actuals_dict_paths = sorted(actuals_dicts.keys())
130 expectations_dict_paths = sorted(expectations_dicts.keys())
131 if actuals_dict_paths != expectations_dict_paths:
132 raise Exception('actuals_dict_paths %s != expectations_dict_paths %s' % (
133 actuals_dict_paths, expectations_dict_paths))
134 common_dict_paths = actuals_dict_paths
135
114 num_common_dict_paths = len(common_dict_paths) 136 num_common_dict_paths = len(common_dict_paths)
115 dict_num = 0 137 dict_num = 0
116 for dict_path in common_dict_paths: 138 for dict_path in common_dict_paths:
117 dict_num += 1 139 dict_num += 1
118 logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' % 140 logging.info('Generating pixel diffs for dict #%d of %d, "%s"...' %
119 (dict_num, num_common_dict_paths, dict_path)) 141 (dict_num, num_common_dict_paths, dict_path))
120 dictA = subdirA_dicts[dict_path] 142 dictA = actuals_dicts[dict_path]
121 dictB = subdirB_dicts[dict_path] 143 dictB = expectations_dicts[dict_path]
122 self._validate_dict_version(dictA) 144 self._validate_dict_version(dictA)
123 self._validate_dict_version(dictB) 145 self._validate_dict_version(dictB)
124 dictA_results = dictA[gm_json.JSONKEY_ACTUALRESULTS] 146 dictA_results = dictA[gm_json.JSONKEY_ACTUALRESULTS]
125 dictB_results = dictB[gm_json.JSONKEY_ACTUALRESULTS] 147 dictB_results = dictB[gm_json.JSONKEY_ACTUALRESULTS]
126 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys())) 148 skp_names = sorted(set(dictA_results.keys() + dictB_results.keys()))
127 for skp_name in skp_names: 149 for skp_name in skp_names:
128 imagepairs_for_this_skp = [] 150 imagepairs_for_this_skp = []
129 151
130 whole_image_A = RenderedPicturesComparisons.get_multilevel( 152 whole_image_A = RenderedPicturesComparisons.get_multilevel(
131 dictA_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE) 153 dictA_results, skp_name, gm_json.JSONKEY_SOURCE_WHOLEIMAGE)
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 results.KEY__EXTRACOLUMNS__TEST: test, 250 results.KEY__EXTRACOLUMNS__TEST: test,
229 # TODO(epoger): Right now, the client UI crashes if it receives 251 # TODO(epoger): Right now, the client UI crashes if it receives
230 # results that do not include this column. 252 # results that do not include this column.
231 # Until we fix that, keep the client happy. 253 # Until we fix that, keep the client happy.
232 results.KEY__EXTRACOLUMNS__BUILDER: 'TODO', 254 results.KEY__EXTRACOLUMNS__BUILDER: 'TODO',
233 } 255 }
234 256
235 try: 257 try:
236 return imagepair.ImagePair( 258 return imagepair.ImagePair(
237 image_diff_db=self._image_diff_db, 259 image_diff_db=self._image_diff_db,
238 base_url=self._image_base_url, 260 base_url=self._image_base_gs_url,
239 imageA_relative_url=imageA_relative_url, 261 imageA_relative_url=imageA_relative_url,
240 imageB_relative_url=imageB_relative_url, 262 imageB_relative_url=imageB_relative_url,
241 extra_columns=extra_columns_dict) 263 extra_columns=extra_columns_dict)
242 except (KeyError, TypeError): 264 except (KeyError, TypeError):
243 logging.exception( 265 logging.exception(
244 'got exception while creating ImagePair for' 266 'got exception while creating ImagePair for'
245 ' test="%s", config="%s", urlPair=("%s","%s")' % ( 267 ' test="%s", config="%s", urlPair=("%s","%s")' % (
246 test, config, imageA_relative_url, imageB_relative_url)) 268 test, config, imageA_relative_url, imageB_relative_url))
247 return None 269 return None
248
249
250 # TODO(epoger): Add main() so this can be called by vm_run_skia_try.sh
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698