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

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

Issue 157593006: rebaseline_server: add ImagePair class, a step towards new intermediate JSON schema (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: style fix Created 6 years, 10 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
« no previous file with comments | « no previous file | gm/rebaseline_server/imagediffdb_test.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 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 """
11 11
12 import contextlib 12 import contextlib
13 import logging 13 import logging
14 import os 14 import os
15 import re
15 import shutil 16 import shutil
16 import urllib 17 import urllib
17 try: 18 try:
18 from PIL import Image, ImageChops 19 from PIL import Image, ImageChops
19 except ImportError: 20 except ImportError:
20 raise ImportError('Requires PIL to be installed; see ' 21 raise ImportError('Requires PIL to be installed; see '
21 + 'http://www.pythonware.com/products/pil/') 22 + 'http://www.pythonware.com/products/pil/')
22 23
23 DEFAULT_IMAGE_SUFFIX = '.png' 24 DEFAULT_IMAGE_SUFFIX = '.png'
24 DEFAULT_IMAGES_SUBDIR = 'images' 25 DEFAULT_IMAGES_SUBDIR = 'images'
25 26
27 DISALLOWED_FILEPATH_CHAR_REGEX = re.compile('[^\w\-]')
28
26 DIFFS_SUBDIR = 'diffs' 29 DIFFS_SUBDIR = 'diffs'
27 WHITEDIFFS_SUBDIR = 'whitediffs' 30 WHITEDIFFS_SUBDIR = 'whitediffs'
28 31
29 VALUES_PER_BAND = 256 32 VALUES_PER_BAND = 256
30 33
31 34
32 class DiffRecord(object): 35 class DiffRecord(object):
33 """ Record of differences between two images. """ 36 """ Record of differences between two images. """
34 37
35 def __init__(self, storage_root, 38 def __init__(self, storage_root,
(...skipping 18 matching lines...) Expand all
54 guarantee uniqueness) 57 guarantee uniqueness)
55 actual_image_url: file or HTTP url from which we will download the 58 actual_image_url: file or HTTP url from which we will download the
56 actual image 59 actual image
57 actual_image_locator: a unique ID string under which we will store the 60 actual_image_locator: a unique ID string under which we will store the
58 actual image within storage_root (probably including a checksum to 61 actual image within storage_root (probably including a checksum to
59 guarantee uniqueness) 62 guarantee uniqueness)
60 expected_images_subdir: the subdirectory expected images are stored in. 63 expected_images_subdir: the subdirectory expected images are stored in.
61 actual_images_subdir: the subdirectory actual images are stored in. 64 actual_images_subdir: the subdirectory actual images are stored in.
62 image_suffix: the suffix of images. 65 image_suffix: the suffix of images.
63 """ 66 """
67 expected_image_locator = _sanitize_locator(expected_image_locator)
68 actual_image_locator = _sanitize_locator(actual_image_locator)
69
64 # Download the expected/actual images, if we don't have them already. 70 # Download the expected/actual images, if we don't have them already.
65 # TODO(rmistry): Add a parameter that makes _download_and_open_image raise 71 # TODO(rmistry): Add a parameter that makes _download_and_open_image raise
66 # an exception if images are not found locally (instead of trying to 72 # an exception if images are not found locally (instead of trying to
67 # download them). 73 # download them).
68 expected_image = _download_and_open_image( 74 expected_image = _download_and_open_image(
69 os.path.join(storage_root, expected_images_subdir, 75 os.path.join(storage_root, expected_images_subdir,
70 str(expected_image_locator) + image_suffix), 76 str(expected_image_locator) + image_suffix),
71 expected_image_url) 77 expected_image_url)
72 actual_image = _download_and_open_image( 78 actual_image = _download_and_open_image(
73 os.path.join(storage_root, actual_images_subdir, 79 os.path.join(storage_root, actual_images_subdir,
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 def get_weighted_diff_measure(self): 131 def get_weighted_diff_measure(self):
126 """Returns a weighted measure of image diffs, as a float between 0 and 100 132 """Returns a weighted measure of image diffs, as a float between 0 and 100
127 (inclusive).""" 133 (inclusive)."""
128 return self._weighted_diff_measure 134 return self._weighted_diff_measure
129 135
130 def get_max_diff_per_channel(self): 136 def get_max_diff_per_channel(self):
131 """Returns the maximum difference between the expected and actual images 137 """Returns the maximum difference between the expected and actual images
132 for each R/G/B channel, as a list.""" 138 for each R/G/B channel, as a list."""
133 return self._max_diff_per_channel 139 return self._max_diff_per_channel
134 140
141 def as_dict(self):
142 """Returns a dictionary representation of this DiffRecord, as needed when
143 constructing the JSON representation."""
144 return {
145 'numDifferingPixels': self._num_pixels_differing,
146 'percentDifferingPixels': self.get_percent_pixels_differing(),
147 'weightedDiffMeasure': self.get_weighted_diff_measure(),
148 'maxDiffPerChannel': self._max_diff_per_channel,
149 }
150
135 151
136 class ImageDiffDB(object): 152 class ImageDiffDB(object):
137 """ Calculates differences between image pairs, maintaining a database of 153 """ Calculates differences between image pairs, maintaining a database of
138 them for download.""" 154 them for download."""
139 155
140 def __init__(self, storage_root): 156 def __init__(self, storage_root):
141 """ 157 """
142 Args: 158 Args:
143 storage_root: string; root path within the DB will store all of its stuff 159 storage_root: string; root path within the DB will store all of its stuff
144 """ 160 """
(...skipping 22 matching lines...) Expand all
167 expected image 183 expected image
168 expected_image_locator: a unique ID string under which we will store the 184 expected_image_locator: a unique ID string under which we will store the
169 expected image within storage_root (probably including a checksum to 185 expected image within storage_root (probably including a checksum to
170 guarantee uniqueness) 186 guarantee uniqueness)
171 actual_image_url: file or HTTP url from which we will download the 187 actual_image_url: file or HTTP url from which we will download the
172 actual image 188 actual image
173 actual_image_locator: a unique ID string under which we will store the 189 actual_image_locator: a unique ID string under which we will store the
174 actual image within storage_root (probably including a checksum to 190 actual image within storage_root (probably including a checksum to
175 guarantee uniqueness) 191 guarantee uniqueness)
176 """ 192 """
193 expected_image_locator = _sanitize_locator(expected_image_locator)
194 actual_image_locator = _sanitize_locator(actual_image_locator)
177 key = (expected_image_locator, actual_image_locator) 195 key = (expected_image_locator, actual_image_locator)
178 if not key in self._diff_dict: 196 if not key in self._diff_dict:
179 try: 197 try:
180 new_diff_record = DiffRecord( 198 new_diff_record = DiffRecord(
181 self._storage_root, 199 self._storage_root,
182 expected_image_url=expected_image_url, 200 expected_image_url=expected_image_url,
183 expected_image_locator=expected_image_locator, 201 expected_image_locator=expected_image_locator,
184 actual_image_url=actual_image_url, 202 actual_image_url=actual_image_url,
185 actual_image_locator=actual_image_locator) 203 actual_image_locator=actual_image_locator)
186 except Exception: 204 except Exception:
187 logging.exception('got exception while creating new DiffRecord') 205 logging.exception('got exception while creating new DiffRecord')
188 return 206 return
189 self._diff_dict[key] = new_diff_record 207 self._diff_dict[key] = new_diff_record
190 208
191 def get_diff_record(self, expected_image_locator, actual_image_locator): 209 def get_diff_record(self, expected_image_locator, actual_image_locator):
192 """Returns the DiffRecord for this image pair. 210 """Returns the DiffRecord for this image pair.
193 211
194 Raises a KeyError if we don't have a DiffRecord for this image pair. 212 Raises a KeyError if we don't have a DiffRecord for this image pair.
195 """ 213 """
196 key = (expected_image_locator, actual_image_locator) 214 key = (_sanitize_locator(expected_image_locator),
215 _sanitize_locator(actual_image_locator))
197 return self._diff_dict[key] 216 return self._diff_dict[key]
198 217
199 218
200 # Utility functions 219 # Utility functions
201 220
202 def _calculate_weighted_diff_metric(histogram, num_pixels): 221 def _calculate_weighted_diff_metric(histogram, num_pixels):
203 """Given the histogram of a diff image (per-channel diff at each 222 """Given the histogram of a diff image (per-channel diff at each
204 pixel between two images), calculate the weighted diff metric (a 223 pixel between two images), calculate the weighted diff metric (a
205 stab at how different the two images really are). 224 stab at how different the two images really are).
206 225
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
315 334
316 def _mkdir_unless_exists(path): 335 def _mkdir_unless_exists(path):
317 """Unless path refers to an already-existing directory, create it. 336 """Unless path refers to an already-existing directory, create it.
318 337
319 Args: 338 Args:
320 path: path on local disk 339 path: path on local disk
321 """ 340 """
322 if not os.path.isdir(path): 341 if not os.path.isdir(path):
323 os.makedirs(path) 342 os.makedirs(path)
324 343
344 def _sanitize_locator(locator):
345 """Returns a sanitized version of a locator (one in which we know none of the
346 characters will have special meaning in filenames).
347
348 Args:
349 locator: string, or something that can be represented as a string
350 """
351 return DISALLOWED_FILEPATH_CHAR_REGEX.sub('_', str(locator))
352
325 def _get_difference_locator(expected_image_locator, actual_image_locator): 353 def _get_difference_locator(expected_image_locator, actual_image_locator):
326 """Returns the locator string used to look up the diffs between expected_image 354 """Returns the locator string used to look up the diffs between expected_image
327 and actual_image. 355 and actual_image.
328 356
329 Args: 357 Args:
330 expected_image_locator: locator string pointing at expected image 358 expected_image_locator: locator string pointing at expected image
331 actual_image_locator: locator string pointing at actual image 359 actual_image_locator: locator string pointing at actual image
332 360
333 Returns: locator where the diffs between expected and actual images can be 361 Returns: already-sanitized locator where the diffs between expected and
334 found 362 actual images can be found
335 """ 363 """
336 return "%s-vs-%s" % (expected_image_locator, actual_image_locator) 364 return "%s-vs-%s" % (_sanitize_locator(expected_image_locator),
365 _sanitize_locator(actual_image_locator))
OLDNEW
« no previous file with comments | « no previous file | gm/rebaseline_server/imagediffdb_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698