@@ -40,20 +40,20 @@ class Kernel(ABC):
40
40
"""
41
41
42
42
def __init__ (self , plugin ):
43
- # Containment of the plugin allows the kernel to cherry-pick required state
43
+ # Containment of the plugin allows the kernel to cherry-pick required state.
44
44
self ._plugin = plugin
45
45
46
46
@abstractmethod
47
- def equivalent_hash (self , actual , expected , marker = None ):
47
+ def equivalent_hash (self , result , baseline , marker = None ):
48
48
"""
49
- Determine whether the kernel considers the provided actual and
50
- expected hashes as similar.
49
+ Determine whether the kernel considers the provided result (actual)
50
+ and baseline ( expected) hashes as similar.
51
51
52
52
Parameters
53
53
----------
54
- actual : str
55
- The hash of the test image.
56
- expected : str
54
+ result : str
55
+ The hash of the image generated by the test .
56
+ baseline : str
57
57
The hash of the baseline image.
58
58
marker : pytest.Mark
59
59
The test marker, which may contain kwarg options to be
@@ -62,7 +62,7 @@ def equivalent_hash(self, actual, expected, marker=None):
62
62
Returns
63
63
-------
64
64
bool
65
- Whether the actual and expected hashes are deemed similar.
65
+ Whether the result and baseline hashes are deemed similar.
66
66
67
67
"""
68
68
@@ -112,17 +112,16 @@ def update_summary(self, summary):
112
112
113
113
Returns
114
114
-------
115
- dict
116
- The updated image comparison summary.
115
+ None
117
116
118
117
"""
119
- return summary
118
+ summary [ "kernel" ] = self . name
120
119
121
120
122
121
class KernelPHash (Kernel ):
123
122
"""
124
123
Kernel that calculates a perceptual hash of an image for the
125
- specified hash size (N).
124
+ specified hash size (N) and high frequency factor .
126
125
127
126
Where the resultant perceptual hash will be composed of N**2 bits.
128
127
@@ -132,31 +131,37 @@ class KernelPHash(Kernel):
132
131
133
132
def __init__ (self , plugin ):
134
133
super ().__init__ (plugin )
135
- # keep state of equivalence result
134
+ # Keep state of the equivalence result.
136
135
self .equivalent = None
137
- # keep state of hash hamming distance (whole number) result
136
+ # Keep state of hash hamming distance (whole number) result.
138
137
self .hamming_distance = None
139
- # value may be overridden by py.test marker kwarg
138
+ # Value may be overridden by py.test marker kwarg.
140
139
self .hamming_tolerance = (
141
140
self ._plugin .hamming_tolerance or DEFAULT_HAMMING_TOLERANCE
142
141
)
143
- # the hash-size (N) defines the resultant N**2 bits hash size
142
+ # The hash-size (N) defines the resultant N**2 bits hash size.
144
143
self .hash_size = self ._plugin .hash_size
145
- # the level of image detail or structure represented by perceptual hash
144
+ # The level of image detail (high freq) or structure (low freq)
145
+ # represented in perceptual hash thru discrete cosine transform.
146
146
self .high_freq_factor = (
147
147
self ._plugin .high_freq_factor or DEFAULT_HIGH_FREQUENCY_FACTOR
148
148
)
149
- # py.test marker kwarg
149
+ # py.test marker kwarg.
150
150
self .option = "hamming_tolerance"
151
151
152
- def equivalent_hash (self , actual , expected , marker = None ):
152
+ def equivalent_hash (self , result , baseline , marker = None ):
153
153
if marker :
154
154
value = marker .kwargs .get (self .option )
155
155
if value is not None :
156
+ # Override with the decorator marker value.
156
157
self .hamming_tolerance = int (value )
157
- actual = imagehash .hex_to_hash (actual )
158
- expected = imagehash .hex_to_hash (expected )
159
- self .hamming_distance = actual - expected
158
+ # Convert string hexdigest hashes to imagehash.ImageHash instances.
159
+ result = imagehash .hex_to_hash (result )
160
+ baseline = imagehash .hex_to_hash (baseline )
161
+ # Unlike cryptographic hashes, perceptual hashes can measure the
162
+ # degree of "similarity" through hamming distance bit differences
163
+ # between the hashes.
164
+ self .hamming_distance = result - baseline
160
165
self .equivalent = self .hamming_distance <= self .hamming_tolerance
161
166
return self .equivalent
162
167
@@ -170,6 +175,7 @@ def generate_hash(self, buffer):
170
175
171
176
def update_status (self , message ):
172
177
result = str () if message is None else str (message )
178
+ # Only update the status message for non-equivalent hash comparisons.
173
179
if self .equivalent is False :
174
180
msg = (
175
181
f"Hash hamming distance of { self .hamming_distance } bits > "
@@ -179,9 +185,9 @@ def update_status(self, message):
179
185
return result
180
186
181
187
def update_summary (self , summary ):
188
+ super ().update_summary (summary )
182
189
summary ["hamming_distance" ] = self .hamming_distance
183
190
summary ["hamming_tolerance" ] = self .hamming_tolerance
184
- return summary
185
191
186
192
187
193
class KernelSHA256 (Kernel ):
@@ -192,8 +198,13 @@ class KernelSHA256(Kernel):
192
198
193
199
name = KERNEL_SHA256
194
200
195
- def equivalent_hash (self , actual , expected , marker = None ):
196
- return actual == expected
201
+ def equivalent_hash (self , result , baseline , marker = None ):
202
+ # Simple cryptographic hash binary comparison. Interpretation of
203
+ # the comparison result is that the hashes are either identical or
204
+ # not identical. For non-identical hashes, it is not possible to
205
+ # determine a heuristic of hash "similarity" due to the nature of
206
+ # cryptographic hashes.
207
+ return result == baseline
197
208
198
209
def generate_hash (self , buffer ):
199
210
buffer .seek (0 )
0 commit comments