Skip to content

Commit f17f028

Browse files
committed
initial commit
1 parent e387618 commit f17f028

File tree

3 files changed

+296
-74
lines changed

3 files changed

+296
-74
lines changed

pytest_mpl/kernels.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"""
2+
This module contains the supported hashing kernel implementations.
3+
4+
"""
5+
from PIL import Image
6+
from abc import ABC, abstractmethod
7+
import hashlib
8+
import imagehash
9+
10+
11+
KERNEL_SHA256 = "sha256"
12+
KERNEL_PHASH = "phash"
13+
14+
__all__ = [
15+
"KERNEL_PHASH",
16+
"KERNEL_SHA256",
17+
"KernelPHash",
18+
"KernelSHA256",
19+
"kernel_factory",
20+
]
21+
22+
23+
class Kernel(ABC):
24+
"""
25+
Kernel abstract base class (ABC) which defines a common kernel API.
26+
27+
"""
28+
29+
def __init__(self, plugin):
30+
self._plugin = plugin
31+
32+
@abstractmethod
33+
def generate_hash(self, buffer):
34+
"""
35+
Computes the hash of the image from the in-memory/open byte stream
36+
buffer.
37+
38+
Parameters
39+
----------
40+
buffer : stream
41+
The in-memory/open byte stream of the image.
42+
43+
Returns
44+
-------
45+
str
46+
The string representation (hexdigest) of the image hash.
47+
48+
"""
49+
50+
@abstractmethod
51+
def equivalent_hash(self, actual, expected, marker=None):
52+
"""
53+
Determine whether the kernel considers the provided actual and
54+
expected hashes as similar.
55+
56+
Parameters
57+
----------
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.
65+
66+
Returns
67+
-------
68+
bool
69+
Whether the actual and expected hashes are deemed similar.
70+
71+
"""
72+
73+
def update_summary(self, summary):
74+
"""
75+
Refresh the image comparison summary with relevant kernel entries.
76+
77+
Parameters
78+
----------
79+
summary : dict
80+
81+
Returns
82+
-------
83+
dict
84+
The image comparison summary.
85+
86+
"""
87+
return summary
88+
89+
90+
class KernelPHash(Kernel):
91+
"""
92+
Kernel that calculates a perceptual hash of an image for the
93+
specified hash size (N).
94+
95+
Where the resultant perceptual hash will be composed of N**2 bits.
96+
97+
"""
98+
99+
name = KERNEL_PHASH
100+
101+
def __init__(self, plugin):
102+
super().__init__(plugin)
103+
self.hash_size = self._plugin.hash_size
104+
# py.test marker kwarg
105+
self.option = "hamming_tolerance"
106+
# value may be overridden by py.test marker kwarg
107+
self.hamming_tolerance = self._plugin.hamming_tolerance
108+
# keep state of hash hamming distance (whole number) result
109+
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)
116+
117+
def equivalent_hash(self, actual, expected, marker=None):
118+
if marker:
119+
self.hamming_tolerance = int(marker.kwargs.get(self.option))
120+
actual = imagehash.hex_to_hash(actual)
121+
expected = imagehash.hex_to_hash(expected)
122+
self.hamming_distance = actual - expected
123+
return self.hamming_distance <= self.hamming_tolerance
124+
125+
def update_summary(self, summary):
126+
summary["hamming_distance"] = self.hamming_distance
127+
summary["hamming_tolerance"] = self.hamming_tolerance
128+
return summary
129+
130+
131+
class KernelSHA256(Kernel):
132+
"""
133+
A simple kernel that calculates a 256-bit SHA hash of an image.
134+
135+
"""
136+
137+
name = KERNEL_SHA256
138+
139+
def generate_hash(self, buffer):
140+
buffer.seek(0)
141+
data = buffer.read()
142+
hasher = hashlib.sha256()
143+
hasher.update(data)
144+
return hasher.hexdigest()
145+
146+
def equivalent_hash(self, actual, expected, marker=None):
147+
return actual == expected
148+
149+
150+
#: Registry of available hashing kernel factories.
151+
kernel_factory = {
152+
KernelPHash.name: KernelPHash,
153+
KernelSHA256.name: KernelSHA256,
154+
}

0 commit comments

Comments
 (0)