Skip to content

Commit d467865

Browse files
committed
add phash high frequence factor
1 parent b5aa567 commit d467865

File tree

2 files changed

+68
-32
lines changed

2 files changed

+68
-32
lines changed

pytest_mpl/kernels.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
#: The default imagehash hash size (N), resulting in a hash of N**2 bits.
1515
DEFAULT_HASH_SIZE = 16
1616

17+
#: Level of image detail (high) or structure (low) represented by phash .
18+
DEFAULT_HIGH_FREQUENCY_FACTOR = 4
19+
1720
#: Registered kernel names.
1821
KERNEL_SHA256 = "sha256"
1922
KERNEL_PHASH = "phash"
2023

2124
__all__ = [
2225
"DEFAULT_HAMMING_TOLERANCE",
2326
"DEFAULT_HASH_SIZE",
27+
"DEFAULT_HIGH_FREQUENCY_FACTOR",
2428
"KERNEL_PHASH",
2529
"KERNEL_SHA256",
2630
"KernelPHash",
@@ -36,7 +40,7 @@ class Kernel(ABC):
3640
"""
3741

3842
def __init__(self, plugin):
39-
# Containment (read-only) of the plugin allows the kernel to cherry-pick state that it requires
43+
# Containment of the plugin allows the kernel to cherry-pick required state
4044
self._plugin = plugin
4145

4246
@abstractmethod
@@ -104,6 +108,7 @@ def update_summary(self, summary):
104108
Parameters
105109
----------
106110
summary : dict
111+
Image comparison test report summary.
107112
108113
Returns
109114
-------
@@ -127,15 +132,22 @@ class KernelPHash(Kernel):
127132

128133
def __init__(self, plugin):
129134
super().__init__(plugin)
135+
# keep state of equivalence result
136+
self.equivalent = None
137+
# keep state of hash hamming distance (whole number) result
138+
self.hamming_distance = None
139+
# value may be overridden by py.test marker kwarg
140+
self.hamming_tolerance = (
141+
self._plugin.hamming_tolerance or DEFAULT_HAMMING_TOLERANCE
142+
)
143+
# the hash-size (N) defines the resultant N**2 bits hash size
130144
self.hash_size = self._plugin.hash_size
145+
# the level of image detail or structure represented by perceptual hash
146+
self.high_freq_factor = (
147+
self._plugin.high_freq_factor or DEFAULT_HIGH_FREQUENCY_FACTOR
148+
)
131149
# py.test marker kwarg
132150
self.option = "hamming_tolerance"
133-
# value may be overridden by py.test marker kwarg
134-
self.hamming_tolerance = self._plugin.hamming_tolerance or DEFAULT_HAMMING_TOLERANCE
135-
# keep state of hash hamming distance (whole number) result
136-
self.hamming_distance = None
137-
# keep state of equivalence result
138-
self.equivalent = None
139151

140152
def equivalent_hash(self, actual, expected, marker=None):
141153
if marker:
@@ -149,14 +161,18 @@ def equivalent_hash(self, actual, expected, marker=None):
149161
def generate_hash(self, buffer):
150162
buffer.seek(0)
151163
data = Image.open(buffer)
152-
phash = imagehash.phash(data, hash_size=self.hash_size)
164+
phash = imagehash.phash(
165+
data, hash_size=self.hash_size, highfreq_factor=self.high_freq_factor
166+
)
153167
return str(phash)
154168

155169
def update_status(self, message):
156170
result = str() if message is None else str(message)
157171
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.")
172+
msg = (
173+
f"Hash hamming distance of {self.hamming_distance} bits > "
174+
f"hamming tolerance of {self.hamming_tolerance} bits."
175+
)
160176
result = f"{message} {msg}" if len(result) else msg
161177
return result
162178

pytest_mpl/plugin.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343

4444
import pytest
4545

46-
from .kernels import DEFAULT_HAMMING_TOLERANCE, DEFAULT_HASH_SIZE, KERNEL_SHA256, kernel_factory
46+
from .kernels import (DEFAULT_HAMMING_TOLERANCE, DEFAULT_HASH_SIZE,
47+
DEFAULT_HIGH_FREQUENCY_FACTOR, KERNEL_SHA256, kernel_factory)
4748
from .summary.html import generate_summary_basic_html, generate_summary_html
4849

4950
#: The default matplotlib backend.
@@ -67,6 +68,9 @@
6768
#: Valid formats for generate summary.
6869
SUPPORTED_FORMATS = {'html', 'json', 'basic-html'}
6970

71+
#: Supported INET protocols.
72+
PROTOCOLS = ('http://', 'https://')
73+
7074
#: Template error message for image shape conformance.
7175
TEMPLATE_SHAPE_MISMATCH = """Error! Image dimensions did not match.
7276
@@ -150,22 +154,31 @@ def pytest_addoption(parser):
150154

151155
msg = ('Directory for test results, relative to location where py.test '
152156
'is run.')
153-
group.addoption('--mpl-results-path', help=msg, action='store')
154-
parser.addini('mpl-results-path', help=msg)
157+
option = 'mpl-results-path'
158+
group.addoption(f'--{option}', help=msg, action='store')
159+
parser.addini(option, help=msg)
155160

156161
msg = ('Always compare to baseline images and save result images, even '
157162
'for passing tests. This option is automatically applied when '
158163
'generating a HTML summary.')
159-
group.addoption('--mpl-results-always', help=msg, action='store_true')
160-
parser.addini('mpl-results-always', help=msg)
164+
option = 'mpl-results-always'
165+
group.addoption(f'--{option}', help=msg, action='store_true')
166+
parser.addini(option, help=msg)
161167

162168
msg = 'Use fully qualified test name as the filename.'
163169
parser.addini('mpl-use-full-test-name', help=msg, type='bool')
164170

165171
msg = ('Algorithm to be used for hashing images. Supported kernels are '
166172
'`sha256` (default) and `phash`.')
167-
group.addoption('--mpl-kernel', help=msg, action='store')
168-
parser.addini('mpl-kernel', help=msg)
173+
option = 'mpl-kernel'
174+
group.addoption(f'--{option}', help=msg, action='store')
175+
parser.addini(option, help=msg)
176+
177+
msg = ('Determine the level of image detail (high) or structure (low)'
178+
'represented in the perceptual hash.')
179+
option = 'mpl-high-freq-factor'
180+
group.addoption(f'--{option}', help=msg, action='store')
181+
parser.addini(option, help=msg)
169182

170183
msg = 'The hash size (N) used to generate a N**2 bit image hash.'
171184
group.addoption('--mpl-hash-size', help=msg, action='store')
@@ -211,8 +224,7 @@ def pytest_configure(config):
211224
"--mpl-generate-path is set")
212225
warnings.warn(wmsg)
213226

