PyPatchMatch 2.0.0b1__tar.gz → 2.0.0b2__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.0b1 → pypatchmatch-2.0.0b2}/CMakeLists.txt +13 -0
- pypatchmatch-2.0.0b2/PKG-INFO +140 -0
- pypatchmatch-2.0.0b1/PKG-INFO → pypatchmatch-2.0.0b2/README.md +31 -39
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/_version.py +3 -3
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/inpaint.cpp +20 -12
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/masked_image.h +1 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/nnf.cpp +33 -7
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/nnf.h +5 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/pyinterface.cpp +1 -2
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/patch_match.py +14 -5
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/pyproject.toml +9 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/scripts/build_opencv.py +15 -3
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/tests/test_e2e.py +37 -8
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/tests/test_patch_match.py +30 -4
- pypatchmatch-2.0.0b1/README.md +0 -96
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/LICENSE +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/examples/images/forest_pruned.bmp +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/__init__.py +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/_lib.py +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/inpaint.h +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/masked_image.cpp +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/csrc/pyinterface.h +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0b2}/patchmatch/py.typed +0 -0
|
@@ -61,3 +61,16 @@ install(TARGETS patchmatch
|
|
|
61
61
|
LIBRARY DESTINATION patchmatch
|
|
62
62
|
RUNTIME DESTINATION patchmatch
|
|
63
63
|
)
|
|
64
|
+
|
|
65
|
+
# A static OpenCV becomes part of the library, so a wheel has to ship its licenses,
|
|
66
|
+
# which scripts/build_opencv.py collects in <prefix>/licenses.
|
|
67
|
+
if(DEFINED SKBUILD_METADATA_DIR AND NOT OpenCV_SHARED)
|
|
68
|
+
set(PATCHMATCH_OPENCV_LICENSES "${OpenCV_INSTALL_PATH}/licenses")
|
|
69
|
+
if(EXISTS "${PATCHMATCH_OPENCV_LICENSES}/opencv-LICENSE")
|
|
70
|
+
install(DIRECTORY "${PATCHMATCH_OPENCV_LICENSES}/"
|
|
71
|
+
DESTINATION "${SKBUILD_METADATA_DIR}/licenses/opencv")
|
|
72
|
+
else()
|
|
73
|
+
message(WARNING "No OpenCV licenses in ${PATCHMATCH_OPENCV_LICENSES}, "
|
|
74
|
+
"the wheel will not include them")
|
|
75
|
+
endif()
|
|
76
|
+
endif()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyPatchMatch
|
|
3
|
+
Version: 2.0.0b2
|
|
4
|
+
Summary: This library implements the PatchMatch based inpainting algorithm.
|
|
5
|
+
Keywords: inpainting,patchmatch,image-processing,opencv
|
|
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>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: C++
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Project-URL: Source Code, https://github.com/mauwii/PyPatchMatch
|
|
24
|
+
Project-URL: Issues, https://github.com/mauwii/PyPatchMatch/issues
|
|
25
|
+
Project-URL: Release Notes, https://github.com/mauwii/PyPatchMatch/releases
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: numpy
|
|
28
|
+
Requires-Dist: pillow
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# PatchMatch based Inpainting
|
|
32
|
+
|
|
33
|
+
[](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
|
|
34
|
+
[](https://pypi.org/project/PyPatchMatch/)
|
|
35
|
+
[](https://pepy.tech/projects/pypatchmatch)
|
|
36
|
+
[](https://github.com/astral-sh/ruff)
|
|
37
|
+
|
|
38
|
+
This library implements the PatchMatch based inpainting algorithm. It provides both C++
|
|
39
|
+
and Python interfaces. This implementation is heavily based on the implementation by
|
|
40
|
+
Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
|
|
41
|
+
with some bug fixes, and updates.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install PyPatchMatch
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
|
|
50
|
+
(x64) ship the compiled library with a statically linked OpenCV, so no compiler or
|
|
51
|
+
OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
|
|
52
|
+
pip builds from the source distribution, which requires a C++17 compiler, CMake and the
|
|
53
|
+
OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
Python (see
|
|
58
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import patchmatch
|
|
62
|
+
|
|
63
|
+
image = ... # HxWx3 uint8 numpy array or PIL image
|
|
64
|
+
mask = ... # HxW uint8 or bool numpy array or PIL image, non-zero marks the holes
|
|
65
|
+
result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
69
|
+
pure white pixels are treated as holes. The optional keyword argument `global_mask`, in
|
|
70
|
+
the same format, marks pixels that are neither filled nor used as a source; they keep
|
|
71
|
+
their values (see
|
|
72
|
+
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
73
|
+
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
74
|
+
The previous import path `from patchmatch import patch_match` keeps working.
|
|
75
|
+
|
|
76
|
+
`patchmatch.set_random_seed(seed)` sets the seed of the randomized search and
|
|
77
|
+
`patchmatch.set_verbose(True)` prints the progress of the native code to stderr.
|
|
78
|
+
`patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the search with
|
|
79
|
+
a regularity map, an HxWx3 float32 array with the regularity coordinates of each pixel.
|
|
80
|
+
|
|
81
|
+
C++ (see
|
|
82
|
+
[examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
|
|
83
|
+
build and run it with
|
|
84
|
+
[examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
|
|
85
|
+
|
|
86
|
+
```cpp
|
|
87
|
+
#include "inpaint.h"
|
|
88
|
+
|
|
89
|
+
int main() {
|
|
90
|
+
cv::Mat image = ...;
|
|
91
|
+
cv::Mat mask = ...;
|
|
92
|
+
|
|
93
|
+
auto metric = PatchSSDDistanceMetric(5);
|
|
94
|
+
cv::Mat result = Inpainting(image, mask, &metric).run();
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The library is built with CMake; `PATCHMATCH_BUILD_EXAMPLES` builds the example and
|
|
99
|
+
`PATCHMATCH_WITH_HIGHGUI` enables the debug visualization of `Inpainting::run`.
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
The project is managed with [uv](https://docs.astral.sh/uv/):
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
uv sync # create .venv, build the library, install dev deps
|
|
107
|
+
uv run pre-commit install # enable ruff and the other hooks on commit
|
|
108
|
+
uv run pytest # run the test suite
|
|
109
|
+
uv run pytest -m "not e2e" # only the fast unit tests
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Releases are published to PyPI by creating a GitHub release; the version is taken from
|
|
113
|
+
its tag (e.g. `v2.0.0`).
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
PyPatchMatch is released under the
|
|
118
|
+
[MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
|
|
119
|
+
contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
|
|
120
|
+
third-party code; their licenses are included in the `licenses/opencv` directory of the
|
|
121
|
+
wheel's `.dist-info`.
|
|
122
|
+
|
|
123
|
+
## README and COPYRIGHT by Younesse ANDAM
|
|
124
|
+
|
|
125
|
+
@Author: Younesse ANDAM
|
|
126
|
+
|
|
127
|
+
@Contact: <younesse.andam@gmail.com>
|
|
128
|
+
|
|
129
|
+
Description:
|
|
130
|
+
|
|
131
|
+
This project is a personal implementation of an algorithm called PATCHMATCH
|
|
132
|
+
that restores missing areas in an image. The algorithm is presented in the following
|
|
133
|
+
paper PatchMatch A Randomized Correspondence Algorithm for Structural Image Editing by
|
|
134
|
+
C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graphics
|
|
135
|
+
(Proc. SIGGRAPH), vol.28, aug-2009
|
|
136
|
+
|
|
137
|
+
For more information please refer to
|
|
138
|
+
<https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
|
|
139
|
+
|
|
140
|
+
Copyright (c) 2010-2011
|
|
@@ -1,30 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: PyPatchMatch
|
|
3
|
-
Version: 2.0.0b1
|
|
4
|
-
Summary: This library implements the PatchMatch based inpainting algorithm.
|
|
5
|
-
Author-Email: The InvokeAI Project <lincoln.stein@gmail.com>, Kyle Schouviller <kyle0654@hotmail.com>, Matthias Wild <mauwii@outlook.de>, Younesse ANDAM <younesse.andam@gmail.com>
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Classifier: Programming Language :: C++
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Classifier: Topic :: Multimedia :: Graphics
|
|
16
|
-
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
17
|
-
Project-URL: Source Code, https://github.com/mauwii/PyPatchMatch
|
|
18
|
-
Requires-Python: >=3.10
|
|
19
|
-
Requires-Dist: numpy
|
|
20
|
-
Requires-Dist: pillow
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
1
|
# PatchMatch based Inpainting
|
|
24
2
|
|
|
25
3
|
[](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
|
|
26
4
|
[](https://pypi.org/project/PyPatchMatch/)
|
|
27
|
-
[](https://pepy.tech/
|
|
5
|
+
[](https://pepy.tech/projects/pypatchmatch)
|
|
28
6
|
[](https://github.com/astral-sh/ruff)
|
|
29
7
|
|
|
30
8
|
This library implements the PatchMatch based inpainting algorithm. It provides both C++
|
|
@@ -38,14 +16,16 @@ with some bug fixes, and updates.
|
|
|
38
16
|
pip install PyPatchMatch
|
|
39
17
|
```
|
|
40
18
|
|
|
41
|
-
Wheels for Linux,
|
|
42
|
-
|
|
43
|
-
|
|
19
|
+
Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
|
|
20
|
+
(x64) ship the compiled library with a statically linked OpenCV, so no compiler or
|
|
21
|
+
OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
|
|
22
|
+
pip builds from the source distribution, which requires a C++17 compiler, CMake and the
|
|
44
23
|
OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
|
|
45
24
|
|
|
46
25
|
## Usage
|
|
47
26
|
|
|
48
|
-
Python (see
|
|
27
|
+
Python (see
|
|
28
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
|
|
49
29
|
|
|
50
30
|
```python
|
|
51
31
|
import patchmatch
|
|
@@ -56,12 +36,22 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
|
56
36
|
```
|
|
57
37
|
|
|
58
38
|
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
59
|
-
pure white pixels are treated as holes.
|
|
39
|
+
pure white pixels are treated as holes. The optional keyword argument `global_mask`, in
|
|
40
|
+
the same format, marks pixels that are neither filled nor used as a source; they keep
|
|
41
|
+
their values (see
|
|
42
|
+
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
60
43
|
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
61
44
|
The previous import path `from patchmatch import patch_match` keeps working.
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
`patchmatch.set_random_seed(seed)` sets the seed of the randomized search and
|
|
47
|
+
`patchmatch.set_verbose(True)` prints the progress of the native code to stderr.
|
|
48
|
+
`patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the search with
|
|
49
|
+
a regularity map, an HxWx3 float32 array with the regularity coordinates of each pixel.
|
|
50
|
+
|
|
51
|
+
C++ (see
|
|
52
|
+
[examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
|
|
53
|
+
build and run it with
|
|
54
|
+
[examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
|
|
65
55
|
|
|
66
56
|
```cpp
|
|
67
57
|
#include "inpaint.h"
|
|
@@ -90,13 +80,21 @@ uv run pytest -m "not e2e" # only the fast unit tests
|
|
|
90
80
|
```
|
|
91
81
|
|
|
92
82
|
Releases are published to PyPI by creating a GitHub release; the version is taken from
|
|
93
|
-
its tag (e.g. `
|
|
83
|
+
its tag (e.g. `v2.0.0`).
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
PyPatchMatch is released under the
|
|
88
|
+
[MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
|
|
89
|
+
contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
|
|
90
|
+
third-party code; their licenses are included in the `licenses/opencv` directory of the
|
|
91
|
+
wheel's `.dist-info`.
|
|
94
92
|
|
|
95
93
|
## README and COPYRIGHT by Younesse ANDAM
|
|
96
94
|
|
|
97
95
|
@Author: Younesse ANDAM
|
|
98
96
|
|
|
99
|
-
@Contact: younesse.andam@gmail.com
|
|
97
|
+
@Contact: <younesse.andam@gmail.com>
|
|
100
98
|
|
|
101
99
|
Description:
|
|
102
100
|
|
|
@@ -107,12 +105,6 @@ C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graph
|
|
|
107
105
|
(Proc. SIGGRAPH), vol.28, aug-2009
|
|
108
106
|
|
|
109
107
|
For more information please refer to
|
|
110
|
-
|
|
108
|
+
<https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
|
|
111
109
|
|
|
112
110
|
Copyright (c) 2010-2011
|
|
113
|
-
|
|
114
|
-
## Requirements
|
|
115
|
-
|
|
116
|
-
To run the project you need to install Opencv library and link it to your project.
|
|
117
|
-
Opencv can be download it here
|
|
118
|
-
http://opencv.org/downloads.html
|
|
@@ -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, 0, '
|
|
21
|
+
__version__ = version = '2.0.0b2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b2')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gd8a13321b'
|
|
@@ -9,13 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
namespace
|
|
11
11
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
void init_kDistance2Similarity()
|
|
12
|
+
std::vector<double> make_distance2similarity()
|
|
15
13
|
{
|
|
16
14
|
double base[11] = {1.0, 0.99, 0.96, 0.83, 0.38, 0.11, 0.02, 0.005, 0.0006, 0.0001, 0};
|
|
17
15
|
int length = (PatchDistanceMetric::kDistanceScale + 1);
|
|
18
|
-
|
|
16
|
+
std::vector<double> table(length);
|
|
19
17
|
for (int i = 0; i < length; ++i)
|
|
20
18
|
{
|
|
21
19
|
double t = (double)i / length;
|
|
@@ -23,8 +21,17 @@ namespace
|
|
|
23
21
|
int k = j + 1;
|
|
24
22
|
double vj = (j < 11) ? base[j] : 0;
|
|
25
23
|
double vk = (k < 11) ? base[k] : 0;
|
|
26
|
-
|
|
24
|
+
table[i] = vj + (100 * t - j) * (vk - vj);
|
|
27
25
|
}
|
|
26
|
+
return table;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Built on first use. The initialization of a local static is thread-safe, which
|
|
30
|
+
// matters because the Python bindings release the GIL during inpainting.
|
|
31
|
+
const std::vector<double> &distance2similarity()
|
|
32
|
+
{
|
|
33
|
+
static const std::vector<double> table = make_distance2similarity();
|
|
34
|
+
return table;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
inline void _weighted_copy(const MaskedImage &source, int ys, int xs, cv::Mat &target, int yt, int xt, double weight)
|
|
@@ -69,16 +76,11 @@ void Inpainting::_initialize_pyramid()
|
|
|
69
76
|
source = source.downsample();
|
|
70
77
|
m_pyramid.push_back(source);
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
if (kDistance2Similarity.size() == 0)
|
|
74
|
-
{
|
|
75
|
-
init_kDistance2Similarity();
|
|
76
|
-
}
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int random_seed)
|
|
80
82
|
{
|
|
81
|
-
|
|
83
|
+
NearestNeighborField::seed_random(random_seed);
|
|
82
84
|
const int nr_levels = m_pyramid.size();
|
|
83
85
|
|
|
84
86
|
MaskedImage source, target;
|
|
@@ -124,7 +126,12 @@ cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int rando
|
|
|
124
126
|
target = _expectation_maximization(source, target, level, verbose);
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
// Globally masked pixels are neither filled nor used as a source, so the pyramid
|
|
130
|
+
// leaves them black. Keep their input values instead.
|
|
131
|
+
cv::Mat result = target.image();
|
|
132
|
+
if (!m_initial.global_mask().empty())
|
|
133
|
+
m_initial.image().copyTo(result, m_initial.global_mask());
|
|
134
|
+
return result;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
// EM-Like algorithm (see "PatchMatch" - page 6).
|
|
@@ -211,6 +218,7 @@ void Inpainting::_expectation_step(
|
|
|
211
218
|
auto source_size = nnf.source_size();
|
|
212
219
|
auto target_size = nnf.target_size();
|
|
213
220
|
const int patch_size = m_distance_metric->patch_size();
|
|
221
|
+
const auto &kDistance2Similarity = distance2similarity();
|
|
214
222
|
|
|
215
223
|
for (int i = 0; i < source_size.height; ++i)
|
|
216
224
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include <algorithm>
|
|
2
2
|
#include <iostream>
|
|
3
3
|
#include <cmath>
|
|
4
|
+
#include <random>
|
|
4
5
|
|
|
5
6
|
#include "masked_image.h"
|
|
6
7
|
#include "nnf.h"
|
|
@@ -17,9 +18,35 @@ T clamp(T value, T min_value, T max_value)
|
|
|
17
18
|
return std::min(std::max(value, min_value), max_value);
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
namespace
|
|
22
|
+
{
|
|
23
|
+
// One generator per thread, so concurrent inpaintings with the same seed give the
|
|
24
|
+
// same results. It only drives the randomized search, which has to be reproducible
|
|
25
|
+
// from a seed, so a standard generator is the right choice. SonarCloud's PRNG rule
|
|
26
|
+
// (cpp:S2245) flags every declaration of its type, hence the NOSONAR markers.
|
|
27
|
+
std::mt19937 &random_engine() // NOSONAR
|
|
28
|
+
{
|
|
29
|
+
thread_local std::mt19937 engine; // NOSONAR
|
|
30
|
+
return engine;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The modulo instead of std::uniform_int_distribution keeps the sequence identical
|
|
34
|
+
// on all platforms.
|
|
35
|
+
inline int random_int(int n)
|
|
36
|
+
{
|
|
37
|
+
return static_cast<int>(random_engine()() % static_cast<unsigned int>(n));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void NearestNeighborField::seed_random(unsigned int seed)
|
|
42
|
+
{
|
|
43
|
+
random_engine().seed(seed);
|
|
44
|
+
}
|
|
45
|
+
|
|
20
46
|
void NearestNeighborField::_randomize_field(int max_retry, bool reset)
|
|
21
47
|
{
|
|
22
48
|
auto this_size = source_size();
|
|
49
|
+
auto this_target_size = target_size();
|
|
23
50
|
for (int i = 0; i < this_size.height; ++i)
|
|
24
51
|
{
|
|
25
52
|
for (int j = 0; j < this_size.width; ++j)
|
|
@@ -37,8 +64,8 @@ void NearestNeighborField::_randomize_field(int max_retry, bool reset)
|
|
|
37
64
|
int i_target = 0, j_target = 0;
|
|
38
65
|
for (int t = 0; t < max_retry; ++t)
|
|
39
66
|
{
|
|
40
|
-
i_target =
|
|
41
|
-
j_target =
|
|
67
|
+
i_target = random_int(this_target_size.height);
|
|
68
|
+
j_target = random_int(this_target_size.width);
|
|
42
69
|
if (m_target.is_globally_masked(i_target, j_target))
|
|
43
70
|
continue;
|
|
44
71
|
|
|
@@ -138,8 +165,8 @@ void NearestNeighborField::_minimize_link(int y, int x, int direction)
|
|
|
138
165
|
int random_scale = (std::min(this_target_size.height, this_target_size.width) - 1) / 2;
|
|
139
166
|
while (random_scale > 0)
|
|
140
167
|
{
|
|
141
|
-
int yp = this_ptr[0] + (
|
|
142
|
-
int xp = this_ptr[1] + (
|
|
168
|
+
int yp = this_ptr[0] + (random_int(2 * random_scale + 1) - random_scale);
|
|
169
|
+
int xp = this_ptr[1] + (random_int(2 * random_scale + 1) - random_scale);
|
|
143
170
|
yp = clamp(yp, 0, target_size().height - 1);
|
|
144
171
|
xp = clamp(xp, 0, target_size().width - 1);
|
|
145
172
|
|
|
@@ -201,10 +228,9 @@ namespace
|
|
|
201
228
|
const unsigned char *p_sgm = nullptr;
|
|
202
229
|
const unsigned char *p_tgm = nullptr;
|
|
203
230
|
if (!source.global_mask().empty())
|
|
204
|
-
{
|
|
205
231
|
p_sgm = source.global_mask().ptr<unsigned char>(yys, 0);
|
|
232
|
+
if (!target.global_mask().empty())
|
|
206
233
|
p_tgm = target.global_mask().ptr<unsigned char>(yyt, 0);
|
|
207
|
-
}
|
|
208
234
|
|
|
209
235
|
const auto *p_sgy = source.grady().ptr<unsigned char>(yys, 0);
|
|
210
236
|
const auto *p_tgy = target.grady().ptr<unsigned char>(yyt, 0);
|
|
@@ -216,7 +242,7 @@ namespace
|
|
|
216
242
|
int xxs = xs + dx, xxt = xt + dx;
|
|
217
243
|
wsum += 1;
|
|
218
244
|
|
|
219
|
-
if (xxs <= 0 || xxs >= source_size.width - 1 || xxt <= 0 || xxt >=
|
|
245
|
+
if (xxs <= 0 || xxs >= source_size.width - 1 || xxt <= 0 || xxt >= target_size.width - 1)
|
|
220
246
|
{
|
|
221
247
|
distance += PatchSSDDistanceMetric::kSSDScale;
|
|
222
248
|
continue;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include <cassert>
|
|
4
|
+
#include <cmath>
|
|
3
5
|
#include <opencv2/core.hpp>
|
|
4
6
|
#include "masked_image.h"
|
|
5
7
|
|
|
@@ -87,6 +89,9 @@ public:
|
|
|
87
89
|
|
|
88
90
|
void minimize(int nr_pass);
|
|
89
91
|
|
|
92
|
+
// Seeds the random search of the calling thread.
|
|
93
|
+
static void seed_random(unsigned int seed);
|
|
94
|
+
|
|
90
95
|
private:
|
|
91
96
|
inline int _distance(int source_y, int source_x, int target_y, int target_x)
|
|
92
97
|
{
|
|
@@ -115,8 +115,7 @@ int _dtype_cv_to_py(int dtype_cv)
|
|
|
115
115
|
|
|
116
116
|
cv::Mat _py_to_cv2(PM_mat_t pymat)
|
|
117
117
|
{
|
|
118
|
-
int dtype = _dtype_py_to_cv(pymat.dtype);
|
|
119
|
-
dtype = CV_MAKETYPE(pymat.dtype, pymat.shape.channels);
|
|
118
|
+
int dtype = CV_MAKETYPE(_dtype_py_to_cv(pymat.dtype), pymat.shape.channels);
|
|
120
119
|
return cv::Mat(cv::Size(pymat.shape.width, pymat.shape.height), dtype, pymat.data_ptr).clone();
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -76,14 +76,15 @@ def inpaint(
|
|
|
76
76
|
mask: 1-channel uint8 or bool mask of the hole(s) to fill (non-zero = hole),
|
|
77
77
|
with the same height and width as ``image``. If ``None``, all pure
|
|
78
78
|
white pixels (255, 255, 255) are treated as holes.
|
|
79
|
-
global_mask: mask like ``mask`` of pixels that
|
|
79
|
+
global_mask: mask like ``mask`` of pixels that are neither filled nor used as
|
|
80
|
+
a source; they keep their values from ``image``.
|
|
80
81
|
patch_size: patch size for the inpainting algorithm.
|
|
81
82
|
|
|
82
83
|
Returns:
|
|
83
84
|
The repaired image, with the same shape as ``image``.
|
|
84
85
|
"""
|
|
85
86
|
lib = _get_lib()
|
|
86
|
-
image, mask, global_mask = _prepare_inputs(image, mask, global_mask)
|
|
87
|
+
image, mask, global_mask = _prepare_inputs(image, mask, global_mask, patch_size)
|
|
87
88
|
|
|
88
89
|
if global_mask is None:
|
|
89
90
|
return _call(lib.PM_inpaint, image, mask, ctypes.c_int(patch_size))
|
|
@@ -107,15 +108,16 @@ def inpaint_regularity(
|
|
|
107
108
|
guide_weight: weight of the regularity term relative to the patch distance.
|
|
108
109
|
"""
|
|
109
110
|
lib = _get_lib()
|
|
110
|
-
image, mask, global_mask = _prepare_inputs(image, mask, global_mask)
|
|
111
|
+
image, mask, global_mask = _prepare_inputs(image, mask, global_mask, patch_size)
|
|
111
112
|
|
|
112
113
|
if not (
|
|
113
114
|
isinstance(ijmap, np.ndarray)
|
|
114
115
|
and ijmap.ndim == 3
|
|
115
116
|
and ijmap.shape[2] == 3
|
|
116
117
|
and ijmap.dtype == np.float32
|
|
118
|
+
and ijmap.size > 0
|
|
117
119
|
):
|
|
118
|
-
raise ValueError("ijmap must be
|
|
120
|
+
raise ValueError("ijmap must be a non-empty HxWx3 float32 array")
|
|
119
121
|
ijmap = np.ascontiguousarray(ijmap)
|
|
120
122
|
|
|
121
123
|
args = (ijmap, ctypes.c_int(patch_size), ctypes.c_float(guide_weight))
|
|
@@ -125,9 +127,16 @@ def inpaint_regularity(
|
|
|
125
127
|
|
|
126
128
|
|
|
127
129
|
def _prepare_inputs(
|
|
128
|
-
image: ImageLike,
|
|
130
|
+
image: ImageLike,
|
|
131
|
+
mask: ImageLike | None,
|
|
132
|
+
global_mask: ImageLike | None,
|
|
133
|
+
patch_size: int,
|
|
129
134
|
) -> tuple[np.ndarray, np.ndarray, np.ndarray | None]:
|
|
130
135
|
"""Validate the inputs and convert them to contiguous arrays."""
|
|
136
|
+
# the native code crashes for 0 and never terminates for negative sizes
|
|
137
|
+
if patch_size < 1:
|
|
138
|
+
raise ValueError(f"patch_size must be at least 1, got {patch_size}")
|
|
139
|
+
|
|
131
140
|
image = _canonize_image_array(image)
|
|
132
141
|
mask = _default_mask(image) if mask is None else _canonize_mask_array(mask)
|
|
133
142
|
if global_mask is not None:
|
|
@@ -10,12 +10,18 @@ license = "MIT"
|
|
|
10
10
|
license-files = ["LICENSE"]
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "The InvokeAI Project", email = "lincoln.stein@gmail.com" },
|
|
13
|
+
{ name = "Jiayuan Mao", email = "maojiayuan@gmail.com" },
|
|
13
14
|
{ name = "Kyle Schouviller", email = "kyle0654@hotmail.com" },
|
|
14
15
|
{ name = "Matthias Wild", email = "mauwii@outlook.de" },
|
|
15
16
|
{ name = "Younesse ANDAM", email = "younesse.andam@gmail.com" },
|
|
16
17
|
]
|
|
17
18
|
requires-python = ">=3.10"
|
|
19
|
+
keywords = ["inpainting", "patchmatch", "image-processing", "opencv"]
|
|
18
20
|
classifiers = [
|
|
21
|
+
"Development Status :: 5 - Production/Stable",
|
|
22
|
+
"Operating System :: MacOS",
|
|
23
|
+
"Operating System :: Microsoft :: Windows",
|
|
24
|
+
"Operating System :: POSIX :: Linux",
|
|
19
25
|
"Programming Language :: C++",
|
|
20
26
|
"Programming Language :: Python :: 3",
|
|
21
27
|
"Programming Language :: Python :: 3.10",
|
|
@@ -25,12 +31,15 @@ classifiers = [
|
|
|
25
31
|
"Programming Language :: Python :: 3.14",
|
|
26
32
|
"Topic :: Multimedia :: Graphics",
|
|
27
33
|
"Topic :: Scientific/Engineering :: Image Processing",
|
|
34
|
+
"Typing :: Typed",
|
|
28
35
|
]
|
|
29
36
|
dependencies = ["numpy", "pillow"]
|
|
30
37
|
dynamic = ["version"]
|
|
31
38
|
|
|
32
39
|
[project.urls]
|
|
33
40
|
"Source Code" = "https://github.com/mauwii/PyPatchMatch"
|
|
41
|
+
Issues = "https://github.com/mauwii/PyPatchMatch/issues"
|
|
42
|
+
"Release Notes" = "https://github.com/mauwii/PyPatchMatch/releases"
|
|
34
43
|
|
|
35
44
|
[dependency-groups]
|
|
36
45
|
dev = [{ include-group = "test" }, "pre-commit", "ruff"]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
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
|
-
Usage: python build_opencv.py
|
|
6
|
+
Usage: python build_opencv.py (installs into $OpenCV_ROOT)
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import os
|
|
@@ -36,9 +36,14 @@ CMAKE_OPTIONS = {
|
|
|
36
36
|
"BUILD_opencv_apps": "OFF",
|
|
37
37
|
"BUILD_opencv_python3": "OFF",
|
|
38
38
|
"OPENCV_GENERATE_PKGCONFIG": "OFF",
|
|
39
|
+
# CMakeLists.txt ships the licenses from here with the wheels
|
|
40
|
+
"OPENCV_LICENSES_INSTALL_PATH": "licenses",
|
|
39
41
|
# only core is built, so disable every optional backend and codec
|
|
40
42
|
"WITH_ADE": "OFF",
|
|
41
43
|
"WITH_AVIF": "OFF",
|
|
44
|
+
# ARM HAL libraries, patchmatch uses no function they accelerate
|
|
45
|
+
"WITH_CAROTENE": "OFF",
|
|
46
|
+
"WITH_KLEIDICV": "OFF",
|
|
42
47
|
"WITH_EIGEN": "OFF",
|
|
43
48
|
"WITH_FFMPEG": "OFF",
|
|
44
49
|
"WITH_GSTREAMER": "OFF",
|
|
@@ -84,8 +89,10 @@ def find_cmake() -> str:
|
|
|
84
89
|
|
|
85
90
|
def main() -> None:
|
|
86
91
|
# OpenCV_ROOT is the variable CMake's find_package(OpenCV) looks for.
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
root = os.environ.get("OpenCV_ROOT") # noqa: SIM112
|
|
93
|
+
if not root:
|
|
94
|
+
sys.exit("OpenCV_ROOT must be set to the installation prefix")
|
|
95
|
+
prefix = Path(root)
|
|
89
96
|
if any(prefix.rglob("OpenCVConfig.cmake")):
|
|
90
97
|
print(f"OpenCV already installed in {prefix}")
|
|
91
98
|
return
|
|
@@ -115,6 +122,11 @@ def main() -> None:
|
|
|
115
122
|
)
|
|
116
123
|
subprocess.run([cmake, "--install", build, "--config", "Release"], check=True)
|
|
117
124
|
|
|
125
|
+
# OpenCV installs only the licenses of its 3rdparty code
|
|
126
|
+
licenses = prefix / CMAKE_OPTIONS["OPENCV_LICENSES_INSTALL_PATH"]
|
|
127
|
+
licenses.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
shutil.copyfile(source / "LICENSE", licenses / "opencv-LICENSE")
|
|
129
|
+
|
|
118
130
|
|
|
119
131
|
if __name__ == "__main__":
|
|
120
132
|
main()
|
|
@@ -25,9 +25,16 @@ def surrounding(mask: np.ndarray, width: int = 10) -> np.ndarray:
|
|
|
25
25
|
return (np.array(grown) > 0) & ~mask
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def assert_plausible_fill(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
def assert_plausible_fill(
|
|
29
|
+
source: np.ndarray, result: np.ndarray, excluded: np.ndarray | None = None
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Check an inpainting result of ``source`` without a ground truth image.
|
|
32
|
+
|
|
33
|
+
``excluded`` marks the pixels of a global mask, which are not filled.
|
|
34
|
+
"""
|
|
35
|
+
if excluded is None:
|
|
36
|
+
excluded = np.zeros(source.shape[:2], dtype=bool)
|
|
37
|
+
holes = white_pixels(source) & ~excluded
|
|
31
38
|
assert holes.any()
|
|
32
39
|
assert result.shape == source.shape
|
|
33
40
|
assert result.dtype == np.uint8
|
|
@@ -39,7 +46,7 @@ def assert_plausible_fill(source: np.ndarray, result: np.ndarray) -> None:
|
|
|
39
46
|
assert outside < 1, outside
|
|
40
47
|
|
|
41
48
|
# the filling blends in with its surroundings
|
|
42
|
-
band = surrounding(holes)
|
|
49
|
+
band = surrounding(holes) & ~excluded
|
|
43
50
|
color_diff = np.abs(result[holes].mean(axis=0) - source[band].mean(axis=0))
|
|
44
51
|
assert color_diff.max() < 10, color_diff
|
|
45
52
|
|
|
@@ -79,15 +86,37 @@ def test_remove_white_regions(pruned_image, tmp_path, as_array):
|
|
|
79
86
|
|
|
80
87
|
|
|
81
88
|
def test_global_mask(pruned_image):
|
|
82
|
-
"""examples/py_example_global_mask.py:
|
|
89
|
+
"""examples/py_example_global_mask.py: the globally masked plant is kept."""
|
|
83
90
|
source = np.array(pruned_image)
|
|
84
|
-
source[:100, :100] = 255
|
|
85
91
|
global_mask = np.zeros_like(source[..., 0])
|
|
86
|
-
global_mask[
|
|
92
|
+
global_mask[290:, 100:180] = 1
|
|
87
93
|
|
|
88
94
|
result = patchmatch.inpaint(source, global_mask=global_mask, patch_size=3)
|
|
89
95
|
|
|
90
|
-
|
|
96
|
+
excluded = global_mask.astype(bool)
|
|
97
|
+
np.testing.assert_array_equal(result[excluded], source[excluded])
|
|
98
|
+
assert_plausible_fill(source, result, excluded)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_global_mask_is_not_used_as_source(pruned_image):
|
|
102
|
+
"""A red block next to a hole fills it, unless the block is globally masked."""
|
|
103
|
+
source = np.array(pruned_image)
|
|
104
|
+
source[200:300, 20:120] = (255, 0, 0)
|
|
105
|
+
mask = np.zeros(source.shape[:2], dtype=np.uint8)
|
|
106
|
+
mask[210:290, 120:160] = 1
|
|
107
|
+
global_mask = np.zeros_like(mask)
|
|
108
|
+
global_mask[200:300, 20:120] = 1
|
|
109
|
+
|
|
110
|
+
def red_in_hole(result: np.ndarray) -> bool:
|
|
111
|
+
red = (result[..., 0] > 150) & (result[..., 1] < 80) & (result[..., 2] < 80)
|
|
112
|
+
return bool(red[mask == 1].any())
|
|
113
|
+
|
|
114
|
+
assert red_in_hole(patchmatch.inpaint(source, mask, patch_size=3))
|
|
115
|
+
|
|
116
|
+
result = patchmatch.inpaint(source, mask, global_mask=global_mask, patch_size=3)
|
|
117
|
+
assert not red_in_hole(result)
|
|
118
|
+
excluded = global_mask == 1
|
|
119
|
+
np.testing.assert_array_equal(result[excluded], source[excluded])
|
|
91
120
|
|
|
92
121
|
|
|
93
122
|
@pytest.mark.parametrize("hole_value", [1, 255])
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import ctypes
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
import pytest
|
|
@@ -83,9 +84,10 @@ def test_inpaint_explicit_mask(image, hole_mask):
|
|
|
83
84
|
def test_inpaint_global_mask(image, hole_mask):
|
|
84
85
|
global_mask = np.zeros_like(hole_mask)
|
|
85
86
|
global_mask[:8] = 1
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
result = patchmatch.inpaint(image, global_mask=global_mask, patch_size=3)
|
|
88
|
+
assert_filled(result, image)
|
|
89
|
+
# excluded pixels keep their values instead of the zeros of the pyramid
|
|
90
|
+
np.testing.assert_array_equal(result[:8], image[:8])
|
|
89
91
|
|
|
90
92
|
|
|
91
93
|
@pytest.mark.parametrize("patch_size", [1, 3, 7])
|
|
@@ -108,6 +110,17 @@ def test_inpaint_is_deterministic_with_seed(image):
|
|
|
108
110
|
np.testing.assert_array_equal(first, second)
|
|
109
111
|
|
|
110
112
|
|
|
113
|
+
def test_concurrent_inpaint_is_deterministic(image):
|
|
114
|
+
# the native code runs without the GIL, so the threads really overlap
|
|
115
|
+
expected = patchmatch.inpaint(image, patch_size=3)
|
|
116
|
+
with ThreadPoolExecutor(4) as pool:
|
|
117
|
+
results = list(
|
|
118
|
+
pool.map(lambda _: patchmatch.inpaint(image, patch_size=3), range(16))
|
|
119
|
+
)
|
|
120
|
+
for result in results:
|
|
121
|
+
np.testing.assert_array_equal(result, expected)
|
|
122
|
+
|
|
123
|
+
|
|
111
124
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
112
125
|
def test_set_verbose_prints_progress(verbose):
|
|
113
126
|
# The native code writes to its own C runtime's stderr. On Windows the DLL
|
|
@@ -230,14 +243,27 @@ def test_mask_size_must_match_image(image, ijmap, func, argument):
|
|
|
230
243
|
func(image, **kwargs)
|
|
231
244
|
|
|
232
245
|
|
|
246
|
+
@pytest.mark.parametrize("patch_size", [0, -1])
|
|
247
|
+
@pytest.mark.parametrize(
|
|
248
|
+
"func",
|
|
249
|
+
[patchmatch.inpaint, patchmatch.inpaint_regularity],
|
|
250
|
+
ids=lambda f: f.__name__,
|
|
251
|
+
)
|
|
252
|
+
def test_invalid_patch_size(image, ijmap, func, patch_size):
|
|
253
|
+
kwargs = {"ijmap": ijmap} if func is patchmatch.inpaint_regularity else {}
|
|
254
|
+
with pytest.raises(ValueError, match="patch_size"):
|
|
255
|
+
func(image, None, patch_size=patch_size, **kwargs)
|
|
256
|
+
|
|
257
|
+
|
|
233
258
|
@pytest.mark.parametrize(
|
|
234
259
|
"bad_ijmap",
|
|
235
260
|
[
|
|
236
261
|
np.zeros((HEIGHT, WIDTH, 3), dtype=np.float64),
|
|
237
262
|
np.zeros((HEIGHT, WIDTH, 2), dtype=np.float32),
|
|
263
|
+
np.zeros((0, WIDTH, 3), dtype=np.float32),
|
|
238
264
|
[[[0.0, 0.0, 0.0]]],
|
|
239
265
|
],
|
|
240
|
-
ids=["float64", "2-channel", "list"],
|
|
266
|
+
ids=["float64", "2-channel", "empty", "list"],
|
|
241
267
|
)
|
|
242
268
|
def test_inpaint_regularity_invalid_ijmap(image, bad_ijmap):
|
|
243
269
|
with pytest.raises(ValueError, match="ijmap"):
|
pypatchmatch-2.0.0b1/README.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# PatchMatch based Inpainting
|
|
2
|
-
|
|
3
|
-
[](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
|
|
4
|
-
[](https://pypi.org/project/PyPatchMatch/)
|
|
5
|
-
[](https://pepy.tech/project/pypatchmatch)
|
|
6
|
-
[](https://github.com/astral-sh/ruff)
|
|
7
|
-
|
|
8
|
-
This library implements the PatchMatch based inpainting algorithm. It provides both C++
|
|
9
|
-
and Python interfaces. This implementation is heavily based on the implementation by
|
|
10
|
-
Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
|
|
11
|
-
with some bug fixes, and updates.
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
pip install PyPatchMatch
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Wheels for Linux, macOS and Windows ship the compiled library including the required
|
|
20
|
-
OpenCV runtime, so no compiler or OpenCV installation is needed. On other platforms pip
|
|
21
|
-
builds from the source distribution, which requires a C++17 compiler, CMake and the
|
|
22
|
-
OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
|
|
23
|
-
|
|
24
|
-
## Usage
|
|
25
|
-
|
|
26
|
-
Python (see [examples/py_example.py](examples/py_example.py)):
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
import patchmatch
|
|
30
|
-
|
|
31
|
-
image = ... # HxWx3 uint8 numpy array or PIL image
|
|
32
|
-
mask = ... # HxW uint8 or bool numpy array or PIL image, non-zero marks the holes
|
|
33
|
-
result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
37
|
-
pure white pixels are treated as holes.
|
|
38
|
-
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
39
|
-
The previous import path `from patchmatch import patch_match` keeps working.
|
|
40
|
-
|
|
41
|
-
C++ (see [examples/cpp_example.cpp](examples/cpp_example.cpp), build and run it with
|
|
42
|
-
[examples/cpp_example_run.sh](examples/cpp_example_run.sh)):
|
|
43
|
-
|
|
44
|
-
```cpp
|
|
45
|
-
#include "inpaint.h"
|
|
46
|
-
|
|
47
|
-
int main() {
|
|
48
|
-
cv::Mat image = ...;
|
|
49
|
-
cv::Mat mask = ...;
|
|
50
|
-
|
|
51
|
-
auto metric = PatchSSDDistanceMetric(5);
|
|
52
|
-
cv::Mat result = Inpainting(image, mask, &metric).run();
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
The library is built with CMake; `PATCHMATCH_BUILD_EXAMPLES` builds the example and
|
|
57
|
-
`PATCHMATCH_WITH_HIGHGUI` enables the debug visualization of `Inpainting::run`.
|
|
58
|
-
|
|
59
|
-
## Development
|
|
60
|
-
|
|
61
|
-
The project is managed with [uv](https://docs.astral.sh/uv/):
|
|
62
|
-
|
|
63
|
-
```sh
|
|
64
|
-
uv sync # create .venv, build the library, install dev deps
|
|
65
|
-
uv run pre-commit install # enable ruff and the other hooks on commit
|
|
66
|
-
uv run pytest # run the test suite
|
|
67
|
-
uv run pytest -m "not e2e" # only the fast unit tests
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Releases are published to PyPI by creating a GitHub release; the version is taken from
|
|
71
|
-
its tag (e.g. `v1.1.0`).
|
|
72
|
-
|
|
73
|
-
## README and COPYRIGHT by Younesse ANDAM
|
|
74
|
-
|
|
75
|
-
@Author: Younesse ANDAM
|
|
76
|
-
|
|
77
|
-
@Contact: younesse.andam@gmail.com
|
|
78
|
-
|
|
79
|
-
Description:
|
|
80
|
-
|
|
81
|
-
This project is a personal implementation of an algorithm called PATCHMATCH
|
|
82
|
-
that restores missing areas in an image. The algorithm is presented in the following
|
|
83
|
-
paper PatchMatch A Randomized Correspondence Algorithm for Structural Image Editing by
|
|
84
|
-
C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graphics
|
|
85
|
-
(Proc. SIGGRAPH), vol.28, aug-2009
|
|
86
|
-
|
|
87
|
-
For more information please refer to
|
|
88
|
-
http://www.cs.princeton.edu/gfx/pubs/Barnes_2009_PAR/index.php
|
|
89
|
-
|
|
90
|
-
Copyright (c) 2010-2011
|
|
91
|
-
|
|
92
|
-
## Requirements
|
|
93
|
-
|
|
94
|
-
To run the project you need to install Opencv library and link it to your project.
|
|
95
|
-
Opencv can be download it here
|
|
96
|
-
http://opencv.org/downloads.html
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|