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

Side by Side Diff: chrome/test/functional/ispy/image_tools.py

Issue 16855010: Python Tools for Pixel-by-Pixel Image Comparison (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: minor changes: math.ceiling to prevent rounding errors, [0:3] subsections of lists. Created 7 years, 6 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
OLDNEW
(Empty)
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Utilities for performing pixel-by-pixel image comparison."""
6
7
8 import itertools
9 import math
10 import PIL
11
12
13 def _AreTheSameSize(images):
14 """Returns if a set of images are the same size.
craigdh 2013/06/14 19:08:40 It returns either way :) Returns *whether* a set.
cgrimm 2013/06/17 16:24:58 Done.
15
16 Args:
17 images: a list of images to compare
craigdh 2013/06/14 19:08:40 Put blank line between Args/Returns/Raises.
cgrimm 2013/06/17 16:24:58 Done.
18 Returns:
19 boolean
20 Raises:
21 Exception: One image or fewer is passed in.
22 """
23 if len(images) > 1:
24 return not any(images[0].size != img.size for img in images[1:])
25 else:
26 raise Exception('No images passed in')
27
28
29 def _GetColorDist(px1, px2):
30 """Returns the normalized color distance between pixels.
31
32 This function gets the color distance between two pixels and
33 returns a number between 0 and 1 where 0 is lowest distance
34 and 1 is the greatest distance.
35
36 Args:
37 px1: a 3-tuple (R,G,B)
38 px2: a 3-tuple (R,G,B)
39 Returns:
40 a number between 0 and 1
41 """
42 return sum(
43 (ch1-ch2)**2
44 for ch1, ch2 in itertools.izip(px1[0:3], px2[0:3])
45 ) / 195075. # 3*255**2
46
47
48 def _GetColorDistAsColor(px1, px2):
49 """Computes Color Distance as a greyscale pixel.
50
51 This function gets the color distance between two pixels
52 represented as a pixel ranging in color from (0,0,0) to
53 (255,255,255).
54
55 Args:
56 px1: a 3-tuple (R,G,B)
57 px2: a 3-tuple (R,G,B)
58 Returns:
59 a 3-tuple between (0,0,0) and (255,255,255)
60 """
61 n = _GetColorDist(px1, px2)
62 return (int(math.ceil(255*n)),
63 int(math.ceil(255*n)),
64 int(math.ceil(255*n)))
65
66
67 def _Brightness(px):
68 """Gets the brightness of a pixel.
69
70 Returns the sum of a given pixel's color channels.
71
72 Args:
73 px: a 3-tuple (R,G,B)
74 Returns:
75 a number between 0 and 3*255
76 """
77 return sum(px[0:3])
craigdh 2013/06/14 19:08:40 Skip the [0:3], makes an unnecessary copy of the l
cgrimm 2013/06/17 16:24:58 Done.
78
79
80 def _MinPixel(px1, px2):
81 """Gets the pixel with the lower brightness.
82
83 Calculates the brightness of each pixel and returns
84 the pixel with the lower brightness.
85
86 Args:
87 px1: a 3-tuple (R,G,B)
88 px2: a 3-tuple (R,G,B)
89 Returns:
90 a 3-tuple (R,G,B)
91 """
92 if _Brightness(px1) < _Brightness(px2):
93 return px1
94 else:
95 return px2
96
97
98 def _MaxPixel(px1, px2):
99 """gets the pixel with the greater brightness.
100
101 Calculates the brightness of each pixel and returns
102 the pixel with the greater brightness.
103
104 Args:
105 px1: a 3-tuple (R,G,B)
106 px2: a 3-tuple (R,G,B)
107 Returns:
108 a 3-tuple (R,G,B)
109 """
110 if _Brightness(px1) < _Brightness(px2):
111 return px2
112 else:
113 return px1
114
115
116 def CreateMask(images, threshold=True, cutoff=0):
117 """Computes a mask for a set of images.
118
119 Returns an image that is computed from the images
120 passed in. The mask's values are computed by calculating
121 total differences between the image, and storing them
122 such that (255,255,255) represents great difference,
123 and (0,0,0) represents no difference. If is_hard is
124 True, the resulting mask is put through a
125 thresholding pass where pixel values below the
126 cutoff become (0,0,0) and ones above become (255,255,255).
127
128 Args:
129 images: the images to compute the mask from
130 threshold: boolean, whether or not to threshold the mask
131 cutoff: number, the value to threshold the mask at
132 Returns:
133 an image that is a mask of the passed in images.
134 Raises:
135 Exception: if the images passed in are not of the same size
136 """
137 if not _AreTheSameSize(images):
138 raise Exception('All images must be the same size')
139 mask = PIL.Image.new('RGB', images[0].size)
140 mask_data = mask.getdata()
141 image_data = images[0].getdata()
142 for other_image in images[1:]:
143 mask_data = _ComputeLargestDifference(mask_data, image_data,
144 other_image.getdata())
145
146 if threshold:
147 mask.putdata([
148 (255, 255, 255) if _Brightness(px) > cutoff
149 else (0, 0, 0)
150 for px in mask_data
151 ])
152 else:
153 mask.putdata(list(mask_data))
154 return mask
155
156
157 def _ComputeLargestDifference(mask_data, image_data1, image_data2):
158 """Modifies a mask based upon a comparision of two images.
159
160 A helper function used to generate masks. The mask, as
161 it exists when the function is called, is compared to two
162 images on a pixel-by-pixel basis. For each pixel, the
163 ColorDistance between the two images is computed as a Color,
164 and compared against the color of the mask at the same
165 pixel. The brightest of these pixels is chosen for pixel
166 in the images/mask, and a new set of pixels is returned.
167
168 Args:
169 mask_data: a list of pixels of the mask
170 image_data1: a list of pixels of an image
171 image_data2: a list of pixels of another image
172 Returns:
173 a list of pixels representing a modified version of the mask.
174 """
175 return iter(
craigdh 2013/06/14 19:08:40 You shouldn't need the iter
cgrimm 2013/06/17 16:24:58 Done.
176 _MaxPixel(m, _GetColorDistAsColor(i1, i2))
177 for m, i1, i2 in itertools.izip(mask_data, image_data1, image_data2)
178 )
179
180
181 def _ThresholdColorDiff(px1, px2, cutoff):
182 """Thresholds the color distance of two pixels.
183
184 Computes a Threshold of the color distance of two pixels,
185 if the normalized color distance of px1 and px2 is greater than
186 cutoff, returns 1. otherwise returns 0.
187
188 Args:
189 px1: a 3-tuple (R,G,B)
190 px2: a 3-tuple (R,G,B)
191 cutoff: a number used as the threshold limit
192 Returns:
193 either 1. or 0. depending on the distance of px1 and px2.
craigdh 2013/06/14 19:08:40 why not integers?
cgrimm 2013/06/17 16:24:58 Done.
194 """
195 diff = _GetColorDist(px1, px2)
196 if diff > cutoff:
197 return 1.
198 else:
199 return 0.
200
201
202 def _MaskToValue(px):
203 """Converts a black or white pixel to 1. or 0.
204
205 Args:
206 px: a 3-tuple (R,G,B)
207 Returns:
208 1. if the pixel is (0,0,0), 0. if the pixel is (255,255,255)
209 Raises:
210 Exception: if the pixel is not (0,0,0) or (255,255,255)
211 """
212 if px[0:3] == (0, 0, 0):
213 return 1.
214 if px[0:3] == (255, 255, 255):
215 return 0.
216 else:
217 raise Exception('Mask may only contain black or white pixels')
218
219
220 def TotalDifferentPixels(image1, image2, mask=None):
221 """Computes the number of different pixels between two images.
222
223 Args:
224 image1: the first Image to be compared
225 image2: the second Image to be compared
226 mask: an optional mask to occlude parts of the images
227 from calculation
228 Returns:
229 the number of differing pixels between the images.
230 Raises:
231 Exception: if the images to be compared and mask are not the same size.
232 """
233 if mask:
234 if _AreTheSameSize([image1, image2, mask]):
235 return sum(
236 _MaskToValue(m)*_ThresholdColorDiff(px1, px2, 0)
craigdh 2013/06/14 19:08:40 space before and after operators
cgrimm 2013/06/17 16:24:58 Done.
237 for m, px1, px2 in itertools.izip(mask.getdata(),
238 image1.getdata(),
239 image2.getdata())
240 )
241 else:
242 raise Exception('images and mask must be the same size')
243 else:
244 if _AreTheSameSize([image1, image2]):
245 return sum(
246 _ThresholdColorDiff(px1, px2, 0)
247 for px1, px2 in itertools.izip(
248 image1.getdata(),
249 image2.getdata()
250 )
251 )
252 else:
253 raise Exception('images and mask must be the same size')
254
255
256 def SameImage(image1, image2, max_different_pixels=0, mask=None):
257 """Returns a boolean representing if the images are the Same.
craigdh 2013/06/14 19:08:40 if -> whether Same -> same
cgrimm 2013/06/17 16:24:58 Done.
258
259 Returns a boolean relating to whether or not two images
craigdh 2013/06/14 19:08:40 relating to -> indicating
cgrimm 2013/06/17 16:24:58 Done.
260 are similar enough to be considered the same. Essentially
261 wraps the Similarity function and adds a max_different_pixels
262 cutoff for Sameness.
263
264 Args:
265 image1: an Image to compare
266 image2: an Image to compare
267 max_different_pixels: a number that is the cutoff for image sameness
268 mask: an optional Image that occludes parts of the images from
269 same-ness calculation
270 Returns:
271 a boolean representing if the images are the same.
272 Raises:
273 Error: if the images (and mask) are different sizes.
274 """
275
276 different_pixels = TotalDifferentPixels(image1, image2, mask)
277 return different_pixels <= max_different_pixels
OLDNEW
« no previous file with comments | « no previous file | chrome/test/functional/ispy/image_tools_test.py » ('j') | chrome/test/functional/ispy/image_tools_test.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698