214-
protocols = ("https", "http")
215-
if baseline_dir is not None and not baseline_dir.startswith(protocols):
227+
if baseline_dir is not None and not baseline_dir.startswith(PROTOCOLS):
216228
baseline_dir = os.path.abspath(baseline_dir)
217229
if generate_dir is not None:
218230
baseline_dir = os.path.abspath(generate_dir)
@@ -249,8 +261,9 @@ def switch_backend(backend):
249261

250262

251263
def close_mpl_figure(fig):
252-
"Close a given matplotlib Figure. Any other type of figure is ignored"
253-
264+
"""
265+
Close a given matplotlib Figure. Any other type of figure is ignored
266+
"""
254267
import matplotlib.pyplot as plt
255268
from matplotlib.figure import Figure
256269

@@ -264,11 +277,12 @@ def close_mpl_figure(fig):
264277

265278
def get_marker(item, marker_name):
266279
if hasattr(item, 'get_closest_marker'):
267-
return item.get_closest_marker(marker_name)
280+
result = item.get_closest_marker(marker_name)
268281
else:
269282
# "item.keywords.get" was deprecated in pytest 3.6
270283
# See https://docs.pytest.org/en/latest/mark.html#updating-code
271-
return item.keywords.get(marker_name)
284+
result = item.keywords.get(marker_name)
285+
return result
272286

273287

274288
def path_is_not_none(apath):
@@ -308,24 +322,30 @@ def __init__(self,
308322
self.results_always = results_always
309323

310324
# Configure hashing kernel options.
311-
option = "mpl-hash-size"
312-
hash_size = int(config.getoption(f"--{option}") or
325+
option = 'mpl-hash-size'
326+
hash_size = int(config.getoption(f'--{option}') or
313327
config.getini(option) or DEFAULT_HASH_SIZE)
314328
self.hash_size = hash_size
315329

316-
option = "mpl-hamming-tolerance"
317-
hamming_tolerance = int(config.getoption(f"--{option}") or
330+
option = 'mpl-hamming-tolerance'
331+
hamming_tolerance = int(config.getoption(f'--{option}') or
318332
config.getini(option) or
319333
DEFAULT_HAMMING_TOLERANCE)
320334
self.hamming_tolerance = hamming_tolerance
321335

336+
option = 'mpl-high-freq-factor'
337+
high_freq_factor = int(config.getoption(f'--{option}') or
338+
config.getini(option) or
339+
DEFAULT_HIGH_FREQUENCY_FACTOR)
340+
self.high_freq_factor = high_freq_factor
341+
322342
# Configure the hashing kernel - must be done *after* kernel options.
323-
option = "mpl-kernel"
324-
kernel = config.getoption(f"--{option}") or config.getini(option)
343+
option = 'mpl-kernel'
344+
kernel = config.getoption(f'--{option}') or config.getini(option)
325345
if kernel:
326346
requested = str(kernel).lower()
327347
if requested not in kernel_factory:
328-
emsg = f"Unrecognised hashing kernel {kernel!r} not supported."
348+
emsg = f'Unrecognised hashing kernel {kernel!r} not supported.'
329349
raise ValueError(emsg)
330350
kernel = requested
331351
else:
@@ -421,7 +441,7 @@ def get_baseline_directory(self, item):
421441
baseline_dir = self.baseline_dir
422442

423443
baseline_remote = (isinstance(baseline_dir, str) and # noqa
424-
baseline_dir.startswith(('http://', 'https://')))
444+
baseline_dir.startswith(PROTOCOLS))
425445
if not baseline_remote:
426446
return Path(item.fspath).parent / baseline_dir
427447

@@ -457,7 +477,7 @@ def obtain_baseline_image(self, item, target_dir):
457477
filename = self.generate_filename(item)
458478
baseline_dir = self.get_baseline_directory(item)
459479
baseline_remote = (isinstance(baseline_dir, str) and # noqa
460-
baseline_dir.startswith(('http://', 'https://')))
480+
baseline_dir.startswith(PROTOCOLS))
461481
if baseline_remote:
462482
# baseline_dir can be a list of URLs when remote, so we have to
463483
# pass base and filename to download

0 commit comments

Comments
 (0)