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

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

Issue 147453003: Add the perceptual difference metric to the rebaseline server (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Fix for gm_test 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 csv
13 import logging 14 import logging
14 import os 15 import os
15 import re 16 import re
16 import shutil 17 import shutil
18 import sys
19 import tempfile
17 import urllib 20 import urllib
18 try: 21 try:
19 from PIL import Image, ImageChops 22 from PIL import Image, ImageChops
20 except ImportError: 23 except ImportError:
21 raise ImportError('Requires PIL to be installed; see ' 24 raise ImportError('Requires PIL to be installed; see '
22 + 'http://www.pythonware.com/products/pil/') 25 + 'http://www.pythonware.com/products/pil/')
23 26
27 # Set the PYTHONPATH to include the tools directory.
28 sys.path.append(
29 os.path.join(
30 os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir,
31 'tools'))
32 import find_run_binary
33
34 SKPDIFF_BINARY_NAME = 'skpdiff'
35
24 DEFAULT_IMAGE_SUFFIX = '.png' 36 DEFAULT_IMAGE_SUFFIX = '.png'
25 DEFAULT_IMAGES_SUBDIR = 'images' 37 DEFAULT_IMAGES_SUBDIR = 'images'
26 38
27 DISALLOWED_FILEPATH_CHAR_REGEX = re.compile('[^\w\-]') 39 DISALLOWED_FILEPATH_CHAR_REGEX = re.compile('[^\w\-]')
28 40
29 DIFFS_SUBDIR = 'diffs' 41 DIFFS_SUBDIR = 'diffs'
30 WHITEDIFFS_SUBDIR = 'whitediffs' 42 WHITEDIFFS_SUBDIR = 'whitediffs'
31 43
32 VALUES_PER_BAND = 256 44 VALUES_PER_BAND = 256
33 45
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
92 # Generate the whitediff image (any differing pixels show as white). 104 # Generate the whitediff image (any differing pixels show as white).
93 # This is tricky, because when you convert color images to grayscale or 105 # This is tricky, because when you convert color images to grayscale or
94 # black & white in PIL, it has its own ideas about thresholds. 106 # black & white in PIL, it has its own ideas about thresholds.
95 # We have to force it: if a pixel has any color at all, it's a '1'. 107 # We have to force it: if a pixel has any color at all, it's a '1'.
96 bands = diff_image.split() 108 bands = diff_image.split()
97 graydiff_image = ImageChops.lighter(ImageChops.lighter( 109 graydiff_image = ImageChops.lighter(ImageChops.lighter(
98 bands[0], bands[1]), bands[2]) 110 bands[0], bands[1]), bands[2])
99 whitediff_image = (graydiff_image.point(lambda p: p > 0 and VALUES_PER_BAND) 111 whitediff_image = (graydiff_image.point(lambda p: p > 0 and VALUES_PER_BAND)
100 .convert('1', dither=Image.NONE)) 112 .convert('1', dither=Image.NONE))
101 113
114 # Calculate the perceptual difference percentage.
115 skpdiff_csv_dir = tempfile.mkdtemp()
116 try:
117 skpdiff_csv_output = os.path.join(skpdiff_csv_dir, 'skpdiff-output.csv')
118 skpdiff_binary = find_run_binary.find_path_to_program(SKPDIFF_BINARY_NAME)
119 expected_img = os.path.join(storage_root, expected_images_subdir,
120 str(expected_image_locator) + image_suffix)
121 actual_img = os.path.join(storage_root, actual_images_subdir,
122 str(actual_image_locator) + image_suffix)
123 find_run_binary.run_command(
124 [skpdiff_binary, '-p', expected_img, actual_img,
125 '--csv', skpdiff_csv_output, '-d', 'perceptual'])
126 with contextlib.closing(open(skpdiff_csv_output)) as csv_file:
127 for row in csv.DictReader(csv_file):
128 perceptual_similarity = float(row[' perceptual'].strip())
129 if perceptual_similarity == -1:
epoger 2014/02/11 17:48:50 Do you think we should more broadly check for anyt
rmistry 2014/02/11 17:59:56 SGTM. Done.
130 # skpdiff outputs -1 if the images are different sizes, treat them
131 # as having 0% perceptual similarity.
132 perceptual_similarity = 0
133 # skpdiff returns the perceptual similarity, convert it to get the
134 # perceptual difference percentage.
135 self._perceptual_difference = 100 - (perceptual_similarity * 100)
136 finally:
137 shutil.rmtree(skpdiff_csv_dir)
138
102 # Final touches on diff_image: use whitediff_image as an alpha mask. 139 # Final touches on diff_image: use whitediff_image as an alpha mask.
103 # Unchanged pixels are transparent; differing pixels are opaque. 140 # Unchanged pixels are transparent; differing pixels are opaque.
104 diff_image.putalpha(whitediff_image) 141 diff_image.putalpha(whitediff_image)
105 142
106 # Store the diff and whitediff images generated above. 143 # Store the diff and whitediff images generated above.
107 diff_image_locator = _get_difference_locator( 144 diff_image_locator = _get_difference_locator(
108 expected_image_locator=expected_image_locator, 145 expected_image_locator=expected_image_locator,
109 actual_image_locator=actual_image_locator) 146 actual_image_locator=actual_image_locator)
110 basename = str(diff_image_locator) + image_suffix 147 basename = str(diff_image_locator) + image_suffix
111 _save_image(diff_image, os.path.join( 148 _save_image(diff_image, os.path.join(
112 storage_root, DIFFS_SUBDIR, basename)) 149 storage_root, DIFFS_SUBDIR, basename))
113 _save_image(whitediff_image, os.path.join( 150 _save_image(whitediff_image, os.path.join(
114 storage_root, WHITEDIFFS_SUBDIR, basename)) 151 storage_root, WHITEDIFFS_SUBDIR, basename))
115 152
116 # Calculate difference metrics. 153 # Calculate difference metrics.
117 (self._width, self._height) = diff_image.size 154 (self._width, self._height) = diff_image.size
118 self._num_pixels_differing = ( 155 self._num_pixels_differing = (
119 whitediff_image.histogram()[VALUES_PER_BAND - 1]) 156 whitediff_image.histogram()[VALUES_PER_BAND - 1])
120 157
121 def get_num_pixels_differing(self): 158 def get_num_pixels_differing(self):
122 """Returns the absolute number of pixels that differ.""" 159 """Returns the absolute number of pixels that differ."""
123 return self._num_pixels_differing 160 return self._num_pixels_differing
124 161
125 def get_percent_pixels_differing(self): 162 def get_percent_pixels_differing(self):
126 """Returns the percentage of pixels that differ, as a float between 163 """Returns the percentage of pixels that differ, as a float between
127 0 and 100 (inclusive).""" 164 0 and 100 (inclusive)."""
128 return ((float(self._num_pixels_differing) * 100) / 165 return ((float(self._num_pixels_differing) * 100) /
129 (self._width * self._height)) 166 (self._width * self._height))
130 167
168 def get_perceptual_difference(self):
169 """Returns the perceptual difference percentage."""
170 return self._perceptual_difference
171
131 def get_weighted_diff_measure(self): 172 def get_weighted_diff_measure(self):
132 """Returns a weighted measure of image diffs, as a float between 0 and 100 173 """Returns a weighted measure of image diffs, as a float between 0 and 100
133 (inclusive).""" 174 (inclusive)."""
134 return self._weighted_diff_measure 175 return self._weighted_diff_measure
135 176
136 def get_max_diff_per_channel(self): 177 def get_max_diff_per_channel(self):
137 """Returns the maximum difference between the expected and actual images 178 """Returns the maximum difference between the expected and actual images
138 for each R/G/B channel, as a list.""" 179 for each R/G/B channel, as a list."""
139 return self._max_diff_per_channel 180 return self._max_diff_per_channel
140 181
(...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 397
357 Args: 398 Args:
358 expected_image_locator: locator string pointing at expected image 399 expected_image_locator: locator string pointing at expected image
359 actual_image_locator: locator string pointing at actual image 400 actual_image_locator: locator string pointing at actual image
360 401
361 Returns: already-sanitized locator where the diffs between expected and 402 Returns: already-sanitized locator where the diffs between expected and
362 actual images can be found 403 actual images can be found
363 """ 404 """
364 return "%s-vs-%s" % (_sanitize_locator(expected_image_locator), 405 return "%s-vs-%s" % (_sanitize_locator(expected_image_locator),
365 _sanitize_locator(actual_image_locator)) 406 _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