Skip to content

Commit 7842617

Browse files
committed
Merge branch 'main' of https://github.com/Toufool/AutoSplit into Python-3.14
2 parents 014d7e3 + 911b668 commit 7842617

File tree

5 files changed

+57
-122
lines changed

5 files changed

+57
-122
lines changed

.github/workflows/lint-and-build.yml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ env:
3131
GITHUB_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
3232
GITHUB_EXCLUDE_BUILD_NUMBER: ${{ inputs.excludeBuildNumber }}
3333
UV_NO_SYNC: true # Avoid accidentally pulling in dependency-groups with uv run
34+
# https://github.com/opencv/opencv-python#source-distributions
35+
# Allows building OpenCV on Windows ARM64
36+
# https://github.com/opencv/opencv-python/issues/1092#issuecomment-2862538656
37+
CMAKE_ARGS: "-DBUILD_opencv_dnn=OFF -DENABLE_NEON=OFF"
3438

3539
concurrency:
3640
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -75,14 +79,13 @@ jobs:
7579
fail-fast: false
7680
# Only the Python version we plan on shipping matters.
7781
matrix:
78-
# OpenCV doesn't support windows-11-arm yet https://github.com/opencv/opencv-python/issues/806
79-
os: [windows-latest, ubuntu-22.04, ubuntu-22.04-arm]
82+
os: [windows-latest, windows-11-arm, ubuntu-22.04, ubuntu-22.04-arm]
8083
python-version: ["3.13", "3.14"]
8184
steps:
8285
- uses: actions/checkout@v4
8386
# region pyinstaller/pyinstaller#9012 + astral-sh/uv#12906
8487
- name: Set up Python for PyInstaller tk and ARM64 issue
85-
if: ${{ startsWith(matrix.os, 'ubuntu') }}
88+
if: matrix.os != 'windows-latest'
8689
uses: actions/setup-python@v5
8790
with:
8891
allow-prereleases: true
@@ -91,7 +94,7 @@ jobs:
9194
uses: astral-sh/setup-uv@v6
9295
with:
9396
enable-cache: true
94-
python-version: ${{ !startsWith(matrix.os, 'ubuntu') && matrix.python-version || null }}
97+
python-version: ${{ matrix.os == 'windows-latest' && matrix.python-version || null }}
9598
# endregion
9699
- run: scripts/install.ps1
97100
shell: pwsh
@@ -100,26 +103,27 @@ jobs:
100103
- name: Add empty profile
101104
run: echo "" > dist/settings.toml
102105
- name: Extract AutoSplit version
103-
id: autosplit_version
106+
id: artifact_vars
104107
working-directory: src
105-
run: |
108+
run: | # This also serves as a sanity check for imports
106109
$Env:AUTOSPLIT_VERSION=uv run python -c "import utils; print(utils.AUTOSPLIT_VERSION)"
107110
echo "AUTOSPLIT_VERSION=$Env:AUTOSPLIT_VERSION" >> $Env:GITHUB_OUTPUT
111+
echo "OS=$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)" >> $Env:GITHUB_OUTPUT
108112
shell: pwsh
109113
- name: Upload Build Artifact
110114
uses: actions/upload-artifact@v4
111115
with:
112116
name: >
113-
AutoSplit v${{ steps.autosplit_version.outputs.AUTOSPLIT_VERSION }}
114-
for ${{ matrix.os }} (Python ${{ matrix.python-version }})
117+
AutoSplit v${{ steps.artifact_vars.outputs.AUTOSPLIT_VERSION }}
118+
for ${{ steps.artifact_vars.outputs.OS }} (Python ${{ matrix.python-version }})
115119
path: |
116120
dist/AutoSplit*
117121
dist/settings.toml
118122
if-no-files-found: error
119123
- name: Upload Build logs
120124
uses: actions/upload-artifact@v4
121125
with:
122-
name: Build logs for ${{ matrix.os }} (Python ${{ matrix.python-version }})
126+
name: Build logs for ${{ steps.artifact_vars.outputs.OS }} (Python ${{ matrix.python-version }})
123127
path: |
124128
build/AutoSplit/*.toc
125129
build/AutoSplit/*.txt

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ To understand how to use AutoSplit and how it works in-depth, please read the [t
4444

4545
### Compatibility
4646

47-
- Windows 10 and 11. (x64 only)
47+
- Windows 10 and 11.
4848
- Linux (still in early development)
49-
- Both x64 and ARM64 architectures \* (see [Known Limitations](#known-limitations) for ARM64)
5049
- Should work on Ubuntu 22.04+
5150
- Wayland is not currently supported
5251
- WSL2/WSLg requires an additional Desktop Environment, external X11 server, and/or systemd
52+
- x64 and ARM64 architectures \* (see [Known Limitations](#known-limitations) for ARM64)
5353
- If you'd like to run the project directly in Python from the source code, refer to the [build instructions](/docs/build%20instructions.md).
5454

5555
## Timer Integration
@@ -75,6 +75,7 @@ See the [installation instructions](https://github.com/Toufool/LiveSplit.AutoSpl
7575
- Incompatible with LiveSplitOne on Linux (see <https://github.com/LiveSplit/LiveSplitOne/issues/1025>)
7676
- Antivirus false positives. Please read <https://github.com/pyinstaller/pyinstaller/blob/develop/.github/ISSUE_TEMPLATE/antivirus.md>
7777
- The Perceptual Hash Comparison Method similarity may differ by 3.125% on ARM64. (this will be solved eventually, we have to use a fallback method for now)
78+
- Native ARM64 builds go completely untested. There may be unforseen issues.
7879

7980
## Resources
8081

pyproject.toml

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +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.4.0.dev0", # Python 3.14 support
12+
"opencv-contrib-python-headless >=4.10", # NumPy 2 support
1213
"packaging >=20.0", # py.typed
1314
"tomli-w >=1.1.0", # Typing fixes
1415

1516
# When needed, PySide6 dev builds can be found at https://download.qt.io/snapshots/ci/pyside/dev?C=M;O=D
1617
"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))
17-
18-
# scipy is used for pHash calculation as a smaller, but still fast implementation.
19-
# However, scipy is not available on all environments yet.
20-
# In those cases, we're falling back to opencv-contrib-python's cv2.img_hash
21-
"opencv-contrib-python-headless >=4.10; platform_machine == 'aarch64'", # NumPy 2 support
22-
"opencv-python-headless >=4.10; platform_machine != 'aarch64'", # NumPy 2 support
23-
"scipy >=1.14.1; platform_machine != 'aarch64' and python_version < '3.14'", # Python 3.13 support
24-
2518
#
2619
# Build and compile resources
2720
"pyinstaller >=6.14.0", # Mitigate issues with pkg_resources deprecation warning
@@ -63,7 +56,6 @@ dev = [
6356
"ruff >=0.11.13",
6457
#
6558
# Types
66-
"scipy-stubs >=1.14.1.1",
6759
"types-PyAutoGUI",
6860
"types-PyScreeze; sys_platform == 'linux'",
6961
"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):

0 commit comments

Comments
 (0)