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

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

Issue 59283006: rebaseline_server: add pixel diffs, and sorting by diff metrics (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: rename_selftest Created 7 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « gm/rebaseline_server/imagediffdb_test.py ('k') | gm/rebaseline_server/server.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2013 Google Inc. 4 Copyright 2013 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 Repackage expected/actual GM results as needed by our HTML rebaseline viewer. 9 Repackage expected/actual GM results as needed by our HTML rebaseline viewer.
10 """ 10 """
(...skipping 11 matching lines...) Expand all
22 # 22 #
23 # We need to add the 'gm' directory, so that we can import gm_json.py within 23 # We need to add the 'gm' directory, so that we can import gm_json.py within
24 # that directory. That script allows us to parse the actual-results.json file 24 # that directory. That script allows us to parse the actual-results.json file
25 # written out by the GM tool. 25 # written out by the GM tool.
26 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end* 26 # Make sure that the 'gm' dir is in the PYTHONPATH, but add it at the *end*
27 # so any dirs that are already in the PYTHONPATH will be preferred. 27 # so any dirs that are already in the PYTHONPATH will be preferred.
28 GM_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 28 GM_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
29 if GM_DIRECTORY not in sys.path: 29 if GM_DIRECTORY not in sys.path:
30 sys.path.append(GM_DIRECTORY) 30 sys.path.append(GM_DIRECTORY)
31 import gm_json 31 import gm_json
32 import imagediffdb
32 33
33 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 34 IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
34 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config) 35 IMAGE_FILENAME_FORMATTER = '%s_%s.png' # pass in (testname, config)
35 36
36 FIELDS_PASSED_THRU_VERBATIM = [ 37 FIELDS_PASSED_THRU_VERBATIM = [
37 gm_json.JSONKEY_EXPECTEDRESULTS_BUGS, 38 gm_json.JSONKEY_EXPECTEDRESULTS_BUGS,
38 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE, 39 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE,
39 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED, 40 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED,
40 ] 41 ]
41 CATEGORIES_TO_SUMMARIZE = [ 42 CATEGORIES_TO_SUMMARIZE = [
42 'builder', 'test', 'config', 'resultType', 43 'builder', 'test', 'config', 'resultType',
43 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE, 44 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE,
44 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED, 45 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED,
45 ] 46 ]
46 47
47 RESULTS_ALL = 'all' 48 RESULTS_ALL = 'all'
48 RESULTS_FAILURES = 'failures' 49 RESULTS_FAILURES = 'failures'
49 50
50 class Results(object): 51 class Results(object):
51 """ Loads actual and expected results from all builders, supplying combined 52 """ Loads actual and expected results from all builders, supplying combined
52 reports as requested. 53 reports as requested.
53 54
54 Once this object has been constructed, the results (in self._results[]) 55 Once this object has been constructed, the results (in self._results[])
55 are immutable. If you want to update the results based on updated JSON 56 are immutable. If you want to update the results based on updated JSON
56 file contents, you will need to create a new Results object.""" 57 file contents, you will need to create a new Results object."""
57 58
58 def __init__(self, actuals_root, expected_root): 59 def __init__(self, actuals_root, expected_root, generated_images_root):
59 """ 60 """
60 Args: 61 Args:
61 actuals_root: root directory containing all actual-results.json files 62 actuals_root: root directory containing all actual-results.json files
62 expected_root: root directory containing all expected-results.json files 63 expected_root: root directory containing all expected-results.json files
64 generated_images_root: directory within which to create all pixels diffs;
65 if this directory does not yet exist, it will be created
63 """ 66 """
67 self._image_diff_db = imagediffdb.ImageDiffDB(generated_images_root)
64 self._actuals_root = actuals_root 68 self._actuals_root = actuals_root
65 self._expected_root = expected_root 69 self._expected_root = expected_root
66 self._load_actual_and_expected() 70 self._load_actual_and_expected()
67 self._timestamp = int(time.time()) 71 self._timestamp = int(time.time())
68 72
69 def get_timestamp(self): 73 def get_timestamp(self):
70 """Return the time at which this object was created, in seconds past epoch 74 """Return the time at which this object was created, in seconds past epoch
71 (UTC). 75 (UTC).
72 """ 76 """
73 return self._timestamp 77 return self._timestamp
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
242 # Check: did we write out the set of per-builder dictionaries we 246 # Check: did we write out the set of per-builder dictionaries we
243 # expected to? 247 # expected to?
244 expected_builders_written = sorted(meta_dict.keys()) 248 expected_builders_written = sorted(meta_dict.keys())
245 actual_builders_written.sort() 249 actual_builders_written.sort()
246 if expected_builders_written != actual_builders_written: 250 if expected_builders_written != actual_builders_written:
247 raise KeyError( 251 raise KeyError(
248 'expected to write dicts for builders %s, but actually wrote them ' 252 'expected to write dicts for builders %s, but actually wrote them '
249 'for builders %s' % ( 253 'for builders %s' % (
250 expected_builders_written, actual_builders_written)) 254 expected_builders_written, actual_builders_written))
251 255
256 def _generate_pixel_diffs_if_needed(self, test, expected_image, actual_image):
257 """If expected_image and actual_image both exist but are different,
258 add the image pair to self._image_diff_db and generate pixel diffs.
259
260 Args:
261 test: string; name of test
262 expected_image: (hashType, hashDigest) tuple describing the expected image
263 actual_image: (hashType, hashDigest) tuple describing the actual image
264 """
265 if expected_image == actual_image:
266 return
267
268 (expected_hashtype, expected_hashdigest) = expected_image
269 (actual_hashtype, actual_hashdigest) = actual_image
270 if None in [expected_hashtype, expected_hashdigest,
271 actual_hashtype, actual_hashdigest]:
272 return
273
274 expected_url = gm_json.CreateGmActualUrl(
275 test_name=test, hash_type=expected_hashtype,
276 hash_digest=expected_hashdigest)
277 actual_url = gm_json.CreateGmActualUrl(
278 test_name=test, hash_type=actual_hashtype,
279 hash_digest=actual_hashdigest)
280 self._image_diff_db.add_image_pair(
281 expected_image_locator=expected_hashdigest,
282 expected_image_url=expected_url,
283 actual_image_locator=actual_hashdigest,
284 actual_image_url=actual_url)
285
252 def _load_actual_and_expected(self): 286 def _load_actual_and_expected(self):
253 """Loads the results of all tests, across all builders (based on the 287 """Loads the results of all tests, across all builders (based on the
254 files within self._actuals_root and self._expected_root), 288 files within self._actuals_root and self._expected_root),
255 and stores them in self._results. 289 and stores them in self._results.
256 """ 290 """
257 actual_builder_dicts = Results._read_dicts_from_root(self._actuals_root) 291 actual_builder_dicts = Results._read_dicts_from_root(self._actuals_root)
258 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root) 292 expected_builder_dicts = Results._read_dicts_from_root(self._expected_root)
259 293
260 categories_all = {} 294 categories_all = {}
261 categories_failures = {} 295 categories_failures = {}
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
336 # category? Maybe we should not rely on the result_type 370 # category? Maybe we should not rely on the result_type
337 # categories recorded within the gm_actuals AT ALL, and 371 # categories recorded within the gm_actuals AT ALL, and
338 # instead evaluate the result_type ourselves based on what 372 # instead evaluate the result_type ourselves based on what
339 # we see in expectations vs actual checksum? 373 # we see in expectations vs actual checksum?
340 if expected_image == actual_image: 374 if expected_image == actual_image:
341 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED 375 updated_result_type = gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED
342 else: 376 else:
343 updated_result_type = result_type 377 updated_result_type = result_type
344 378
345 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups() 379 (test, config) = IMAGE_FILENAME_RE.match(image_name).groups()
380 self._generate_pixel_diffs_if_needed(
381 test=test, expected_image=expected_image,
382 actual_image=actual_image)
346 results_for_this_test = { 383 results_for_this_test = {
347 'resultType': updated_result_type, 384 'resultType': updated_result_type,
348 'builder': builder, 385 'builder': builder,
349 'test': test, 386 'test': test,
350 'config': config, 387 'config': config,
351 'actualHashType': actual_image[0], 388 'actualHashType': actual_image[0],
352 'actualHashDigest': str(actual_image[1]), 389 'actualHashDigest': str(actual_image[1]),
353 'expectedHashType': expected_image[0], 390 'expectedHashType': expected_image[0],
354 'expectedHashDigest': str(expected_image[1]), 391 'expectedHashDigest': str(expected_image[1]),
355 392
356 # FIELDS_PASSED_THRU_VERBATIM that may be overwritten below... 393 # FIELDS_PASSED_THRU_VERBATIM that may be overwritten below...
357 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE: False, 394 gm_json.JSONKEY_EXPECTEDRESULTS_IGNOREFAILURE: False,
358 } 395 }
359 if expectations_per_test: 396 if expectations_per_test:
360 for field in FIELDS_PASSED_THRU_VERBATIM: 397 for field in FIELDS_PASSED_THRU_VERBATIM:
361 results_for_this_test[field] = expectations_per_test.get(field) 398 results_for_this_test[field] = expectations_per_test.get(field)
399
400 if updated_result_type == gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON:
401 pass # no diff record to calculate at all
402 elif updated_result_type == gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
403 results_for_this_test['percentDifferingPixels'] = 0
404 results_for_this_test['weightedDiffMeasure'] = 0
405 else:
406 try:
407 diff_record = self._image_diff_db.get_diff_record(
408 expected_image_locator=expected_image[1],
409 actual_image_locator=actual_image[1])
410 results_for_this_test['percentDifferingPixels'] = (
411 diff_record.get_percent_pixels_differing())
412 results_for_this_test['weightedDiffMeasure'] = (
413 diff_record.get_weighted_diff_measure())
414 except KeyError:
415 logging.warning('unable to find diff_record for ("%s", "%s")' %
416 (expected_image[1], actual_image[1]))
417 pass
418
362 Results._add_to_category_dict(categories_all, results_for_this_test) 419 Results._add_to_category_dict(categories_all, results_for_this_test)
363 data_all.append(results_for_this_test) 420 data_all.append(results_for_this_test)
364 421
365 # TODO(epoger): In effect, we have a list of resultTypes that we 422 # TODO(epoger): In effect, we have a list of resultTypes that we
366 # include in the different result lists (data_all and data_failures). 423 # include in the different result lists (data_all and data_failures).
367 # This same list should be used by the calls to 424 # This same list should be used by the calls to
368 # Results._ensure_included_in_category_dict() earlier on. 425 # Results._ensure_included_in_category_dict() earlier on.
369 if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED: 426 if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
370 Results._add_to_category_dict(categories_failures, 427 Results._add_to_category_dict(categories_failures,
371 results_for_this_test) 428 results_for_this_test)
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 category_dict: category dict-of-dicts to modify 470 category_dict: category dict-of-dicts to modify
414 category_name: category name, as a string 471 category_name: category name, as a string
415 category_values: list of values we want to make sure are represented 472 category_values: list of values we want to make sure are represented
416 for this category 473 for this category
417 """ 474 """
418 if not category_dict.get(category_name): 475 if not category_dict.get(category_name):
419 category_dict[category_name] = {} 476 category_dict[category_name] = {}
420 for category_value in category_values: 477 for category_value in category_values:
421 if not category_dict[category_name].get(category_value): 478 if not category_dict[category_name].get(category_value):
422 category_dict[category_name][category_value] = 0 479 category_dict[category_name][category_value] = 0
OLDNEW
« no previous file with comments | « gm/rebaseline_server/imagediffdb_test.py ('k') | gm/rebaseline_server/server.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698