Skip to content

Commit 911b668

Browse files
authored
Reduce build size by dropping scipy in favor of opencv-contrib-python-headless (#331)
1 parent 9c4021e commit 911b668

File tree

3 files changed

+40
-109
lines changed

3 files changed

+40
-109
lines changed

pyproject.toml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,12 @@ dependencies = [
99
"PyWinCtl >=0.0.42", # py.typed
1010
"keyboard @ git+https://github.com/boppreh/keyboard.git", # Fix install on macos and linux-ci https://github.com/boppreh/keyboard/pull/568
1111
"numpy >=2.3", # Windows ARM64 wheels
12+
"opencv-contrib-python-headless >=4.10", # NumPy 2 support
1213
"packaging >=20.0", # py.typed
1314
# When needed, dev builds can be found at https://download.qt.io/snapshots/ci/pyside/dev?C=M;O=D
1415
"PySide6-Essentials", # Let package resolution find the minimum for wheels (>=6.9.0 on Windows ARM64; <6.8.1 on ubuntu-22.04-arm (glibc 2.39))
1516
"tomli-w >=1.1.0", # Typing fixes
1617

17-
# scipy is used for pHash calculation as a smaller, but still fast implementation.
18-
# However, scipy is not available on all environments yet.
19-
# In those cases, we're falling back to opencv-contrib-python's cv2.img_hash
20-
"opencv-contrib-python-headless; platform_machine == 'ARM64' or platform_machine == 'aarch64'",
21-
"opencv-python-headless; platform_machine != 'ARM64' and platform_machine != 'aarch64'",
22-
"scipy >=1.14.1; platform_machine != 'ARM64' and platform_machine != 'aarch64'", # Python 3.13 support
23-
2418
#
2519
# Build and compile resources
2620
"pyinstaller >=6.14.0", # Mitigate issues with pkg_resources deprecation warning
@@ -59,7 +53,6 @@ dev = [
5953
"ruff >=0.11.13",
6054
#
6155
# Types
62-
"scipy-stubs >=1.14.1.1",
6356
"types-PyAutoGUI",
6457
"types-PyScreeze; sys_platform == 'linux'",
6558
"types-keyboard",

src/compare.py

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from collections.abc import Iterable
22
from math import sqrt
3-
from typing import TYPE_CHECKING
43

54
import cv2
65
import Levenshtein
@@ -22,6 +21,7 @@
2221
RANGES = (0, MAXRANGE, 0, MAXRANGE, 0, MAXRANGE)
2322
MASK_SIZE_MULTIPLIER = ColorChannel.Alpha * MAXBYTE * MAXBYTE
2423
MAX_VALUE = 1.0
24+
CV2_PHASH_SIZE = 8
2525

2626

2727
def compare_histograms(source: MatLike, capture: MatLike, mask: MatLike | None = None):
@@ -90,41 +90,39 @@ def compare_template(source: MatLike, capture: MatLike, mask: MatLike | None = N
9090
return 1 - (min_val / max_error)
9191

9292

93-
try:
94-
from scipy import fft
95-
96-
def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: int = 4):
97-
"""Implementation copied from https://github.com/JohannesBuchner/imagehash/blob/38005924fe9be17cfed145bbc6d83b09ef8be025/imagehash/__init__.py#L260 .""" # noqa: E501
98-
img_size = hash_size * highfreq_factor
99-
image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)
100-
image = cv2.resize(image, (img_size, img_size), interpolation=cv2.INTER_AREA)
101-
dct = fft.dct(fft.dct(image, axis=0), axis=1)
102-
dct_low_frequency = dct[:hash_size, :hash_size]
103-
median = np.median(dct_low_frequency)
104-
return dct_low_frequency > median
105-
106-
def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
107-
source_hash = __cv2_scipy_compute_phash(source, hash_size)
108-
capture_hash = __cv2_scipy_compute_phash(capture, hash_size)
109-
hash_diff = np.count_nonzero(source_hash != capture_hash)
110-
return 1 - (hash_diff / 64.0)
111-
112-
except ModuleNotFoundError:
113-
if not TYPE_CHECKING: # opencv-contrib-python-headless being installed is based on architecture
114-
115-
def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
116-
# OpenCV has its own pHash comparison implementation in `cv2.img_hash`,
117-
# but it requires contrib/extra modules and is inaccurate
118-
# unless we precompute the size with a specific interpolation.
119-
# See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684
120-
#
121-
phash = cv2.img_hash.PHash.create()
122-
source = cv2.resize(source, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
123-
capture = cv2.resize(capture, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
124-
source_hash = phash.compute(source)
125-
capture_hash = phash.compute(capture)
126-
hash_diff = phash.compare(source_hash, capture_hash)
127-
return 1 - (hash_diff / 64.0)
93+
# The old scipy-based implementation.
94+
# Turns out this cuases an extra 25 MB build compared to opencv-contrib-python-headless
95+
# # from scipy import fft
96+
# def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: int = 4):
97+
# """Implementation copied from https://github.com/JohannesBuchner/imagehash/blob/38005924fe9be17cfed145bbc6d83b09ef8be025/imagehash/__init__.py#L260 .""" # noqa: E501
98+
# img_size = hash_size * highfreq_factor
99+
# image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)
100+
# image = cv2.resize(image, (img_size, img_size), interpolation=cv2.INTER_AREA)
101+
# dct = fft.dct(fft.dct(image, axis=0), axis=1)
102+
# dct_low_frequency = dct[:hash_size, :hash_size]
103+
# median = np.median(dct_low_frequency)
104+
# return dct_low_frequency > median
105+
# def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
106+
# source_hash = __cv2_scipy_compute_phash(source, hash_size)
107+
# capture_hash = __cv2_scipy_compute_phash(capture, hash_size)
108+
# hash_diff = np.count_nonzero(source_hash != capture_hash)
109+
# return 1 - (hash_diff / 64.0)
110+
111+
112+
def __cv2_phash(source: MatLike, capture: MatLike):
113+
"""
114+
OpenCV has its own pHash comparison implementation in `cv2.img_hash`,
115+
but is inaccurate unless we precompute the size with a specific interpolation.
116+
117+
See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684
118+
"""
119+
phash = cv2.img_hash.PHash.create()
120+
source = cv2.resize(source, (CV2_PHASH_SIZE, CV2_PHASH_SIZE), interpolation=cv2.INTER_AREA)
121+
capture = cv2.resize(capture, (CV2_PHASH_SIZE, CV2_PHASH_SIZE), interpolation=cv2.INTER_AREA)
122+
source_hash = phash.compute(source)
123+
capture_hash = phash.compute(capture)
124+
hash_diff = phash.compare(source_hash, capture_hash)
125+
return 1 - (hash_diff / 64.0)
128126

129127

130128
def compare_phash(source: MatLike, capture: MatLike, mask: MatLike | None = None):

uv.lock

Lines changed: 5 additions & 65 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)