-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathimgdiff.py
More file actions
211 lines (163 loc) · 6.54 KB
/
imgdiff.py
File metadata and controls
211 lines (163 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""
Image Difference for Blend Modes Library.
This module provides functions to compare two images.
It calculates the pixel-wise difference and the percentage of differing pixels between
two images, which is useful for testing and validating blend mode implementations.
Functions:
- image_diff: Compute the difference between two images as a float between 0-1 or
0-100
- image_diff_array: Compute the difference between two images as numpy arrays as a
float between 0-1 or 0-100
- is_equal: Check if two images are identical, optionally allowing for a tolerance
in pixel values.
- is_x_diff: Compare two images and return True/False if the image is within `tolerance` of
`cmp_diff`.
"""
from __future__ import annotations
import numpy as np
from PIL import Image
def is_x_diff(
img1in: Image.Image,
img2in: Image.Image,
compare_mode: str = "RGBA",
cmp_diff: float = 0,
tolerance: float = 0,
*,
percentage: bool = True,
) -> bool:
"""
Compare two images and return True/False if the image is within `tolerance` of
`cmp_diff`.
For example, a black and white image compared in 'RGB' mode would
return a value of 100, which would then be checked if its between
`cmp_diff - tolerance` and `cmp_diff + tolerance`
:param Image.Image img1in: image 1 to compare
:param Image.Image img2in: image 2 to compare
:param str compare_mode: how should the pillow images be compared? eg RGBA, RGB, L etc
:param float cmp_diff: how 'unequal' should the images be? 0 for identical, 1 (or 100)
for completely different (eg black + white in L mode)
param float tolerance: what tolerance should we accept on the inequality?
param bool percentage: are we comparing in percentage mode vs 0-1 mode?
:return bool: True/False if the images are within `tolerance` of
`cmp_diff`.
Example Use
-----------
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> is_x_diff(img1, img2, compare_mode="RGB", cmp_diff=33, tolerance=1)
True
>>> img1 = Image.new("RGB", (100, 100), "white")
>>> img2 = Image.new("RGB", (100, 100), "black")
>>> is_x_diff(img1, img2, compare_mode="RGB", cmp_diff=100, tolerance=1)
True
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> is_x_diff(img1, img2, compare_mode="L", cmp_diff=18, tolerance=1)
True
"""
compare_res = image_diff(img1in, img2in, compare_mode, percentage=percentage)
return cmp_diff - tolerance <= compare_res <= cmp_diff + tolerance
def is_equal(
img1in: Image.Image,
img2in: Image.Image,
compare_mode: str = "RGBA",
tolerance: float = 0,
*,
percentage: bool = True,
) -> bool:
"""
Compare two images and return True/False if the image is within `tolerance` of
the second image.
:param Image.Image img1in: image 1 to compare
:param Image.Image img2in: image 2 to compare
:param str compare_mode: how should the pillow images be compared? eg RGBA, RGB, L etc
param float tolerance: what tolerance should we accept on any inequality?
param bool percentage: are we comparing in percentage mode vs 0-1 mode?
:return bool: if the images are equal with a given tolerance
Example Use
-----------
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> is_equal(img1, img2, compare_mode="RGB", tolerance=1)
False
>>> img1 = Image.new("RGB", (100, 100), "white")
>>> img2 = Image.new("RGB", (100, 100), "black")
>>> is_equal(img1, img2, compare_mode="RGB", tolerance=1)
False
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> is_equal(img1, img2, compare_mode="L", tolerance=1)
False
"""
compare_res = image_diff(img1in, img2in, compare_mode, percentage=percentage)
return compare_res <= tolerance
def image_diff(
img1in: Image.Image, img2in: Image.Image, compare_mode: str = "RGBA", *, percentage: bool = True
) -> float:
"""
Compare two images and return the difference as a value between 0 and 1, or
if percentage: 0 and 100.
For example, a black and white image compared in 'RGB' mode would
return a value of 100, which would then be checked if its between
`cmp_diff - tolerance` and `cmp_diff + tolerance`
:param Image.Image img1in: image 1 to compare
:param Image.Image img2in: image 2 to compare
:param str compare_mode: how should the pillow images be compared? eg RGBA, RGB, L etc
param bool percentage: are we comparing in percentage mode vs 0-1 mode?
:return float: value representing how different the images are
Example Use
-----------
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> res = image_diff(img1, img2, compare_mode="RGB")
>>> int(res)
33
>>> img1 = Image.new("RGB", (100, 100), "white")
>>> img2 = Image.new("RGB", (100, 100), "black")
>>> image_diff(img1, img2, compare_mode="RGB")
100.0
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> res = image_diff(img1, img2, compare_mode="L")
>>> int(res)
18
"""
img1 = img1in.convert(mode=compare_mode)
img2 = img2in.convert(mode=compare_mode)
return image_diff_array(img1, img2) * (100 if percentage else 1)
def image_diff_array(img1in: Image.Image | np.ndarray, img2in: Image.Image | np.ndarray) -> float:
"""
Compare two images and return difference between 0, and 1.
Supports both PIL Images and NumPy arrays.
Both images must be in the same mode/ shape
:param Image.Image | np.ndarray img1in: image 1 to compare
:param Image.Image | np.ndarray img2in: image 2 to compare
:return float: value representing how different the images are. between 0, and 1
Example Use
-----------
>>> img1 = Image.new("RGB", (100, 100), "red")
>>> img2 = Image.new("RGB", (100, 100), "blue")
>>> res = image_diff(img1, img2)
>>> int(res)
25
>>> img1 = Image.new("RGB", (100, 100), "white")
>>> img2 = Image.new("RGB", (100, 100), "black")
>>> image_diff(img1, img2)
75.0
"""
# Convert PIL images to NumPy arrays if needed
img1 = np.array(img1in, dtype=np.int16) if isinstance(img1in, Image.Image) else img1in
img2 = np.array(img2in, dtype=np.int16) if isinstance(img2in, Image.Image) else img2in
# Ensure images have the same dimensions
if img1.shape != img2.shape:
msg = "Images must have the same dimensions for comparison."
raise ValueError(msg)
# Compute absolute difference
difference = np.abs(img1 - img2)
# Sum the differences and normalize to get a percentage
total_diff = np.sum(difference)
return float(total_diff / img1.size / 255)
if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
doctest.testmod()