| OLD | NEW |
| 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 Calulate differences between image pairs, and store them in a database. | 9 Calulate differences between image pairs, and store them in a database. |
| 10 """ | 10 """ |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 118 # Return early if we do not need to generate diffs. | 118 # Return early if we do not need to generate diffs. |
| 119 if (expected_image_url == actual_image_url or | 119 if (expected_image_url == actual_image_url or |
| 120 not expected_image_url or not actual_image_url): | 120 not expected_image_url or not actual_image_url): |
| 121 return | 121 return |
| 122 | 122 |
| 123 # Get all diff images and values using the skpdiff binary. | 123 # Get all diff images and values using the skpdiff binary. |
| 124 skpdiff_output_dir = tempfile.mkdtemp() | 124 skpdiff_output_dir = tempfile.mkdtemp() |
| 125 try: | 125 try: |
| 126 skpdiff_summary_file = os.path.join(skpdiff_output_dir, | 126 skpdiff_summary_file = os.path.join(skpdiff_output_dir, |
| 127 'skpdiff-output.json') | 127 'skpdiff-output.json') |
| 128 skpdiff_rgbdiff_dir = os.path.join(skpdiff_output_dir, 'rgbDiff') | 128 skpdiff_rgbdiff_dir = os.path.join(storage_root, RGBDIFFS_SUBDIR) |
| 129 skpdiff_whitediff_dir = os.path.join(skpdiff_output_dir, 'whiteDiff') | 129 skpdiff_whitediff_dir = os.path.join(storage_root, WHITEDIFFS_SUBDIR) |
| 130 _mkdir_unless_exists(skpdiff_rgbdiff_dir) |
| 131 _mkdir_unless_exists(skpdiff_rgbdiff_dir) |
| 130 | 132 |
| 131 # TODO(epoger): Consider calling skpdiff ONCE for all image pairs, | 133 # TODO(epoger): Consider calling skpdiff ONCE for all image pairs, |
| 132 # instead of calling it separately for each image pair. | 134 # instead of calling it separately for each image pair. |
| 133 # Pro: we'll incur less overhead from making repeated system calls, | 135 # Pro: we'll incur less overhead from making repeated system calls, |
| 134 # spinning up the skpdiff binary, etc. | 136 # spinning up the skpdiff binary, etc. |
| 135 # Con: we would have to wait until all image pairs were loaded before | 137 # Con: we would have to wait until all image pairs were loaded before |
| 136 # generating any of the diffs? | 138 # generating any of the diffs? |
| 139 # Note(stephana): '--longnames' was added to allow for this |
| 140 # case (multiple files at once) versus specifying output diffs |
| 141 # directly. |
| 137 find_run_binary.run_command( | 142 find_run_binary.run_command( |
| 138 [SKPDIFF_BINARY, '-p', expected_image_file, actual_image_file, | 143 [SKPDIFF_BINARY, '-p', expected_image_file, actual_image_file, |
| 139 '--jsonp', 'false', | 144 '--jsonp', 'false', |
| 145 '--longnames', 'true', |
| 140 '--output', skpdiff_summary_file, | 146 '--output', skpdiff_summary_file, |
| 141 '--differs', 'perceptual', 'different_pixels', | 147 '--differs', 'perceptual', 'different_pixels', |
| 142 '--rgbDiffDir', skpdiff_rgbdiff_dir, | 148 '--rgbDiffDir', skpdiff_rgbdiff_dir, |
| 143 '--whiteDiffDir', skpdiff_whitediff_dir, | 149 '--whiteDiffDir', skpdiff_whitediff_dir, |
| 144 ]) | 150 ]) |
| 145 | 151 |
| 146 # Get information out of the skpdiff_summary_file. | 152 # Get information out of the skpdiff_summary_file. |
| 147 with contextlib.closing(open(skpdiff_summary_file)) as fp: | 153 with contextlib.closing(open(skpdiff_summary_file)) as fp: |
| 148 data = json.load(fp) | 154 data = json.load(fp) |
| 149 | 155 |
| 150 # For now, we can assume there is only one record in the output summary, | 156 # For now, we can assume there is only one record in the output summary, |
| 151 # since we passed skpdiff only one pair of images. | 157 # since we passed skpdiff only one pair of images. |
| 152 record = data['records'][0] | 158 record = data['records'][0] |
| 153 self._width = record['width'] | 159 self._width = record['width'] |
| 154 self._height = record['height'] | 160 self._height = record['height'] |
| 155 # TODO: make max_diff_per_channel a tuple instead of a list, because the | 161 # TODO: make max_diff_per_channel a tuple instead of a list, because the |
| 156 # structure is meaningful (first element is red, second is green, etc.) | 162 # structure is meaningful (first element is red, second is green, etc.) |
| 157 # See http://stackoverflow.com/a/626871 | 163 # See http://stackoverflow.com/a/626871 |
| 158 self._max_diff_per_channel = [ | 164 self._max_diff_per_channel = [ |
| 159 record['maxRedDiff'], record['maxGreenDiff'], record['maxBlueDiff']] | 165 record['maxRedDiff'], record['maxGreenDiff'], record['maxBlueDiff']] |
| 160 rgb_diff_path = record['rgbDiffPath'] | |
| 161 white_diff_path = record['whiteDiffPath'] | |
| 162 per_differ_stats = record['diffs'] | 166 per_differ_stats = record['diffs'] |
| 163 for stats in per_differ_stats: | 167 for stats in per_differ_stats: |
| 164 differ_name = stats['differName'] | 168 differ_name = stats['differName'] |
| 165 if differ_name == 'different_pixels': | 169 if differ_name == 'different_pixels': |
| 166 self._num_pixels_differing = stats['pointsOfInterest'] | 170 self._num_pixels_differing = stats['pointsOfInterest'] |
| 167 elif differ_name == 'perceptual': | 171 elif differ_name == 'perceptual': |
| 168 perceptual_similarity = stats['result'] | 172 perceptual_similarity = stats['result'] |
| 169 | 173 |
| 170 # skpdiff returns the perceptual similarity; convert it to get the | 174 # skpdiff returns the perceptual similarity; convert it to get the |
| 171 # perceptual difference percentage. | 175 # perceptual difference percentage. |
| 172 # skpdiff outputs -1 if the images are different sizes. Treat any | 176 # skpdiff outputs -1 if the images are different sizes. Treat any |
| 173 # output that does not lie in [0, 1] as having 0% perceptual | 177 # output that does not lie in [0, 1] as having 0% perceptual |
| 174 # similarity. | 178 # similarity. |
| 175 if not 0 <= perceptual_similarity <= 1: | 179 if not 0 <= perceptual_similarity <= 1: |
| 176 perceptual_similarity = 0 | 180 perceptual_similarity = 0 |
| 177 self._perceptual_difference = 100 - (perceptual_similarity * 100) | 181 self._perceptual_difference = 100 - (perceptual_similarity * 100) |
| 178 | |
| 179 # Store the rgbdiff and whitediff images generated above. | |
| 180 diff_image_locator = _get_difference_locator( | |
| 181 expected_image_locator=expected_image_locator, | |
| 182 actual_image_locator=actual_image_locator) | |
| 183 basename = str(diff_image_locator) + image_suffix | |
| 184 _mkdir_unless_exists(os.path.join(storage_root, RGBDIFFS_SUBDIR)) | |
| 185 _mkdir_unless_exists(os.path.join(storage_root, WHITEDIFFS_SUBDIR)) | |
| 186 # TODO: Modify skpdiff's behavior so we can tell it exactly where to | |
| 187 # write the image files into, rather than having to move them around | |
| 188 # after skpdiff writes them out. | |
| 189 shutil.copyfile(rgb_diff_path, | |
| 190 os.path.join(storage_root, RGBDIFFS_SUBDIR, basename)) | |
| 191 shutil.copyfile(white_diff_path, | |
| 192 os.path.join(storage_root, WHITEDIFFS_SUBDIR, basename)) | |
| 193 | |
| 194 finally: | 182 finally: |
| 195 shutil.rmtree(skpdiff_output_dir) | 183 shutil.rmtree(skpdiff_output_dir) |
| 196 | 184 |
| 197 # TODO(epoger): Use properties instead of getters throughout. | 185 # TODO(epoger): Use properties instead of getters throughout. |
| 198 # See http://stackoverflow.com/a/6618176 | 186 # See http://stackoverflow.com/a/6618176 |
| 199 def get_num_pixels_differing(self): | 187 def get_num_pixels_differing(self): |
| 200 """Returns the absolute number of pixels that differ.""" | 188 """Returns the absolute number of pixels that differ.""" |
| 201 return self._num_pixels_differing | 189 return self._num_pixels_differing |
| 202 | 190 |
| 203 def get_percent_pixels_differing(self): | 191 def get_percent_pixels_differing(self): |
| (...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 472 | 460 |
| 473 Args: | 461 Args: |
| 474 locator: string, or something that can be represented as a string. | 462 locator: string, or something that can be represented as a string. |
| 475 If None or '', it is returned without modification, because empty | 463 If None or '', it is returned without modification, because empty |
| 476 locators have a particular meaning ("there is no image for this") | 464 locators have a particular meaning ("there is no image for this") |
| 477 """ | 465 """ |
| 478 if locator: | 466 if locator: |
| 479 return DISALLOWED_FILEPATH_CHAR_REGEX.sub('_', str(locator)) | 467 return DISALLOWED_FILEPATH_CHAR_REGEX.sub('_', str(locator)) |
| 480 else: | 468 else: |
| 481 return locator | 469 return locator |
| 482 | |
| 483 def _get_difference_locator(expected_image_locator, actual_image_locator): | |
| 484 """Returns the locator string used to look up the diffs between expected_image | |
| 485 and actual_image. | |
| 486 | |
| 487 We must keep this function in sync with getImageDiffRelativeUrl() in | |
| 488 static/loader.js | |
| 489 | |
| 490 Args: | |
| 491 expected_image_locator: locator string pointing at expected image | |
| 492 actual_image_locator: locator string pointing at actual image | |
| 493 | |
| 494 Returns: already-sanitized locator where the diffs between expected and | |
| 495 actual images can be found | |
| 496 """ | |
| 497 return "%s-vs-%s" % (_sanitize_locator(expected_image_locator), | |
| 498 _sanitize_locator(actual_image_locator)) | |
| OLD | NEW |