| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Module that outputs an JSON summary containing the comparision of images.""" | 6 """Module that outputs an JSON summary containing the comparision of images.""" |
| 7 | 7 |
| 8 import json | 8 import json |
| 9 import optparse | 9 import optparse |
| 10 import os | 10 import os |
| 11 import posixpath | 11 import posixpath |
| 12 import sys | 12 import sys |
| 13 import traceback | 13 import traceback |
| 14 | 14 |
| 15 sys.path.append( | 15 sys.path.append( |
| 16 os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) | 16 os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) |
| 17 import json_summary_constants | 17 import json_summary_constants |
| 18 | 18 |
| 19 # TODO(epoger): These constants must be kept in sync with the ones in |
| 20 # https://skia.googlesource.com/skia/+/master/tools/PictureRenderer.cpp |
| 21 JSONKEY_HEADER = 'header' |
| 22 JSONKEY_HEADER_TYPE = 'type' |
| 23 JSONKEY_HEADER_REVISION = 'revision' |
| 24 JSONKEY_IMAGE_CHECKSUMALGORITHM = 'checksumAlgorithm' |
| 25 JSONKEY_IMAGE_CHECKSUMVALUE = 'checksumValue' |
| 26 JSONKEY_IMAGE_COMPARISONRESULT = 'comparisonResult' |
| 27 JSONKEY_IMAGE_FILEPATH = 'filepath' |
| 28 JSONKEY_SOURCE_TILEDIMAGES = 'tiled-images' |
| 29 JSONKEY_SOURCE_WHOLEIMAGE = 'whole-image' |
| 30 |
| 31 JSONVALUE_HEADER_TYPE = 'ChecksummedImages' |
| 32 JSONVALUE_HEADER_REVISION = 1 |
| 33 |
| 34 IMAGE_SOURCE = 'imageSource' |
| 35 |
| 19 | 36 |
| 20 def WriteJsonSummary(img_root, nopatch_json, nopatch_images_base_url, | 37 def WriteJsonSummary(img_root, nopatch_json, nopatch_images_base_url, |
| 21 withpatch_json, withpatch_images_base_url, | 38 withpatch_json, withpatch_images_base_url, |
| 22 output_file_path, gs_output_dir, gs_skp_dir, slave_num, | 39 output_file_path, gs_output_dir, gs_skp_dir, slave_num, |
| 23 additions_to_sys_path): | 40 additions_to_sys_path): |
| 24 """Outputs the JSON summary of image comparisions. | 41 """Outputs the JSON summary of image comparisions. |
| 25 | 42 |
| 26 Args: | 43 Args: |
| 27 img_root: (str) The root directory on local disk where we store all images. | 44 img_root: (str) The root directory on local disk where we store all images. |
| 28 nopatch_json: (str) Location of the nopatch render_pictures JSON summary | 45 nopatch_json: (str) Location of the nopatch render_pictures JSON summary |
| (...skipping 22 matching lines...) Expand all Loading... |
| 51 | 68 |
| 52 # Modules from skia's gm/ and gm/rebaseline_server/ dirs. | 69 # Modules from skia's gm/ and gm/rebaseline_server/ dirs. |
| 53 try: | 70 try: |
| 54 import gm_json | 71 import gm_json |
| 55 import imagediffdb | 72 import imagediffdb |
| 56 except ImportError: | 73 except ImportError: |
| 57 print 'sys.path is [%s]' % sys.path | 74 print 'sys.path is [%s]' % sys.path |
| 58 traceback.print_exc() | 75 traceback.print_exc() |
| 59 raise Exception('You need to add gm/ and gm/rebaseline_server to sys.path') | 76 raise Exception('You need to add gm/ and gm/rebaseline_server to sys.path') |
| 60 | 77 |
| 61 files_to_checksums_nopatch = GetFilesAndChecksums(gm_json, nopatch_json) | 78 all_image_descriptions_nopatch = GetImageDescriptions(gm_json, nopatch_json) |
| 62 files_to_checksums_withpatch = GetFilesAndChecksums(gm_json, withpatch_json) | 79 all_image_descriptions_withpatch = GetImageDescriptions( |
| 80 gm_json, withpatch_json) |
| 63 | 81 |
| 64 assert len(files_to_checksums_nopatch) == len(files_to_checksums_withpatch), ( | 82 assert (len(all_image_descriptions_nopatch) == |
| 65 'Number of images in both JSON summary files are different') | 83 len(all_image_descriptions_withpatch)), \ |
| 66 assert files_to_checksums_nopatch.keys() == \ | 84 'Number of images in the two JSON summary files are different' |
| 67 files_to_checksums_withpatch.keys(), ( | 85 assert (all_image_descriptions_nopatch.keys() == |
| 68 'File names in both JSON summary files are different') | 86 all_image_descriptions_withpatch.keys()), \ |
| 87 'SKP filenames in the two JSON summary files are different' |
| 69 | 88 |
| 70 # Compare checksums in both directories and output differences. | 89 # Compare checksums in both directories and output differences. |
| 71 file_differences = [] | 90 file_differences = [] |
| 72 slave_dict = { | 91 slave_dict = { |
| 73 json_summary_constants.JSONKEY_SKPS_LOCATION: gs_skp_dir, | 92 json_summary_constants.JSONKEY_SKPS_LOCATION: gs_skp_dir, |
| 74 json_summary_constants.JSONKEY_FAILED_FILES: file_differences, | 93 json_summary_constants.JSONKEY_FAILED_FILES: file_differences, |
| 75 json_summary_constants.JSONKEY_FILES_LOCATION_NOPATCH: posixpath.join( | 94 json_summary_constants.JSONKEY_FILES_LOCATION_NOPATCH: posixpath.join( |
| 76 gs_output_dir, 'slave%s' % slave_num, 'nopatch-images'), | 95 gs_output_dir, 'slave%s' % slave_num, 'nopatch-images'), |
| 77 json_summary_constants.JSONKEY_FILES_LOCATION_WITHPATCH: posixpath.join( | 96 json_summary_constants.JSONKEY_FILES_LOCATION_WITHPATCH: posixpath.join( |
| 78 gs_output_dir, 'slave%s' % slave_num, 'withpatch-images'), | 97 gs_output_dir, 'slave%s' % slave_num, 'withpatch-images'), |
| 79 json_summary_constants.JSONKEY_FILES_LOCATION_DIFFS: posixpath.join( | 98 json_summary_constants.JSONKEY_FILES_LOCATION_DIFFS: posixpath.join( |
| 80 gs_output_dir, 'slave%s' % slave_num, 'diffs'), | 99 gs_output_dir, 'slave%s' % slave_num, 'diffs'), |
| 81 json_summary_constants.JSONKEY_FILES_LOCATION_WHITE_DIFFS: posixpath.join( | 100 json_summary_constants.JSONKEY_FILES_LOCATION_WHITE_DIFFS: posixpath.join( |
| 82 gs_output_dir, 'slave%s' % slave_num, 'whitediffs') | 101 gs_output_dir, 'slave%s' % slave_num, 'whitediffs') |
| 83 } | 102 } |
| 84 json_summary = { | 103 json_summary = { |
| 85 'slave%s' % slave_num: slave_dict | 104 'slave%s' % slave_num: slave_dict |
| 86 } | 105 } |
| 87 | 106 |
| 88 image_diff_db = imagediffdb.ImageDiffDB(storage_root=img_root) | 107 image_diff_db = imagediffdb.ImageDiffDB(storage_root=img_root) |
| 89 for filename in files_to_checksums_nopatch: | 108 for image_filepath in all_image_descriptions_nopatch: |
| 90 algo_nopatch, checksum_nopatch = files_to_checksums_nopatch[filename] | 109 image_desc_nopatch = all_image_descriptions_nopatch[image_filepath] |
| 91 algo_withpatch, checksum_withpatch = files_to_checksums_withpatch[filename] | 110 image_desc_withpatch = all_image_descriptions_withpatch[image_filepath] |
| 92 assert algo_nopatch == algo_withpatch, 'Different checksum algorithms found' | 111 |
| 112 algo_nopatch = image_desc_nopatch[JSONKEY_IMAGE_CHECKSUMALGORITHM] |
| 113 algo_withpatch = image_desc_withpatch[JSONKEY_IMAGE_CHECKSUMALGORITHM] |
| 114 assert algo_nopatch == algo_withpatch, 'Different checksum algorithms' |
| 115 |
| 116 imagefile_nopatch = image_desc_nopatch[JSONKEY_IMAGE_FILEPATH] |
| 117 imagefile_withpatch = image_desc_withpatch[JSONKEY_IMAGE_FILEPATH] |
| 118 assert imagefile_nopatch == imagefile_withpatch, 'Different imagefile names' |
| 119 |
| 120 skpfile_nopatch = image_desc_nopatch[IMAGE_SOURCE] |
| 121 skpfile_withpatch = image_desc_withpatch[IMAGE_SOURCE] |
| 122 assert skpfile_nopatch == skpfile_withpatch, 'Different skpfile names' |
| 123 |
| 124 checksum_nopatch = image_desc_nopatch[JSONKEY_IMAGE_CHECKSUMVALUE] |
| 125 checksum_withpatch = image_desc_withpatch[JSONKEY_IMAGE_CHECKSUMVALUE] |
| 93 if checksum_nopatch != checksum_withpatch: | 126 if checksum_nopatch != checksum_withpatch: |
| 94 # TODO(epoger): It seems silly that we add this DiffRecord to ImageDiffDB | 127 # TODO(epoger): It seems silly that we add this DiffRecord to ImageDiffDB |
| 95 # and then pull it out again right away, but this is a stepping-stone | 128 # and then pull it out again right away, but this is a stepping-stone |
| 96 # to using ImagePairSet instead of replicating its behavior here. | 129 # to using ImagePairSet instead of replicating its behavior here. |
| 97 image_locator_base = os.path.splitext(filename)[0] | 130 image_locator_base = os.path.splitext(imagefile_nopatch)[0] |
| 98 image_locator_nopatch = image_locator_base + '_nopatch' | 131 image_locator_nopatch = image_locator_base + '_nopatch' |
| 99 image_locator_withpatch = image_locator_base + '_withpatch' | 132 image_locator_withpatch = image_locator_base + '_withpatch' |
| 100 image_diff_db.add_image_pair( | 133 image_diff_db.add_image_pair( |
| 101 expected_image_url=posixpath.join(nopatch_images_base_url, filename), | 134 expected_image_url=posixpath.join( |
| 135 nopatch_images_base_url, image_filepath), |
| 102 expected_image_locator=image_locator_nopatch, | 136 expected_image_locator=image_locator_nopatch, |
| 103 actual_image_url=posixpath.join(withpatch_images_base_url, filename), | 137 actual_image_url=posixpath.join( |
| 138 withpatch_images_base_url, image_filepath), |
| 104 actual_image_locator=image_locator_withpatch) | 139 actual_image_locator=image_locator_withpatch) |
| 105 diff_record = image_diff_db.get_diff_record( | 140 diff_record = image_diff_db.get_diff_record( |
| 106 expected_image_locator=image_locator_nopatch, | 141 expected_image_locator=image_locator_nopatch, |
| 107 actual_image_locator=image_locator_withpatch) | 142 actual_image_locator=image_locator_withpatch) |
| 108 file_differences.append({ | 143 file_differences.append({ |
| 109 json_summary_constants.JSONKEY_FILE_NAME: filename, | 144 json_summary_constants.JSONKEY_FILE_NAME: imagefile_nopatch, |
| 110 json_summary_constants.JSONKEY_SKP_LOCATION: posixpath.join( | 145 json_summary_constants.JSONKEY_SKP_LOCATION: posixpath.join( |
| 111 gs_skp_dir, GetSkpFileName(filename)), | 146 gs_skp_dir, skpfile_nopatch), |
| 112 json_summary_constants.JSONKEY_NUM_PIXELS_DIFFERING: | 147 json_summary_constants.JSONKEY_NUM_PIXELS_DIFFERING: |
| 113 diff_record.get_num_pixels_differing(), | 148 diff_record.get_num_pixels_differing(), |
| 114 json_summary_constants.JSONKEY_PERCENT_PIXELS_DIFFERING: | 149 json_summary_constants.JSONKEY_PERCENT_PIXELS_DIFFERING: |
| 115 diff_record.get_percent_pixels_differing(), | 150 diff_record.get_percent_pixels_differing(), |
| 116 json_summary_constants.JSONKEY_WEIGHTED_DIFF_MEASURE: | 151 json_summary_constants.JSONKEY_WEIGHTED_DIFF_MEASURE: |
| 117 diff_record.get_weighted_diff_measure(), | 152 diff_record.get_weighted_diff_measure(), |
| 118 json_summary_constants.JSONKEY_MAX_DIFF_PER_CHANNEL: | 153 json_summary_constants.JSONKEY_MAX_DIFF_PER_CHANNEL: |
| 119 diff_record.get_max_diff_per_channel(), | 154 diff_record.get_max_diff_per_channel(), |
| 120 json_summary_constants.JSONKEY_PERCEPTUAL_DIFF: | 155 json_summary_constants.JSONKEY_PERCEPTUAL_DIFF: |
| 121 diff_record.get_perceptual_difference(), | 156 diff_record.get_perceptual_difference(), |
| 122 }) | 157 }) |
| 123 if file_differences: | 158 if file_differences: |
| 124 slave_dict[json_summary_constants.JSONKEY_FAILED_FILES_COUNT] = len( | 159 slave_dict[json_summary_constants.JSONKEY_FAILED_FILES_COUNT] = len( |
| 125 file_differences) | 160 file_differences) |
| 126 with open(output_file_path, 'w') as f: | 161 with open(output_file_path, 'w') as f: |
| 127 f.write(json.dumps(json_summary, indent=4, sort_keys=True)) | 162 f.write(json.dumps(json_summary, indent=4, sort_keys=True)) |
| 128 | 163 |
| 129 | 164 |
| 130 def GetSkpFileName(img_file_name): | 165 def GetImageDescriptions(gm_json_mod, json_location): |
| 131 """Determine the SKP file name from the image's file name.""" | 166 """Reads the JSON summary and returns {ImageFilePath: ImageDescription} dict. |
| 132 # TODO(rmistry): The below relies too much on the current output of render | |
| 133 # pictures to determine the root SKP. | |
| 134 return '%s_.skp' % '_'.join(img_file_name.split('_')[:-1]) | |
| 135 | 167 |
| 168 Each ImageDescription is a dict of this form: |
| 169 { |
| 170 JSONKEY_IMAGE_CHECKSUMALGORITHM: 'bitmap-64bitMD5', |
| 171 JSONKEY_IMAGE_CHECKSUMVALUE: 5815827069051002745, |
| 172 JSONKEY_IMAGE_COMPARISONRESULT: 'no-comparison', |
| 173 JSONKEY_IMAGE_FILEPATH: 'red_skp-tile0.png', # equals ImageFilePath dict key |
| 174 IMAGE_SOURCE: 'red.skp' |
| 175 } |
| 176 """ |
| 177 json_data = gm_json_mod.LoadFromFile(json_location) |
| 178 if json_data: |
| 179 header_type = json_data[JSONKEY_HEADER][JSONKEY_HEADER_TYPE] |
| 180 if header_type != JSONVALUE_HEADER_TYPE: |
| 181 raise Exception('expected header_type %s but found %s' % ( |
| 182 JSONVALUE_HEADER_TYPE, header_type)) |
| 183 header_revision = json_data[JSONKEY_HEADER][JSONKEY_HEADER_REVISION] |
| 184 if header_revision != JSONVALUE_HEADER_REVISION: |
| 185 raise Exception('expected header_revision %s but found %s' % ( |
| 186 JSONVALUE_HEADER_REVISION, header_revision)) |
| 136 | 187 |
| 137 def GetFilesAndChecksums(gm_json_mod, json_location): | 188 actual_results = json_data[gm_json_mod.JSONKEY_ACTUALRESULTS] |
| 138 """Reads the JSON summary and returns dict of files to checksums.""" | 189 newdict = {} |
| 139 data = gm_json_mod.LoadFromFile(json_location) | 190 for skp_file in actual_results: |
| 140 if data: | 191 whole_image_description = actual_results[skp_file].get( |
| 141 return data[gm_json_mod.JSONKEY_ACTUALRESULTS][ | 192 JSONKEY_SOURCE_WHOLEIMAGE, None) |
| 142 gm_json_mod.JSONKEY_ACTUALRESULTS_NOCOMPARISON] | 193 all_image_descriptions = actual_results[skp_file].get( |
| 194 JSONKEY_SOURCE_TILEDIMAGES, []) |
| 195 if whole_image_description: |
| 196 all_image_descriptions.append(whole_image_description) |
| 197 for image_description in all_image_descriptions: |
| 198 image_filepath = image_description[JSONKEY_IMAGE_FILEPATH] |
| 199 image_description[IMAGE_SOURCE] = skp_file |
| 200 if image_filepath in newdict: |
| 201 raise Exception('found two images with same filepath %s' % |
| 202 image_filepath) |
| 203 newdict[image_filepath] = image_description |
| 204 return newdict |
| 143 else: | 205 else: |
| 144 return {} | 206 return {} |
| 145 | 207 |
| 146 | 208 |
| 147 if '__main__' == __name__: | 209 if '__main__' == __name__: |
| 148 option_parser = optparse.OptionParser() | 210 option_parser = optparse.OptionParser() |
| 149 option_parser.add_option( | 211 option_parser.add_option( |
| 150 '', '--img_root', | 212 '', '--img_root', |
| 151 help='The root directory on local disk where we store all images.') | 213 help='The root directory on local disk where we store all images.') |
| 152 option_parser.add_option( | 214 option_parser.add_option( |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 192 option_parser.error( | 254 option_parser.error( |
| 193 'Must specify img_root, nopatch_json, nopatch_images_base_url, ' | 255 'Must specify img_root, nopatch_json, nopatch_images_base_url, ' |
| 194 'withpatch_json, withpatch_images_base_url, output_file_path, ' | 256 'withpatch_json, withpatch_images_base_url, output_file_path, ' |
| 195 'gs_output_dir, gs_skp_dir, and slave_num.') | 257 'gs_output_dir, gs_skp_dir, and slave_num.') |
| 196 | 258 |
| 197 WriteJsonSummary(options.img_root, options.nopatch_json, | 259 WriteJsonSummary(options.img_root, options.nopatch_json, |
| 198 options.nopatch_images_base_url, options.withpatch_json, | 260 options.nopatch_images_base_url, options.withpatch_json, |
| 199 options.withpatch_images_base_url, options.output_file_path, | 261 options.withpatch_images_base_url, options.output_file_path, |
| 200 options.gs_output_dir, options.gs_skp_dir, options.slave_num, | 262 options.gs_output_dir, options.gs_skp_dir, options.slave_num, |
| 201 options.add_to_sys_path) | 263 options.add_to_sys_path) |
| OLD | NEW |