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 |