improcv 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- improcv-0.1.0a1/.github/workflows/release.yml +265 -0
- improcv-0.1.0a1/.github/workflows/test.yml +240 -0
- improcv-0.1.0a1/.gitignore +16 -0
- improcv-0.1.0a1/CHANGELOG.md +251 -0
- improcv-0.1.0a1/LICENSE +21 -0
- improcv-0.1.0a1/PKG-INFO +277 -0
- improcv-0.1.0a1/README.md +243 -0
- improcv-0.1.0a1/ROADMAP.md +53 -0
- improcv-0.1.0a1/pyproject.toml +83 -0
- improcv-0.1.0a1/scripts/verify_release_version.py +43 -0
- improcv-0.1.0a1/src/improcv/__init__.py +264 -0
- improcv-0.1.0a1/src/improcv/_compat/__init__.py +0 -0
- improcv-0.1.0a1/src/improcv/_compat/opencv.py +58 -0
- improcv-0.1.0a1/src/improcv/_validation.py +363 -0
- improcv-0.1.0a1/src/improcv/analysis.py +524 -0
- improcv-0.1.0a1/src/improcv/barcode.py +207 -0
- improcv-0.1.0a1/src/improcv/color.py +124 -0
- improcv-0.1.0a1/src/improcv/contours.py +355 -0
- improcv-0.1.0a1/src/improcv/detectors.py +471 -0
- improcv-0.1.0a1/src/improcv/drawing.py +451 -0
- improcv-0.1.0a1/src/improcv/edges.py +210 -0
- improcv-0.1.0a1/src/improcv/features.py +779 -0
- improcv-0.1.0a1/src/improcv/filters.py +276 -0
- improcv-0.1.0a1/src/improcv/hough.py +689 -0
- improcv-0.1.0a1/src/improcv/morphology.py +269 -0
- improcv-0.1.0a1/src/improcv/pixels.py +281 -0
- improcv-0.1.0a1/src/improcv/py.typed +0 -0
- improcv-0.1.0a1/src/improcv/qrcode.py +270 -0
- improcv-0.1.0a1/src/improcv/regions.py +474 -0
- improcv-0.1.0a1/src/improcv/restoration.py +129 -0
- improcv-0.1.0a1/src/improcv/segmentation.py +193 -0
- improcv-0.1.0a1/src/improcv/transforms.py +632 -0
- improcv-0.1.0a1/src/improcv/types.py +49 -0
- improcv-0.1.0a1/src/improcv/visualization/__init__.py +25 -0
- improcv-0.1.0a1/src/improcv/visualization/image.py +196 -0
- improcv-0.1.0a1/tests/conftest.py +17 -0
- improcv-0.1.0a1/tests/test_analysis.py +630 -0
- improcv-0.1.0a1/tests/test_barcode.py +458 -0
- improcv-0.1.0a1/tests/test_color.py +125 -0
- improcv-0.1.0a1/tests/test_compat_opencv.py +72 -0
- improcv-0.1.0a1/tests/test_contours.py +510 -0
- improcv-0.1.0a1/tests/test_detectors.py +681 -0
- improcv-0.1.0a1/tests/test_drawing.py +651 -0
- improcv-0.1.0a1/tests/test_edges.py +177 -0
- improcv-0.1.0a1/tests/test_features.py +1491 -0
- improcv-0.1.0a1/tests/test_filters.py +309 -0
- improcv-0.1.0a1/tests/test_hough.py +829 -0
- improcv-0.1.0a1/tests/test_morphology.py +235 -0
- improcv-0.1.0a1/tests/test_package_import.py +60 -0
- improcv-0.1.0a1/tests/test_pixels.py +399 -0
- improcv-0.1.0a1/tests/test_qrcode.py +456 -0
- improcv-0.1.0a1/tests/test_regions.py +555 -0
- improcv-0.1.0a1/tests/test_restoration.py +263 -0
- improcv-0.1.0a1/tests/test_segmentation.py +261 -0
- improcv-0.1.0a1/tests/test_transforms.py +799 -0
- improcv-0.1.0a1/tests/test_types.py +11 -0
- improcv-0.1.0a1/tests/test_validation.py +547 -0
- improcv-0.1.0a1/tests/test_visualization.py +279 -0
- improcv-0.1.0a1/uv.lock +825 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Triggered only by pushing a tag matching v* (e.g. v0.1.0a1). test.yml also
|
|
4
|
+
# reacts to the same tag push independently -- this workflow does not rely on
|
|
5
|
+
# that parallel run to gate publishing; it re-runs the full test matrix itself
|
|
6
|
+
# (via a reusable workflow call to test.yml) and adds its own release-specific
|
|
7
|
+
# checks (tag/version/HEAD verification, exact-artifact build+install+smoke
|
|
8
|
+
# test) before anything is uploaded or published.
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
tags: ["v*"]
|
|
13
|
+
|
|
14
|
+
# Deny-by-default at the workflow level; each job below grants only the
|
|
15
|
+
# scope it actually needs. In particular, only the two publish jobs get
|
|
16
|
+
# `id-token: write` (required for PyPI Trusted Publishing) -- the job that
|
|
17
|
+
# builds and verifies the artifact never receives OIDC credentials.
|
|
18
|
+
permissions: {}
|
|
19
|
+
|
|
20
|
+
# Two tags pushed close together must not publish concurrently, and an
|
|
21
|
+
# in-progress release must never be cancelled by a later trigger (which could
|
|
22
|
+
# leave TestPyPI and PyPI in different states, or abort mid-publish).
|
|
23
|
+
#
|
|
24
|
+
# `queue: max` (GitHub Actions concurrency, shipped 2026-05-07) lets up to 100
|
|
25
|
+
# runs wait in this group instead of the `queue: single` default, which only
|
|
26
|
+
# holds one pending run and cancels/replaces it if another arrives -- so a
|
|
27
|
+
# third tag pushed while a second is still queued behind a running first
|
|
28
|
+
# would otherwise never run at all. Verified directly against GitHub's own
|
|
29
|
+
# "Using concurrency" docs (fetched 2026-07-23): `queue` accepts `single`
|
|
30
|
+
# (default) or `max` (up to 100 pending). NOTE: `actionlint` v1.7.12 (current
|
|
31
|
+
# latest release as of this writing) does not yet recognize the `queue` key
|
|
32
|
+
# and will report `unexpected key "queue" for "concurrency" section` --
|
|
33
|
+
# this is a known, tracked actionlint gap (rhysd/actionlint#654, #657, #661,
|
|
34
|
+
# all open), not evidence that the key is invalid. Confirmed via GitHub's
|
|
35
|
+
# current documentation, which is authoritative here over a linter that
|
|
36
|
+
# hasn't caught up yet.
|
|
37
|
+
concurrency:
|
|
38
|
+
group: release
|
|
39
|
+
queue: max
|
|
40
|
+
cancel-in-progress: false
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
check-tag:
|
|
44
|
+
name: Verify tag matches origin/main HEAD and pyproject.toml version
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
permissions:
|
|
47
|
+
contents: read
|
|
48
|
+
steps:
|
|
49
|
+
- name: Checkout full history
|
|
50
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
51
|
+
with:
|
|
52
|
+
fetch-depth: 0
|
|
53
|
+
|
|
54
|
+
- name: Set up Python
|
|
55
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.11"
|
|
58
|
+
|
|
59
|
+
- name: Verify the tag points at origin/main's current HEAD
|
|
60
|
+
# Comparing the tag's version to pyproject.toml alone is not enough:
|
|
61
|
+
# 0.1.0a1 has been the version string for many commits, so an old
|
|
62
|
+
# commit would also pass a version-only check. Requiring an exact
|
|
63
|
+
# match against origin/main's current HEAD (not "is an ancestor of
|
|
64
|
+
# main") ensures the release is always cut from the actual latest
|
|
65
|
+
# reviewed state of the default branch.
|
|
66
|
+
run: |
|
|
67
|
+
git fetch origin main --quiet
|
|
68
|
+
tag_commit="$(git rev-parse "${GITHUB_SHA}^{commit}")"
|
|
69
|
+
main_commit="$(git rev-parse origin/main)"
|
|
70
|
+
if [ "$tag_commit" != "$main_commit" ]; then
|
|
71
|
+
echo "::error::Tag commit ($tag_commit) does not match origin/main HEAD ($main_commit). Refusing to release from a commit that is not main's current HEAD."
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
echo "Tag commit matches origin/main HEAD: $main_commit"
|
|
75
|
+
|
|
76
|
+
- name: Verify the tag matches pyproject.toml's version exactly (PEP 440)
|
|
77
|
+
run: python3 scripts/verify_release_version.py
|
|
78
|
+
|
|
79
|
+
test-matrix:
|
|
80
|
+
name: Full Python/OpenCV test matrix (reused from test.yml)
|
|
81
|
+
needs: check-tag
|
|
82
|
+
permissions:
|
|
83
|
+
contents: read
|
|
84
|
+
uses: ./.github/workflows/test.yml
|
|
85
|
+
|
|
86
|
+
build-and-verify:
|
|
87
|
+
name: Build and verify the exact artifacts to be published
|
|
88
|
+
needs: test-matrix
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
# No id-token permission here on purpose: this job builds and smoke-tests
|
|
91
|
+
# the artifact but never publishes, so it never needs OIDC credentials.
|
|
92
|
+
permissions:
|
|
93
|
+
contents: read
|
|
94
|
+
env:
|
|
95
|
+
MPLBACKEND: "Agg"
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
98
|
+
|
|
99
|
+
- name: Install uv
|
|
100
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
101
|
+
with:
|
|
102
|
+
version: "0.11.31"
|
|
103
|
+
python-version: "3.11"
|
|
104
|
+
|
|
105
|
+
- name: Build sdist and wheel
|
|
106
|
+
run: uv build
|
|
107
|
+
|
|
108
|
+
- name: Verify dist/ contains exactly one wheel and one sdist
|
|
109
|
+
run: |
|
|
110
|
+
shopt -s nullglob
|
|
111
|
+
wheels=(dist/*.whl)
|
|
112
|
+
sdists=(dist/*.tar.gz)
|
|
113
|
+
echo "wheels: ${#wheels[@]}, sdists: ${#sdists[@]}"
|
|
114
|
+
ls -la dist/
|
|
115
|
+
if [ "${#wheels[@]}" -ne 1 ] || [ "${#sdists[@]}" -ne 1 ]; then
|
|
116
|
+
echo "::error::Expected exactly one wheel and one sdist in dist/, found ${#wheels[@]} wheel(s) and ${#sdists[@]} sdist(s)."
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
- name: Check package metadata and description rendering (pinned Twine)
|
|
121
|
+
run: uvx "twine==6.2.0" check dist/*
|
|
122
|
+
|
|
123
|
+
- name: Install the wheel into a clean environment (no extras) and smoke-test
|
|
124
|
+
# uv venv/uv pip --python pin the interpreter explicitly: setup-uv's
|
|
125
|
+
# own python-version input sets UV_PYTHON for uv's *own* commands,
|
|
126
|
+
# but does not guarantee a plain `python`/`python3` on PATH resolves
|
|
127
|
+
# to that same interpreter.
|
|
128
|
+
run: |
|
|
129
|
+
uv venv --python 3.11 /tmp/smoke-wheel
|
|
130
|
+
uv pip install --python /tmp/smoke-wheel/bin/python dist/*.whl "opencv-python-headless>=4.9,<6"
|
|
131
|
+
/tmp/smoke-wheel/bin/python -c "
|
|
132
|
+
import os
|
|
133
|
+
import sys
|
|
134
|
+
|
|
135
|
+
import numpy as np
|
|
136
|
+
import improcv as im
|
|
137
|
+
|
|
138
|
+
expected_version = os.environ['GITHUB_REF_NAME']
|
|
139
|
+
if expected_version.startswith('v'):
|
|
140
|
+
expected_version = expected_version[1:]
|
|
141
|
+
assert im.__version__ == expected_version, (im.__version__, expected_version)
|
|
142
|
+
|
|
143
|
+
image = np.zeros((20, 20, 3), dtype=np.uint8)
|
|
144
|
+
result = im.resize(image, width=10)
|
|
145
|
+
assert result.shape == (10, 10, 3), result.shape
|
|
146
|
+
assert 'matplotlib' not in sys.modules, 'import improcv must never import matplotlib'
|
|
147
|
+
print('smoke test OK (wheel, no extras):', im.__version__, result.shape)
|
|
148
|
+
"
|
|
149
|
+
|
|
150
|
+
- name: Verify improcv.visualization fails clearly without the viz extra (wheel)
|
|
151
|
+
run: |
|
|
152
|
+
/tmp/smoke-wheel/bin/python -c "
|
|
153
|
+
try:
|
|
154
|
+
import improcv.visualization
|
|
155
|
+
raise SystemExit('expected ImportError, none was raised')
|
|
156
|
+
except ImportError as exc:
|
|
157
|
+
assert 'improcv[viz]' in str(exc), str(exc)
|
|
158
|
+
print('smoke test OK: clear ImportError without the viz extra (wheel)')
|
|
159
|
+
"
|
|
160
|
+
|
|
161
|
+
- name: Install the wheel with the viz extra into a separate clean environment and smoke-test
|
|
162
|
+
run: |
|
|
163
|
+
uv venv --python 3.11 /tmp/smoke-wheel-viz
|
|
164
|
+
uv pip install --python /tmp/smoke-wheel-viz/bin/python "$(echo dist/*.whl)[viz]" "opencv-python-headless>=4.9,<6"
|
|
165
|
+
/tmp/smoke-wheel-viz/bin/python -c "
|
|
166
|
+
import os
|
|
167
|
+
|
|
168
|
+
import numpy as np
|
|
169
|
+
import improcv as im
|
|
170
|
+
import improcv.visualization as viz
|
|
171
|
+
|
|
172
|
+
expected_version = os.environ['GITHUB_REF_NAME']
|
|
173
|
+
if expected_version.startswith('v'):
|
|
174
|
+
expected_version = expected_version[1:]
|
|
175
|
+
assert im.__version__ == expected_version, (im.__version__, expected_version)
|
|
176
|
+
|
|
177
|
+
image = np.zeros((20, 20, 3), dtype=np.uint8)
|
|
178
|
+
viz.show_image(image)
|
|
179
|
+
viz.plot_histogram(image)
|
|
180
|
+
print('smoke test OK: show_image and plot_histogram both ran under Agg (wheel+viz)', im.__version__)
|
|
181
|
+
"
|
|
182
|
+
|
|
183
|
+
- name: Install the sdist into a separate clean environment and smoke-test
|
|
184
|
+
run: |
|
|
185
|
+
uv venv --python 3.11 /tmp/smoke-sdist
|
|
186
|
+
uv pip install --python /tmp/smoke-sdist/bin/python dist/*.tar.gz "opencv-python-headless>=4.9,<6"
|
|
187
|
+
/tmp/smoke-sdist/bin/python -c "
|
|
188
|
+
import os
|
|
189
|
+
|
|
190
|
+
import numpy as np
|
|
191
|
+
import improcv as im
|
|
192
|
+
|
|
193
|
+
expected_version = os.environ['GITHUB_REF_NAME']
|
|
194
|
+
if expected_version.startswith('v'):
|
|
195
|
+
expected_version = expected_version[1:]
|
|
196
|
+
assert im.__version__ == expected_version, (im.__version__, expected_version)
|
|
197
|
+
|
|
198
|
+
image = np.zeros((20, 20, 3), dtype=np.uint8)
|
|
199
|
+
result = im.resize(image, width=10)
|
|
200
|
+
assert result.shape == (10, 10, 3), result.shape
|
|
201
|
+
print('smoke test OK (sdist):', im.__version__, result.shape)
|
|
202
|
+
"
|
|
203
|
+
|
|
204
|
+
- name: Upload the verified dist/ as the release artifact
|
|
205
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
206
|
+
with:
|
|
207
|
+
name: release-dist
|
|
208
|
+
path: dist/
|
|
209
|
+
# Short, but long enough to survive the manual TestPyPI
|
|
210
|
+
# verification + `pypi` environment approval step in between
|
|
211
|
+
# publish-testpypi and publish-pypi -- 1 day was too tight if that
|
|
212
|
+
# approval doesn't happen immediately.
|
|
213
|
+
retention-days: 7
|
|
214
|
+
if-no-files-found: error
|
|
215
|
+
|
|
216
|
+
publish-testpypi:
|
|
217
|
+
name: Publish to TestPyPI
|
|
218
|
+
needs: build-and-verify
|
|
219
|
+
runs-on: ubuntu-latest
|
|
220
|
+
environment: testpypi
|
|
221
|
+
permissions:
|
|
222
|
+
id-token: write
|
|
223
|
+
steps:
|
|
224
|
+
- name: Download the verified release artifact
|
|
225
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
226
|
+
with:
|
|
227
|
+
name: release-dist
|
|
228
|
+
path: dist/
|
|
229
|
+
|
|
230
|
+
- name: Publish to TestPyPI
|
|
231
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
232
|
+
with:
|
|
233
|
+
repository-url: https://test.pypi.org/legacy/
|
|
234
|
+
packages-dir: dist/
|
|
235
|
+
# TestPyPI-only: lets a re-run after a partial upload or a
|
|
236
|
+
# transient TestPyPI outage succeed safely instead of erroring on
|
|
237
|
+
# a version that's already (partially) there. Deliberately not
|
|
238
|
+
# set on publish-pypi below -- a duplicate there should still
|
|
239
|
+
# fail loudly.
|
|
240
|
+
skip-existing: true
|
|
241
|
+
|
|
242
|
+
publish-pypi:
|
|
243
|
+
name: Publish to PyPI
|
|
244
|
+
# Order is TestPyPI -> PyPI: the real-PyPI job only starts after the
|
|
245
|
+
# TestPyPI publish (the dry run of the mechanism itself) has succeeded.
|
|
246
|
+
needs: publish-testpypi
|
|
247
|
+
runs-on: ubuntu-latest
|
|
248
|
+
# Production PyPI stays behind the `pypi` GitHub Environment, which
|
|
249
|
+
# should be configured with a required reviewer (see the design doc's
|
|
250
|
+
# manual-steps section) -- this job pauses for manual approval before
|
|
251
|
+
# running.
|
|
252
|
+
environment: pypi
|
|
253
|
+
permissions:
|
|
254
|
+
id-token: write
|
|
255
|
+
steps:
|
|
256
|
+
- name: Download the verified release artifact
|
|
257
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
258
|
+
with:
|
|
259
|
+
name: release-dist
|
|
260
|
+
path: dist/
|
|
261
|
+
|
|
262
|
+
- name: Publish to PyPI
|
|
263
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
264
|
+
with:
|
|
265
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: ["main"]
|
|
9
|
+
# Lets release.yml invoke this workflow's own `test`/`lowest-deps` matrix as
|
|
10
|
+
# part of the release pipeline, so a release can't be published without the
|
|
11
|
+
# full test matrix passing inside that same run -- rather than only relying
|
|
12
|
+
# on this workflow's own independent, parallel run against the same tag
|
|
13
|
+
# push, whose failure would not block publishing.
|
|
14
|
+
workflow_call:
|
|
15
|
+
|
|
16
|
+
# Deny-by-default beyond read access to repo contents: standalone runs of
|
|
17
|
+
# this workflow (push/pull_request) only ever need to check out the code,
|
|
18
|
+
# and the reusable-workflow-call path from release.yml explicitly caps this
|
|
19
|
+
# further via its own `permissions: contents: read` on the calling job.
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
test:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
strategy:
|
|
27
|
+
matrix:
|
|
28
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
29
|
+
opencv: ["4", "5"]
|
|
30
|
+
include:
|
|
31
|
+
- opencv: "4"
|
|
32
|
+
opencv-spec: "opencv-python-headless>=4.9,<5"
|
|
33
|
+
- opencv: "5"
|
|
34
|
+
opencv-spec: "opencv-python-headless>=5,<6"
|
|
35
|
+
env:
|
|
36
|
+
OMP_NUM_THREADS: "1"
|
|
37
|
+
OPENBLAS_NUM_THREADS: "1"
|
|
38
|
+
MKL_NUM_THREADS: "1"
|
|
39
|
+
MPLBACKEND: "Agg"
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
42
|
+
|
|
43
|
+
- name: Install uv
|
|
44
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
45
|
+
with:
|
|
46
|
+
version: "0.11.31"
|
|
47
|
+
python-version: ${{ matrix.python-version }}
|
|
48
|
+
|
|
49
|
+
- name: Install dependencies
|
|
50
|
+
run: uv sync --locked --all-groups
|
|
51
|
+
|
|
52
|
+
- name: Install pinned OpenCV major version (${{ matrix.opencv }}.x)
|
|
53
|
+
run: uv pip install "${{ matrix.opencv-spec }}"
|
|
54
|
+
|
|
55
|
+
# --no-sync everywhere below: plain `uv run` re-syncs the environment
|
|
56
|
+
# against pyproject/uv.lock first, which would silently reinstall the
|
|
57
|
+
# locked (latest, 5.x) opencv-python-headless and undo the pinned
|
|
58
|
+
# override above — verified this actually happens before adding
|
|
59
|
+
# --no-sync, it's not a hypothetical concern.
|
|
60
|
+
- name: Lint
|
|
61
|
+
run: uv run --no-sync ruff check .
|
|
62
|
+
|
|
63
|
+
- name: Check formatting
|
|
64
|
+
run: uv run --no-sync ruff format --check .
|
|
65
|
+
|
|
66
|
+
- name: Type check
|
|
67
|
+
run: uv run --no-sync pyright
|
|
68
|
+
|
|
69
|
+
- name: Test
|
|
70
|
+
run: uv run --no-sync pytest
|
|
71
|
+
|
|
72
|
+
# Representative, non-Linux platform coverage (brief §8 requires "at least
|
|
73
|
+
# representative Windows and macOS configurations") -- deliberately just
|
|
74
|
+
# one job per platform, not the full Python x OpenCV matrix duplicated
|
|
75
|
+
# twice more: the goal is catching platform-specific problems (path
|
|
76
|
+
# handling, OpenCV wheel behavior, thread-library differences), not
|
|
77
|
+
# tripling CI runtime. Each of the two platforms takes one end of the
|
|
78
|
+
# supported Python range and the opposite OpenCV major line from the
|
|
79
|
+
# other, so between them all four "extremes" (oldest/newest Python,
|
|
80
|
+
# both OpenCV lines) get at least one non-Linux run. Verified directly
|
|
81
|
+
# (PyPI file listing for the latest opencv-python-headless release on
|
|
82
|
+
# both the 4.x and 5.x lines) that both ship `cp37-abi3` wheels for
|
|
83
|
+
# `win_amd64` and both `macosx_*_arm64`/`macosx_*_x86_64` -- so wheel
|
|
84
|
+
# availability isn't a constraint on either platform or Python choice.
|
|
85
|
+
# No lint/type-check here: both are platform-independent by nature and
|
|
86
|
+
# already covered above -- only `pytest` runs, which is the check that
|
|
87
|
+
# can actually differ by platform.
|
|
88
|
+
test-windows:
|
|
89
|
+
name: Representative test (windows-latest, Python 3.11, OpenCV 4.x)
|
|
90
|
+
runs-on: windows-latest
|
|
91
|
+
env:
|
|
92
|
+
MPLBACKEND: "Agg"
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
95
|
+
|
|
96
|
+
- name: Install uv
|
|
97
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
98
|
+
with:
|
|
99
|
+
version: "0.11.31"
|
|
100
|
+
python-version: "3.11"
|
|
101
|
+
|
|
102
|
+
- name: Install dependencies
|
|
103
|
+
run: uv sync --locked --all-groups
|
|
104
|
+
|
|
105
|
+
- name: Install pinned OpenCV major version (4.x)
|
|
106
|
+
run: uv pip install "opencv-python-headless>=4.9,<5"
|
|
107
|
+
|
|
108
|
+
- name: Test
|
|
109
|
+
run: uv run --no-sync pytest
|
|
110
|
+
|
|
111
|
+
test-macos:
|
|
112
|
+
name: Representative test (macos-latest, Python 3.14, OpenCV 5.x)
|
|
113
|
+
runs-on: macos-latest
|
|
114
|
+
env:
|
|
115
|
+
MPLBACKEND: "Agg"
|
|
116
|
+
steps:
|
|
117
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
118
|
+
|
|
119
|
+
- name: Install uv
|
|
120
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
121
|
+
with:
|
|
122
|
+
version: "0.11.31"
|
|
123
|
+
python-version: "3.14"
|
|
124
|
+
|
|
125
|
+
- name: Install dependencies
|
|
126
|
+
run: uv sync --locked --all-groups
|
|
127
|
+
|
|
128
|
+
- name: Install pinned OpenCV major version (5.x)
|
|
129
|
+
run: uv pip install "opencv-python-headless>=5,<6"
|
|
130
|
+
|
|
131
|
+
- name: Test
|
|
132
|
+
run: uv run --no-sync pytest
|
|
133
|
+
|
|
134
|
+
lowest-deps:
|
|
135
|
+
# Everything above always resolves via uv.lock, which picks the
|
|
136
|
+
# newest version satisfying each constraint — so numpy>=1.24,
|
|
137
|
+
# opencv-python>=4.9, and matplotlib>=3.8 in pyproject.toml were never
|
|
138
|
+
# actually tested at those floors. This job pins the exact declared
|
|
139
|
+
# minimums (on the oldest supported Python, since numpy 1.24 has no
|
|
140
|
+
# wheels for 3.12+) to catch a floor that's declared but doesn't
|
|
141
|
+
# actually work.
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
env:
|
|
144
|
+
MPLBACKEND: "Agg"
|
|
145
|
+
steps:
|
|
146
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
147
|
+
|
|
148
|
+
- name: Install uv
|
|
149
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
150
|
+
with:
|
|
151
|
+
version: "0.11.31"
|
|
152
|
+
python-version: "3.11"
|
|
153
|
+
|
|
154
|
+
- name: Create a venv and install the declared minimum versions
|
|
155
|
+
# Exact floors, not `.*` wildcards: `numpy==1.24.*`/`matplotlib==3.8.*`
|
|
156
|
+
# resolve to the newest patch on that branch, not the actual declared
|
|
157
|
+
# minimum -- verified this job was never really testing 1.24.0/3.8.0.
|
|
158
|
+
# pytest is pinned to the version currently locked in uv.lock so this
|
|
159
|
+
# release-critical job doesn't depend on an arbitrary future pytest.
|
|
160
|
+
run: |
|
|
161
|
+
uv venv --python 3.11
|
|
162
|
+
uv pip install "numpy==1.24.0" "opencv-python-headless==4.9.0.80" "matplotlib==3.8.0" "pytest==9.1.1"
|
|
163
|
+
uv pip install -e . --no-deps
|
|
164
|
+
|
|
165
|
+
- name: Test against the lowest declared dependency versions
|
|
166
|
+
run: uv run --no-sync pytest
|
|
167
|
+
|
|
168
|
+
build:
|
|
169
|
+
runs-on: ubuntu-latest
|
|
170
|
+
steps:
|
|
171
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
172
|
+
|
|
173
|
+
- name: Install uv
|
|
174
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
175
|
+
with:
|
|
176
|
+
version: "0.11.31"
|
|
177
|
+
python-version: "3.11"
|
|
178
|
+
|
|
179
|
+
- name: Build sdist and wheel
|
|
180
|
+
run: uv build
|
|
181
|
+
|
|
182
|
+
- name: Check package metadata and description rendering
|
|
183
|
+
run: uvx "twine==6.2.0" check dist/*
|
|
184
|
+
|
|
185
|
+
- name: Install the built wheel into a clean environment (no viz extra)
|
|
186
|
+
# uv venv/uv pip --python pin the interpreter explicitly: setup-uv's
|
|
187
|
+
# own python-version input sets UV_PYTHON for uv's *own* commands,
|
|
188
|
+
# but does not guarantee a plain `python`/`python3` on PATH resolves
|
|
189
|
+
# to that same interpreter.
|
|
190
|
+
run: |
|
|
191
|
+
uv venv --python 3.11 /tmp/smoke-env
|
|
192
|
+
uv pip install --python /tmp/smoke-env/bin/python dist/*.whl "opencv-python-headless>=4.9,<6"
|
|
193
|
+
|
|
194
|
+
- name: Smoke-test the installed wheel (not the source tree)
|
|
195
|
+
run: |
|
|
196
|
+
cd /tmp
|
|
197
|
+
/tmp/smoke-env/bin/python -c "
|
|
198
|
+
import sys
|
|
199
|
+
|
|
200
|
+
import numpy as np
|
|
201
|
+
import improcv as im
|
|
202
|
+
|
|
203
|
+
image = np.zeros((20, 20, 3), dtype=np.uint8)
|
|
204
|
+
result = im.resize(image, width=10)
|
|
205
|
+
assert result.shape == (10, 10, 3), result.shape
|
|
206
|
+
assert 'matplotlib' not in sys.modules, 'import improcv must never import matplotlib'
|
|
207
|
+
print('smoke test OK:', im.__name__, result.shape)
|
|
208
|
+
"
|
|
209
|
+
|
|
210
|
+
- name: Smoke-test that improcv.visualization fails clearly without the viz extra
|
|
211
|
+
run: |
|
|
212
|
+
cd /tmp
|
|
213
|
+
/tmp/smoke-env/bin/python -c "
|
|
214
|
+
try:
|
|
215
|
+
import improcv.visualization
|
|
216
|
+
raise SystemExit('expected ImportError, none was raised')
|
|
217
|
+
except ImportError as exc:
|
|
218
|
+
assert 'improcv[viz]' in str(exc), str(exc)
|
|
219
|
+
print('smoke test OK: clear ImportError without the viz extra')
|
|
220
|
+
"
|
|
221
|
+
|
|
222
|
+
- name: Install the built wheel with the viz extra into a separate clean environment
|
|
223
|
+
run: |
|
|
224
|
+
uv venv --python 3.11 /tmp/smoke-env-viz
|
|
225
|
+
uv pip install --python /tmp/smoke-env-viz/bin/python "$(echo dist/*.whl)[viz]" "opencv-python-headless>=4.9,<6"
|
|
226
|
+
|
|
227
|
+
- name: Smoke-test improcv.visualization with the viz extra installed
|
|
228
|
+
env:
|
|
229
|
+
MPLBACKEND: "Agg"
|
|
230
|
+
run: |
|
|
231
|
+
cd /tmp
|
|
232
|
+
/tmp/smoke-env-viz/bin/python -c "
|
|
233
|
+
import numpy as np
|
|
234
|
+
import improcv.visualization as viz
|
|
235
|
+
|
|
236
|
+
image = np.zeros((20, 20, 3), dtype=np.uint8)
|
|
237
|
+
ax = viz.show_image(image)
|
|
238
|
+
ax2 = viz.plot_histogram(image)
|
|
239
|
+
print('smoke test OK: show_image and plot_histogram both ran under Agg')
|
|
240
|
+
"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
|
|
10
|
+
# Private planning/working docs — internal agreements and process notes,
|
|
11
|
+
# not meant to be visible in the public repo. Public docs (once we have
|
|
12
|
+
# them) live elsewhere under docs/, so only this subfolder is ignored.
|
|
13
|
+
CLAUDE.md
|
|
14
|
+
IMPROCV_PROJECT_BRIEF.md
|
|
15
|
+
.claude/
|
|
16
|
+
docs/superpowers/
|