2
2
This module contains the supported hashing kernel implementations.
3
3
4
4
"""
5
- from PIL import Image
6
- from abc import ABC , abstractmethod
7
5
import hashlib
6
+ from abc import ABC , abstractmethod
7
+
8
8
import imagehash
9
+ from PIL import Image
10
+
11
+ #: The default hamming distance bit tolerance for "similar" imagehash hashes.
12
+ DEFAULT_HAMMING_TOLERANCE = 2
9
13
14
+ #: The default imagehash hash size (N), resulting in a hash of N**2 bits.
15
+ DEFAULT_HASH_SIZE = 16
10
16
17
+ #: Registered kernel names.
11
18
KERNEL_SHA256 = "sha256"
12
19
KERNEL_PHASH = "phash"
13
20
14
21
__all__ = [
22
+ "DEFAULT_HAMMING_TOLERANCE" ,
23
+ "DEFAULT_HASH_SIZE" ,
15
24
"KERNEL_PHASH" ,
16
25
"KERNEL_SHA256" ,
17
26
"KernelPHash" ,
22
31
23
32
class Kernel (ABC ):
24
33
"""
25
- Kernel abstract base class (ABC) which defines a common kernel API.
34
+ Kernel abstract base class (ABC) which defines a simple common kernel API.
26
35
27
36
"""
28
37
29
38
def __init__ (self , plugin ):
39
+ # Containment (read-only) of the plugin allows the kernel to cherry-pick state that it requires
30
40
self ._plugin = plugin
31
41
42
+ @abstractmethod
43
+ def equivalent_hash (self , actual , expected , marker = None ):
44
+ """
45
+ Determine whether the kernel considers the provided actual and
46
+ expected hashes as similar.
47
+
48
+ Parameters
49
+ ----------
50
+ actual : str
51
+ The hash of the test image.
52
+ expected : str
53
+ The hash of the baseline image.
54
+ marker : pytest.Mark
55
+ The test marker, which may contain kwarg options to be
56
+ applied to the equivalence test.
57
+
58
+ Returns
59
+ -------
60
+ bool
61
+ Whether the actual and expected hashes are deemed similar.
62
+
63
+ """
64
+
32
65
@abstractmethod
33
66
def generate_hash (self , buffer ):
34
67
"""
@@ -47,28 +80,22 @@ def generate_hash(self, buffer):
47
80
48
81
"""
49
82
50
- @abstractmethod
51
- def equivalent_hash (self , actual , expected , marker = None ):
83
+ def update_status (self , message ):
52
84
"""
53
- Determine whether the kernel considers the provided actual and
54
- expected hashes as similar.
85
+ Append the kernel status message to the provided message.
55
86
56
87
Parameters
57
88
----------
58
- actual : str
59
- The hash of the test image.
60
- expected : str
61
- The hash of the baseline image.
62
- marker : pytest.Mark
63
- The test marker, which may contain kwarg options to be
64
- applied to the equivalence test.
89
+ message : str
90
+ The existing status message.
65
91
66
92
Returns
67
93
-------
68
- bool
69
- Whether the actual and expected hashes are deemed similar .
94
+ str
95
+ The updated status message .
70
96
71
97
"""
98
+ return message
72
99
73
100
def update_summary (self , summary ):
74
101
"""
@@ -81,7 +108,7 @@ def update_summary(self, summary):
81
108
Returns
82
109
-------
83
110
dict
84
- The image comparison summary.
111
+ The updated image comparison summary.
85
112
86
113
"""
87
114
return summary
@@ -104,23 +131,34 @@ def __init__(self, plugin):
104
131
# py.test marker kwarg
105
132
self .option = "hamming_tolerance"
106
133
# value may be overridden by py.test marker kwarg
107
- self .hamming_tolerance = self ._plugin .hamming_tolerance
134
+ self .hamming_tolerance = self ._plugin .hamming_tolerance or DEFAULT_HAMMING_TOLERANCE
108
135
# keep state of hash hamming distance (whole number) result
109
136
self .hamming_distance = None
110
-
111
- def generate_hash (self , buffer ):
112
- buffer .seek (0 )
113
- data = Image .open (buffer )
114
- phash = imagehash .phash (data , hash_size = self .hash_size )
115
- return str (phash )
137
+ # keep state of equivalence result
138
+ self .equivalent = None
116
139
117
140
def equivalent_hash (self , actual , expected , marker = None ):
118
141
if marker :
119
142
self .hamming_tolerance = int (marker .kwargs .get (self .option ))
120
143
actual = imagehash .hex_to_hash (actual )
121
144
expected = imagehash .hex_to_hash (expected )
122
145
self .hamming_distance = actual - expected
123
- return self .hamming_distance <= self .hamming_tolerance
146
+ self .equivalent = self .hamming_distance <= self .hamming_tolerance
147
+ return self .equivalent
148
+
149
+ def generate_hash (self , buffer ):
150
+ buffer .seek (0 )
151
+ data = Image .open (buffer )
152
+ phash = imagehash .phash (data , hash_size = self .hash_size )
153
+ return str (phash )
154
+
155
+ def update_status (self , message ):
156
+ result = str () if message is None else str (message )
157
+ if self .equivalent is False :
158
+ msg = (f"Hash hamming distance of { self .hamming_distance } bits > "
159
+ f"hamming tolerance of { self .hamming_tolerance } bits." )
160
+ result = f"{ message } { msg } " if len (result ) else msg
161
+ return result
124
162
125
163
def update_summary (self , summary ):
126
164
summary ["hamming_distance" ] = self .hamming_distance
@@ -136,16 +174,16 @@ class KernelSHA256(Kernel):
136
174
137
175
name = KERNEL_SHA256
138
176
177
+ def equivalent_hash (self , actual , expected , marker = None ):
178
+ return actual == expected
179
+
139
180
def generate_hash (self , buffer ):
140
181
buffer .seek (0 )
141
182
data = buffer .read ()
142
183
hasher = hashlib .sha256 ()
143
184
hasher .update (data )
144
185
return hasher .hexdigest ()
145
186
146
- def equivalent_hash (self , actual , expected , marker = None ):
147
- return actual == expected
148
-
149
187
150
188
#: Registry of available hashing kernel factories.
151
189
kernel_factory = {
0 commit comments