PyPatchMatch 2.0.0rc5__tar.gz → 2.0.1__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.
- pypatchmatch-2.0.1/CLAUDE.md +155 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/PKG-INFO +18 -11
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/README.md +13 -6
- pypatchmatch-2.0.1/SECURITY.md +20 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/_version.py +3 -3
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/inpaint.cpp +12 -1
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/pyproject.toml +27 -9
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/scripts/build_opencv.py +25 -10
- pypatchmatch-2.0.1/scripts/render_readme_example.py +36 -0
- pypatchmatch-2.0.1/tests/conftest.py +6 -0
- pypatchmatch-2.0.1/tests/test_properties.py +159 -0
- pypatchmatch-2.0.0rc5/CLAUDE.md +0 -436
- pypatchmatch-2.0.0rc5/tests/test_properties.py +0 -95
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/CMakeLists.txt +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/LICENSE +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/examples/images/forest_pruned.bmp +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/__init__.py +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/_lib.py +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/inpaint.h +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/masked_image.cpp +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/masked_image.h +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/nnf.cpp +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/nnf.h +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/pyinterface.cpp +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/csrc/pyinterface.h +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/patch_match.py +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/patchmatch/py.typed +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/scripts/evaluate_inpainting.py +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/tests/test_e2e.py +0 -0
- {pypatchmatch-2.0.0rc5 → pypatchmatch-2.0.1}/tests/test_patch_match.py +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in
|
|
4
|
+
this repository. The details of the native code, the C interface, packaging and CI are
|
|
5
|
+
path-scoped rules in `.claude/rules/`, which load when a matching file is read or
|
|
6
|
+
edited; read the rule before changing such a file through Bash. The sanitizer, C++
|
|
7
|
+
coverage and fill-quality runs are skills in `.claude/skills/`.
|
|
8
|
+
|
|
9
|
+
## Project
|
|
10
|
+
|
|
11
|
+
PyPatchMatch implements PatchMatch based image inpainting (Barnes et al., SIGGRAPH 2009):
|
|
12
|
+
a C++17 library on top of OpenCV core, and a thin Python package that loads it through
|
|
13
|
+
`ctypes`. It is published on PyPI as `PyPatchMatch` (import name `patchmatch`) and used
|
|
14
|
+
by InvokeAI. This repository (`mauwii/PyPatchMatch`) is the maintained fork of
|
|
15
|
+
`invoke-ai/PyPatchMatch`, which derives from `vacancy/PyPatchMatch` and
|
|
16
|
+
`younesse-cv/PatchMatch`.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
The project is managed with uv (`.python-version` pins 3.12 for the local venv). Building
|
|
21
|
+
needs a C++17 compiler, CMake >= 3.21 and an OpenCV that `find_package(OpenCV)` finds:
|
|
22
|
+
Homebrew `opencv`, `libopencv-dev`, or an installation prefix in `OpenCV_ROOT`.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
uv sync # .venv, editable install, compiles the library
|
|
26
|
+
uv run pre-commit install
|
|
27
|
+
uv run pytest # all tests, with the coverage gate
|
|
28
|
+
uv run pytest -m "not e2e" # fast unit tests only
|
|
29
|
+
uv run pytest -m e2e --no-cov # end-to-end tests only
|
|
30
|
+
uv run pytest tests/test_patch_match.py::test_inpaint_global_mask --no-cov
|
|
31
|
+
uv run pytest -k regularity --no-cov
|
|
32
|
+
uv run mypy # strict, also a pre-commit hook
|
|
33
|
+
uv run pre-commit run --all-files # what the CI lint job runs
|
|
34
|
+
uv run ruff check --fix . && uv run ruff format .
|
|
35
|
+
uv build # sdist and wheel into dist/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Pass `--no-cov` for any partial run: `addopts` always enables coverage, and
|
|
39
|
+
`fail_under = 95` (branch coverage, subprocesses included through
|
|
40
|
+
`patch = ["subprocess"]`) fails the run otherwise.
|
|
41
|
+
- Use `uv run pytest`, not `python -m pytest`. The latter puts the repository root on
|
|
42
|
+
`sys.path`, which imports the source tree `patchmatch/` without the compiled library;
|
|
43
|
+
the symptoms are the warning `patchmatch failed to load: libpatchmatch.* not found`
|
|
44
|
+
and `RuntimeError: the patchmatch native library is not available` in every test.
|
|
45
|
+
- `--cov=patchmatch` measures the source tree, so coverage only works with the editable
|
|
46
|
+
install that `uv sync` creates. With a non-editable install (`uv sync --no-editable`,
|
|
47
|
+
`uv pip install .`) it reports 0 % and the gate fails.
|
|
48
|
+
- `uv sync` rebuilds the library only when a `tool.uv.cache-keys` entry changes
|
|
49
|
+
(`CMakeLists.txt`, `patchmatch/csrc/*`, `pyproject.toml`, git commit or tags). Force it
|
|
50
|
+
with `uv sync --reinstall-package pypatchmatch`.
|
|
51
|
+
- CI builds the library with warnings as errors. Locally:
|
|
52
|
+
`SKBUILD_CMAKE_DEFINE=CMAKE_COMPILE_WARNING_AS_ERROR=ON uv sync --reinstall-package pypatchmatch`.
|
|
53
|
+
|
|
54
|
+
Build against the same static, core-only OpenCV as the release wheels:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
export OpenCV_ROOT=/tmp/opencv
|
|
58
|
+
uv run --no-project python scripts/build_opencv.py # pinned version and SHA-256
|
|
59
|
+
uv sync --reinstall-package pypatchmatch
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Other entry points:
|
|
63
|
+
|
|
64
|
+
- `examples/cpp_example_run.sh` builds the library and `examples/cpp_example.cpp` with
|
|
65
|
+
CMake into `build/cpp` and runs it. It needs OpenCV `imgcodecs`, so a full OpenCV, not
|
|
66
|
+
the core-only build of the script.
|
|
67
|
+
- `uv run python examples/py_example.py` (and `py_example_global_mask.py`) write
|
|
68
|
+
`examples/images/forest_recovered.bmp`, which is git-ignored.
|
|
69
|
+
- `uv run python scripts/render_readme_example.py` runs `py_example.py` and renders the
|
|
70
|
+
picture at the top of the README, `examples/images/readme_example.jpg`; rerun it and
|
|
71
|
+
commit the picture after changes that affect the fills.
|
|
72
|
+
- CMake options: `PATCHMATCH_BUILD_EXAMPLES` (OFF), `PATCHMATCH_FAST_MATH` (ON,
|
|
73
|
+
`-ffast-math` or `/fp:fast`), `PATCHMATCH_SANITIZE` (OFF, ASan and UBSan with
|
|
74
|
+
`float-cast-overflow`, every finding aborts; turns off fast math, which lets the
|
|
75
|
+
compiler drop checks; GCC and Clang only), `PATCHMATCH_COVERAGE` (OFF, gcov counters
|
|
76
|
+
at `-Og`; GCC and Clang only).
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
### Layers, in the call order of `patchmatch.inpaint`
|
|
81
|
+
|
|
82
|
+
1. `patchmatch/__init__.py` re-exports the public API of `patch_match.py` and
|
|
83
|
+
`__version__` from the generated `_version.py`. `test_top_level_exports` checks that
|
|
84
|
+
every name in `patch_match.__all__` is re-exported.
|
|
85
|
+
2. `patchmatch/patch_match.py` is the public API. It validates and canonicalizes the
|
|
86
|
+
inputs (`_prepare_inputs`, `_check_patch_size`, the `ijmap` and `guide_weight`
|
|
87
|
+
checks), picks the entry point (`PM_inpaint`, `PM_inpaint2` with a global mask, and
|
|
88
|
+
their `_regularity` variants) and runs it through `_call`. It loads the library at
|
|
89
|
+
import time; on `OSError` it logs a warning and sets `patchmatch_available = False`,
|
|
90
|
+
and every function then raises `RuntimeError` through `_get_lib()`.
|
|
91
|
+
3. `patchmatch/_lib.py` is the ctypes layer: the `CShapeT`/`CMatT` structures, the dtype
|
|
92
|
+
table, `np_to_pymat`/`pymat_to_np`, `find_library` (looks for `libpatchmatch.so`,
|
|
93
|
+
`libpatchmatch.dylib` or `patchmatch.dll` in every directory of `patchmatch.__path__`)
|
|
94
|
+
and `load_library` (argtypes and restype of every `PM_*` function).
|
|
95
|
+
4. `patchmatch/csrc/pyinterface.{h,cpp}` is the `extern "C"` API: the `PM_*` functions,
|
|
96
|
+
the conversion between `PM_mat_t` and `cv::Mat`, the global settings and the error
|
|
97
|
+
reporting.
|
|
98
|
+
5. `csrc/inpaint.{h,cpp}`: `Inpainting`, the image pyramid and the EM loop.
|
|
99
|
+
6. `csrc/nnf.{h,cpp}`: `NearestNeighborField` (propagation and random search) and the
|
|
100
|
+
distance metrics `PatchSSDDistanceMetric` and `RegularityGuidedPatchDistanceMetricV2`.
|
|
101
|
+
7. `csrc/masked_image.{h,cpp}`: `MaskedImage`, an image with its hole mask and optional
|
|
102
|
+
global mask, down- and upsampling, gradients.
|
|
103
|
+
|
|
104
|
+
The C++ headers double as the C++ API: `examples/cpp_example.cpp` uses `Inpainting` and
|
|
105
|
+
`PatchSSDDistanceMetric` directly.
|
|
106
|
+
|
|
107
|
+
### ctypes instead of a CPython extension
|
|
108
|
+
|
|
109
|
+
The library does not use the CPython ABI, so the wheels are tagged
|
|
110
|
+
`py3-none-<platform>` (`wheel.py-api = "py3"`): one wheel per platform serves all Python
|
|
111
|
+
versions, and cibuildwheel only builds `cp311-*`. Keep `csrc` free of `Python.h`,
|
|
112
|
+
pybind11 or nanobind. The C++ sources are excluded from the wheel (`wheel.exclude`);
|
|
113
|
+
CMake installs the shared library into the package directory.
|
|
114
|
+
|
|
115
|
+
## Conventions
|
|
116
|
+
|
|
117
|
+
- Python >= 3.11: keep `from __future__ import annotations`. Ruff targets py311 with the
|
|
118
|
+
rules B, C4, E, F, I, RUF, SIM, UP and W, and formats at 88 columns. `patchmatch/csrc`
|
|
119
|
+
is excluded from ruff. C++ is formatted by clang-format (Microsoft style, 120 columns,
|
|
120
|
+
includes grouped as own header, `<...>`, `"..."`); naming is not checked: `m_`
|
|
121
|
+
members, `k` constants. Commit a reformat separately and add its hash to
|
|
122
|
+
`.git-blame-ignore-revs`.
|
|
123
|
+
- The package ships `py.typed`; public functions are fully annotated. mypy checks
|
|
124
|
+
`patchmatch` and `tests` in strict mode (`[tool.mypy]`); the tests have to use the
|
|
125
|
+
API correctly but need no annotations of their own. A deliberately wrong argument
|
|
126
|
+
goes through a test parameter instead of a `# type: ignore`.
|
|
127
|
+
- Backwards compatibility: `from patchmatch import patch_match` and
|
|
128
|
+
`patch_match.CShapeT`/`CMatT` keep working (tested).
|
|
129
|
+
- Comments explain why: which crash a check prevents, why a construct is avoided.
|
|
130
|
+
- No `NOSONAR` markers: change the code so that the SonarCloud rule is satisfied, and ask
|
|
131
|
+
when that seems impossible. The only exception is `nnf.cpp`: cpp:S2245 flags every
|
|
132
|
+
declaration of `std::mt19937`, which only drives the randomized search.
|
|
133
|
+
- Tests check invariants instead of golden images: holes filled, all other pixels
|
|
134
|
+
unchanged, determinism with `set_random_seed`, fills that blend in with their
|
|
135
|
+
surroundings. `test_patch_match.py` and `test_e2e.py` seed through an autouse fixture.
|
|
136
|
+
Unit tests use a small synthetic image; the `e2e` tests use
|
|
137
|
+
`examples/images/forest_pruned.bmp` and mirror the workflows of the README and the
|
|
138
|
+
examples. `test_properties.py` checks the invariants on random inputs with Hypothesis
|
|
139
|
+
(in the `test` group, so the wheel tests run it too); it seeds inside the tests,
|
|
140
|
+
because Hypothesis rejects function-scoped fixtures, and has no deadline, because
|
|
141
|
+
the sanitizer and coverage builds are much slower.
|
|
142
|
+
- The README documents the behavior (mask semantics, `patch_size` as a radius, the
|
|
143
|
+
fallback installation); update it together with API or installation changes.
|
|
144
|
+
- Commit messages: imperative subject in sentence case without a prefix (e.g. "Reject
|
|
145
|
+
invalid patch sizes and empty regularity maps"), and a body that explains the reason,
|
|
146
|
+
with measurements where relevant. Changes go through PRs from branches such as
|
|
147
|
+
`fix/<topic>`.
|
|
148
|
+
|
|
149
|
+
## Git and GitHub
|
|
150
|
+
|
|
151
|
+
- `origin` is `mauwii/PyPatchMatch`, where PRs and releases go; `upstream` is
|
|
152
|
+
`invoke-ai/PyPatchMatch`. `gh` resolves the default repository to upstream, so pass
|
|
153
|
+
`-R mauwii/PyPatchMatch`.
|
|
154
|
+
- Releases: creating a GitHub release with a tag like `v2.0.0` (or `v2.0.0rc2` for
|
|
155
|
+
pre-releases) builds the wheels and publishes them to PyPI.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPatchMatch
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: This library implements the PatchMatch based inpainting algorithm.
|
|
5
5
|
Keywords: inpainting,patchmatch,image-processing,opencv
|
|
6
6
|
Author-Email: The InvokeAI Project <lincoln.stein@gmail.com>, Jiayuan Mao <maojiayuan@gmail.com>, Kyle Schouviller <kyle0654@hotmail.com>, Matthias Wild <mauwii@outlook.de>, Younesse ANDAM <younesse.andam@gmail.com>
|
|
@@ -12,20 +12,20 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
12
12
|
Classifier: Operating System :: POSIX :: Linux
|
|
13
13
|
Classifier: Programming Language :: C++
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
20
|
Classifier: Topic :: Multimedia :: Graphics
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Project-URL: Source Code, https://github.com/mauwii/PyPatchMatch
|
|
24
24
|
Project-URL: Issues, https://github.com/mauwii/PyPatchMatch/issues
|
|
25
25
|
Project-URL: Release Notes, https://github.com/mauwii/PyPatchMatch/releases
|
|
26
|
-
Requires-Python: >=3.
|
|
27
|
-
Requires-Dist: numpy>=1.
|
|
28
|
-
Requires-Dist: pillow>=
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: numpy>=1.23.2
|
|
28
|
+
Requires-Dist: pillow>=9.2.0
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
|
|
31
31
|
# PatchMatch based Inpainting
|
|
@@ -42,6 +42,12 @@ and Python interfaces. This implementation is heavily based on the implementatio
|
|
|
42
42
|
Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
|
|
43
43
|
with some bug fixes, and updates.
|
|
44
44
|
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
From left to right: the photo, the input of
|
|
48
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)
|
|
49
|
+
with the birdhouses painted out in white, and its result.
|
|
50
|
+
|
|
45
51
|
## Installation
|
|
46
52
|
|
|
47
53
|
```sh
|
|
@@ -78,12 +84,13 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
|
78
84
|
|
|
79
85
|
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
80
86
|
pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
|
|
81
|
-
their values
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
their colors have no influence on
|
|
87
|
+
their values, and the colors under the holes have no influence on the fill. Holes need
|
|
88
|
+
hard edges: an anti-aliased brush leaves light pixels along the edge of its strokes that
|
|
89
|
+
are not pure white, so they count as known and the fill continues them. Paint without
|
|
90
|
+
anti-aliasing or pass a mask that covers those pixels as well. The optional keyword
|
|
91
|
+
argument `global_mask`, in the same format, marks pixels that are neither filled nor
|
|
92
|
+
used as a source; they keep their values as well, and their colors have no influence on
|
|
93
|
+
the fill (see
|
|
87
94
|
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
88
95
|
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
89
96
|
The previous import path `from patchmatch import patch_match` keeps working.
|
|
@@ -12,6 +12,12 @@ and Python interfaces. This implementation is heavily based on the implementatio
|
|
|
12
12
|
Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
|
|
13
13
|
with some bug fixes, and updates.
|
|
14
14
|
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
From left to right: the photo, the input of
|
|
18
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)
|
|
19
|
+
with the birdhouses painted out in white, and its result.
|
|
20
|
+
|
|
15
21
|
## Installation
|
|
16
22
|
|
|
17
23
|
```sh
|
|
@@ -48,12 +54,13 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
|
48
54
|
|
|
49
55
|
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
50
56
|
pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
|
|
51
|
-
their values
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
their colors have no influence on
|
|
57
|
+
their values, and the colors under the holes have no influence on the fill. Holes need
|
|
58
|
+
hard edges: an anti-aliased brush leaves light pixels along the edge of its strokes that
|
|
59
|
+
are not pure white, so they count as known and the fill continues them. Paint without
|
|
60
|
+
anti-aliasing or pass a mask that covers those pixels as well. The optional keyword
|
|
61
|
+
argument `global_mask`, in the same format, marks pixels that are neither filled nor
|
|
62
|
+
used as a source; they keep their values as well, and their colors have no influence on
|
|
63
|
+
the fill (see
|
|
57
64
|
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
58
65
|
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
59
66
|
The previous import path `from patchmatch import patch_match` keeps working.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
Security fixes are released as a new version of PyPatchMatch on PyPI. Only the latest
|
|
6
|
+
release is supported; older versions are not updated.
|
|
7
|
+
|
|
8
|
+
## Reporting a Vulnerability
|
|
9
|
+
|
|
10
|
+
Please report vulnerabilities privately through GitHub's
|
|
11
|
+
[vulnerability reporting](https://github.com/mauwii/PyPatchMatch/security/advisories/new),
|
|
12
|
+
not in a public issue.
|
|
13
|
+
|
|
14
|
+
## OpenCV
|
|
15
|
+
|
|
16
|
+
The wheels on PyPI contain a statically linked, core-only build of OpenCV; its version
|
|
17
|
+
is listed in the CycloneDX SBOM in `sboms/opencv.cdx.json` of each wheel's
|
|
18
|
+
`.dist-info`. A vulnerability in OpenCV core is fixed by a new PyPatchMatch release
|
|
19
|
+
with an updated OpenCV. Installations built from the source distribution link the
|
|
20
|
+
OpenCV of the system and get its fixes from there.
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 0,
|
|
21
|
+
__version__ = version = '2.0.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g995b76749'
|
|
@@ -164,7 +164,18 @@ Inpainting::Inpainting(
|
|
|
164
164
|
|
|
165
165
|
void Inpainting::_initialize_pyramid()
|
|
166
166
|
{
|
|
167
|
-
|
|
167
|
+
// The colors under the holes would reach the fill through the gradients of their
|
|
168
|
+
// neighbors and the target of a single-level pyramid. Black, like on coarser levels.
|
|
169
|
+
auto source = m_initial.clone();
|
|
170
|
+
const auto size = source.size();
|
|
171
|
+
for (int y = 0; y < size.height; ++y)
|
|
172
|
+
{
|
|
173
|
+
for (int x = 0; x < size.width; ++x)
|
|
174
|
+
{
|
|
175
|
+
if (source.is_masked(y, x))
|
|
176
|
+
std::fill_n(source.get_mutable_image(y, x), 3, 0);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
168
179
|
m_pyramid.push_back(source);
|
|
169
180
|
while (source.size().height > m_distance_metric->patch_size() &&
|
|
170
181
|
source.size().width > m_distance_metric->patch_size())
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
build-backend = "scikit_build_core.build"
|
|
3
|
-
requires = ["scikit-build-core>=0.11", "setuptools-scm>=8"]
|
|
3
|
+
requires = ["scikit-build-core>=0.11.2", "setuptools-scm>=8"]
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "PyPatchMatch"
|
|
7
7
|
description = "This library implements the PatchMatch based inpainting algorithm."
|
|
8
|
-
readme = "README.md"
|
|
9
8
|
license = "MIT"
|
|
10
9
|
license-files = ["LICENSE"]
|
|
11
10
|
authors = [
|
|
@@ -15,7 +14,7 @@ authors = [
|
|
|
15
14
|
{ name = "Matthias Wild", email = "mauwii@outlook.de" },
|
|
16
15
|
{ name = "Younesse ANDAM", email = "younesse.andam@gmail.com" },
|
|
17
16
|
]
|
|
18
|
-
requires-python = ">=3.
|
|
17
|
+
requires-python = ">=3.11"
|
|
19
18
|
keywords = ["inpainting", "patchmatch", "image-processing", "opencv"]
|
|
20
19
|
classifiers = [
|
|
21
20
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -24,17 +23,17 @@ classifiers = [
|
|
|
24
23
|
"Operating System :: POSIX :: Linux",
|
|
25
24
|
"Programming Language :: C++",
|
|
26
25
|
"Programming Language :: Python :: 3",
|
|
27
|
-
"Programming Language :: Python :: 3.10",
|
|
28
26
|
"Programming Language :: Python :: 3.11",
|
|
29
27
|
"Programming Language :: Python :: 3.12",
|
|
30
28
|
"Programming Language :: Python :: 3.13",
|
|
31
29
|
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"Programming Language :: Python :: 3.15",
|
|
32
31
|
"Topic :: Multimedia :: Graphics",
|
|
33
32
|
"Topic :: Scientific/Engineering :: Image Processing",
|
|
34
33
|
"Typing :: Typed",
|
|
35
34
|
]
|
|
36
|
-
dependencies = ["numpy>=1.
|
|
37
|
-
dynamic = ["version"]
|
|
35
|
+
dependencies = ["numpy>=1.23.2", "pillow>=9.2.0"]
|
|
36
|
+
dynamic = ["version", "readme"]
|
|
38
37
|
|
|
39
38
|
[project.urls]
|
|
40
39
|
"Source Code" = "https://github.com/mauwii/PyPatchMatch"
|
|
@@ -56,6 +55,7 @@ wheel.py-api = "py3"
|
|
|
56
55
|
wheel.packages = ["patchmatch"]
|
|
57
56
|
wheel.exclude = ["patchmatch/csrc/**"]
|
|
58
57
|
metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"
|
|
58
|
+
metadata.readme.provider = "scikit_build_core.metadata.fancy_pypi_readme"
|
|
59
59
|
# The release wheels are built from the sdist, so it has to contain the tests
|
|
60
60
|
# and their input image; _version.py is generated and therefore git-ignored.
|
|
61
61
|
sdist.include = ["examples/images/forest_pruned.bmp", "patchmatch/_version.py"]
|
|
@@ -70,6 +70,17 @@ if.from-sdist = true
|
|
|
70
70
|
if.failed = true
|
|
71
71
|
wheel.cmake = false
|
|
72
72
|
|
|
73
|
+
[tool.hatch.metadata.hooks.fancy-pypi-readme]
|
|
74
|
+
content-type = "text/markdown"
|
|
75
|
+
|
|
76
|
+
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
|
|
77
|
+
path = "README.md"
|
|
78
|
+
|
|
79
|
+
# PyPI does not resolve relative image paths
|
|
80
|
+
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
|
|
81
|
+
pattern = '!\[([^\]]*)\]\((?!https?:)([^)]+)\)'
|
|
82
|
+
replacement = ''
|
|
83
|
+
|
|
73
84
|
[tool.setuptools_scm]
|
|
74
85
|
version_file = "patchmatch/_version.py"
|
|
75
86
|
# used when building without git metadata, e.g. from a GitHub source archive
|
|
@@ -78,10 +89,13 @@ fallback_version = "0.0.0"
|
|
|
78
89
|
[tool.cibuildwheel]
|
|
79
90
|
# The wheels are Python-version independent (py3-none-<platform>), so each platform
|
|
80
91
|
# is built and tested once; the CI workflow covers the other Python versions.
|
|
81
|
-
build = "
|
|
92
|
+
build = "cp311-*"
|
|
82
93
|
skip = "*-musllinux_*"
|
|
83
94
|
# OpenCV is built once per job in before-all, only for the runner's native arch
|
|
84
95
|
archs = ["native"]
|
|
96
|
+
# uv installs the build and test environments, so that UV_EXCLUDE_NEWER of wheels.yml
|
|
97
|
+
# applies to them
|
|
98
|
+
build-frontend = "build[uv]"
|
|
85
99
|
test-groups = ["test"]
|
|
86
100
|
test-command = "pytest {project}/tests --no-cov"
|
|
87
101
|
before-all = "python {project}/scripts/build_opencv.py"
|
|
@@ -89,6 +103,7 @@ environment = { OpenCV_ROOT = "/tmp/opencv" }
|
|
|
89
103
|
|
|
90
104
|
[tool.cibuildwheel.linux]
|
|
91
105
|
before-all = "/opt/python/cp312-cp312/bin/python {project}/scripts/build_opencv.py"
|
|
106
|
+
environment-pass = ["UV_EXCLUDE_NEWER"]
|
|
92
107
|
|
|
93
108
|
[tool.cibuildwheel.macos]
|
|
94
109
|
environment = { OpenCV_ROOT = "/tmp/opencv", MACOSX_DEPLOYMENT_TARGET = "11.0" }
|
|
@@ -108,7 +123,7 @@ cache-keys = [
|
|
|
108
123
|
[tool.ruff]
|
|
109
124
|
extend-exclude = ["patchmatch/csrc"]
|
|
110
125
|
line-length = 88
|
|
111
|
-
target-version = "
|
|
126
|
+
target-version = "py311"
|
|
112
127
|
|
|
113
128
|
[tool.ruff.lint]
|
|
114
129
|
select = ["B", "C4", "E", "F", "I", "RUF", "SIM", "UP", "W"]
|
|
@@ -138,7 +153,10 @@ warn_return_any = false
|
|
|
138
153
|
|
|
139
154
|
[tool.pytest.ini_options]
|
|
140
155
|
addopts = ["--cov-branch", "--cov-report=term:skip-covered", "--cov=patchmatch"]
|
|
156
|
+
# shows which test hangs before the job timeout kills the run
|
|
157
|
+
faulthandler_timeout = 300
|
|
141
158
|
markers = ["e2e: end-to-end tests of the user workflows (deselect with -m 'not e2e')"]
|
|
142
|
-
minversion = "
|
|
159
|
+
minversion = "9.0"
|
|
143
160
|
python_files = ["test_*.py"]
|
|
161
|
+
strict = true
|
|
144
162
|
testpaths = ["tests"]
|
|
@@ -4,6 +4,9 @@ The patchmatch library only needs opencv_core. Linking it statically keeps the
|
|
|
4
4
|
wheels small and free of OpenCV's GUI/codec dependencies.
|
|
5
5
|
|
|
6
6
|
Usage: python build_opencv.py (installs into $OpenCV_ROOT)
|
|
7
|
+
|
|
8
|
+
PATCHMATCH_OPENCV_VERSION selects another pinned version, e.g. 4.14.0 to rebuild the
|
|
9
|
+
wheels of PyPatchMatch 2.0.0.
|
|
7
10
|
"""
|
|
8
11
|
|
|
9
12
|
import hashlib
|
|
@@ -17,13 +20,20 @@ import tempfile
|
|
|
17
20
|
import urllib.request
|
|
18
21
|
from pathlib import Path
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
# The wheels link this code statically, so every archive is pinned by its SHA-256,
|
|
24
|
+
# e.g. from `sha256sum` or `shasum -a 256`.
|
|
25
|
+
OPENCV_SHA256S = {
|
|
26
|
+
"4.14.0": "ee8fb9b30eb60850431b4656447080e3737b56e45719c92b67f245950609f86e",
|
|
27
|
+
"5.0.0": "b0528f5a1d379d59d4701cb28c36e22214cc51cf64594e5b56f2d3e6c0233095",
|
|
28
|
+
}
|
|
29
|
+
# the version of the release wheels
|
|
30
|
+
OPENCV_VERSION = os.environ.get("PATCHMATCH_OPENCV_VERSION") or "5.0.0"
|
|
31
|
+
if OPENCV_VERSION not in OPENCV_SHA256S:
|
|
32
|
+
sys.exit(f"OpenCV {OPENCV_VERSION} is not pinned, known: {sorted(OPENCV_SHA256S)}")
|
|
33
|
+
OPENCV_SHA256 = OPENCV_SHA256S[OPENCV_VERSION]
|
|
21
34
|
OPENCV_URL = (
|
|
22
35
|
f"https://github.com/opencv/opencv/archive/refs/tags/{OPENCV_VERSION}.tar.gz"
|
|
23
36
|
)
|
|
24
|
-
# The wheels link this code statically, so the archive is pinned by its SHA-256.
|
|
25
|
-
# Update it together with the version, e.g. with `sha256sum` or `shasum -a 256`.
|
|
26
|
-
OPENCV_SHA256 = "ee8fb9b30eb60850431b4656447080e3737b56e45719c92b67f245950609f86e"
|
|
27
37
|
|
|
28
38
|
CMAKE_OPTIONS = {
|
|
29
39
|
"CMAKE_BUILD_TYPE": "Release",
|
|
@@ -63,6 +73,8 @@ CMAKE_OPTIONS = {
|
|
|
63
73
|
"WITH_QT": "OFF",
|
|
64
74
|
"WITH_TIFF": "OFF",
|
|
65
75
|
"WITH_V4L": "OFF",
|
|
76
|
+
# an installed VTK adds OpenGL to the link interface of core (OpenCV 5)
|
|
77
|
+
"WITH_VTK": "OFF",
|
|
66
78
|
"WITH_WEBP": "OFF",
|
|
67
79
|
"WITH_IPP": "OFF",
|
|
68
80
|
"WITH_ITT": "OFF",
|
|
@@ -80,6 +92,8 @@ WINDOWS_CMAKE_OPTIONS = {
|
|
|
80
92
|
"OPENCV_CONFIG_INSTALL_PATH": "cmake",
|
|
81
93
|
"OPENCV_INSTALL_BINARIES_PREFIX": "",
|
|
82
94
|
"OPENCV_SKIP_CMAKE_ROOT_CONFIG": "ON",
|
|
95
|
+
# skips OpenCV 5's assembler check: MinGW's would set MINGW and link pthread
|
|
96
|
+
"CMAKE_ASM_COMPILER": "CMAKE_ASM_COMPILER-NOTFOUND",
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
|
|
@@ -149,21 +163,22 @@ def main() -> None:
|
|
|
149
163
|
return
|
|
150
164
|
|
|
151
165
|
cmake = find_cmake()
|
|
152
|
-
with tempfile.TemporaryDirectory() as
|
|
153
|
-
|
|
166
|
+
with tempfile.TemporaryDirectory() as tmp_name:
|
|
167
|
+
# OpenCV 5 compares paths as strings, which fails with 8.3 names (RUNNER~1)
|
|
168
|
+
tmp = Path(tmp_name).resolve()
|
|
169
|
+
archive = tmp / "opencv.tar.gz"
|
|
154
170
|
print(f"Downloading {OPENCV_URL}", flush=True)
|
|
155
171
|
urllib.request.urlretrieve(OPENCV_URL, archive)
|
|
156
172
|
verify_archive(archive)
|
|
157
173
|
with tarfile.open(archive) as tar:
|
|
158
|
-
# extraction filters are missing on older patch releases
|
|
159
|
-
# last Windows installer of Python 3.10 (3.10.11)
|
|
174
|
+
# extraction filters are missing on older patch releases (3.11 < 3.11.4)
|
|
160
175
|
if hasattr(tarfile, "data_filter"):
|
|
161
176
|
tar.extractall(tmp, filter="data")
|
|
162
177
|
else:
|
|
163
178
|
tar.extractall(tmp)
|
|
164
179
|
|
|
165
|
-
source =
|
|
166
|
-
build =
|
|
180
|
+
source = tmp / f"opencv-{OPENCV_VERSION}"
|
|
181
|
+
build = tmp / "build"
|
|
167
182
|
options = {**CMAKE_OPTIONS, "CMAKE_INSTALL_PREFIX": prefix.as_posix()}
|
|
168
183
|
if sys.platform == "win32":
|
|
169
184
|
options.update(WINDOWS_CMAKE_OPTIONS)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Render examples/images/readme_example.jpg, the picture at the top of the README.
|
|
2
|
+
|
|
3
|
+
It runs examples/py_example.py and shows, from left to right, the photo forest.bmp,
|
|
4
|
+
the input forest_pruned.bmp with the birdhouses painted out in white, and the result.
|
|
5
|
+
The PyPI description links the picture at the release tag, so rerun the script and
|
|
6
|
+
commit the picture after changes that affect the fills.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
uv run python scripts/render_readme_example.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import runpy
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from PIL import Image
|
|
19
|
+
|
|
20
|
+
import patchmatch
|
|
21
|
+
|
|
22
|
+
EXAMPLES = Path(__file__).resolve().parents[1] / "examples"
|
|
23
|
+
IMAGES = EXAMPLES / "images"
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
patchmatch.set_random_seed(0)
|
|
27
|
+
runpy.run_path(str(EXAMPLES / "py_example.py"), run_name="__main__")
|
|
28
|
+
panels = [
|
|
29
|
+
np.asarray(Image.open(IMAGES / f"forest{suffix}.bmp"))
|
|
30
|
+
for suffix in ("", "_pruned", "_recovered")
|
|
31
|
+
]
|
|
32
|
+
gap = np.full((panels[0].shape[0], 8, 3), 255, dtype=np.uint8)
|
|
33
|
+
picture = np.hstack([panels[0], gap, panels[1], gap, panels[2]])
|
|
34
|
+
Image.fromarray(picture).save(
|
|
35
|
+
IMAGES / "readme_example.jpg", quality=85, optimize=True
|
|
36
|
+
)
|