PyPatchMatch 2.2.0rc2__py3-none-win_arm64.whl
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.
- patchmatch/__init__.py +19 -0
- patchmatch/_lib.py +123 -0
- patchmatch/_version.py +24 -0
- patchmatch/patch_match.py +263 -0
- patchmatch/patchmatch.dll +0 -0
- patchmatch/py.typed +0 -0
- pypatchmatch-2.2.0rc2.dist-info/METADATA +196 -0
- pypatchmatch-2.2.0rc2.dist-info/RECORD +16 -0
- pypatchmatch-2.2.0rc2.dist-info/WHEEL +5 -0
- pypatchmatch-2.2.0rc2.dist-info/licenses/LICENSE +22 -0
- pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/SoftFloat-COPYING.txt +36 -0
- pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/dlpack-LICENSE +201 -0
- pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/flatbuffers-LICENSE.txt +202 -0
- pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/opencv-LICENSE +202 -0
- pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/zlib-LICENSE +22 -0
- pypatchmatch-2.2.0rc2.dist-info/sboms/opencv.cdx.json +35 -0
patchmatch/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""PatchMatch based inpainting."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .patch_match import (
|
|
5
|
+
inpaint,
|
|
6
|
+
inpaint_regularity,
|
|
7
|
+
patchmatch_available,
|
|
8
|
+
set_random_seed,
|
|
9
|
+
set_verbose,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"__version__",
|
|
14
|
+
"inpaint",
|
|
15
|
+
"inpaint_regularity",
|
|
16
|
+
"patchmatch_available",
|
|
17
|
+
"set_random_seed",
|
|
18
|
+
"set_verbose",
|
|
19
|
+
]
|
patchmatch/_lib.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""ctypes bindings for the native patchmatch library."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ctypes
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
if sys.platform == "win32":
|
|
12
|
+
LIBRARY_NAME = "patchmatch.dll"
|
|
13
|
+
elif sys.platform == "darwin":
|
|
14
|
+
LIBRARY_NAME = "libpatchmatch.dylib"
|
|
15
|
+
else:
|
|
16
|
+
LIBRARY_NAME = "libpatchmatch.so"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CShapeT(ctypes.Structure):
|
|
20
|
+
_fields_ = [
|
|
21
|
+
("width", ctypes.c_int),
|
|
22
|
+
("height", ctypes.c_int),
|
|
23
|
+
("channels", ctypes.c_int),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CMatT(ctypes.Structure):
|
|
28
|
+
_fields_ = [
|
|
29
|
+
("data_ptr", ctypes.c_void_p),
|
|
30
|
+
("shape", CShapeT),
|
|
31
|
+
("dtype", ctypes.c_int),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Order matches the PM_dtype_e enum in csrc/pyinterface.h.
|
|
36
|
+
_PYMAT_DTYPES = [np.dtype(np.uint8), np.dtype(np.float32)]
|
|
37
|
+
_PYMAT_DTYPE_IDS = {dtype: i for i, dtype in enumerate(_PYMAT_DTYPES)}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def np_to_pymat(npmat: np.ndarray) -> CMatT:
|
|
41
|
+
"""Wrap a contiguous HxWxC array without copying it."""
|
|
42
|
+
if npmat.ndim != 3:
|
|
43
|
+
raise ValueError(f"expected a 3-dimensional array, got {npmat.ndim} dims")
|
|
44
|
+
if not npmat.flags.c_contiguous:
|
|
45
|
+
raise ValueError("expected a C-contiguous array")
|
|
46
|
+
try:
|
|
47
|
+
dtype_id = _PYMAT_DTYPE_IDS[npmat.dtype]
|
|
48
|
+
except KeyError:
|
|
49
|
+
raise TypeError(f"unsupported dtype {npmat.dtype}") from None
|
|
50
|
+
height, width, channels = npmat.shape
|
|
51
|
+
return CMatT(
|
|
52
|
+
ctypes.c_void_p(npmat.ctypes.data),
|
|
53
|
+
CShapeT(width, height, channels),
|
|
54
|
+
dtype_id,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def pymat_to_np(pymat: CMatT) -> np.ndarray:
|
|
59
|
+
"""Copy the data referenced by ``pymat`` into a new numpy array."""
|
|
60
|
+
dtype = _PYMAT_DTYPES[pymat.dtype]
|
|
61
|
+
shape = (pymat.shape.height, pymat.shape.width, pymat.shape.channels)
|
|
62
|
+
buffer = (ctypes.c_byte * (int(np.prod(shape)) * dtype.itemsize)).from_address(
|
|
63
|
+
pymat.data_ptr
|
|
64
|
+
)
|
|
65
|
+
return np.frombuffer(buffer, dtype=dtype).reshape(shape).copy()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def find_library() -> Path:
|
|
69
|
+
"""Locate the shared library that is installed next to this package."""
|
|
70
|
+
package = sys.modules[__package__]
|
|
71
|
+
for directory in package.__path__:
|
|
72
|
+
candidate = Path(directory) / LIBRARY_NAME
|
|
73
|
+
if candidate.is_file():
|
|
74
|
+
return candidate
|
|
75
|
+
# Without a wheel for the platform, a failed build from the sdist installs the
|
|
76
|
+
# package without the library, see the overrides in pyproject.toml.
|
|
77
|
+
raise OSError(
|
|
78
|
+
f"{LIBRARY_NAME} not found in {list(package.__path__)}. Without a wheel for "
|
|
79
|
+
"this platform, PyPatchMatch is built from source, which needs a C++ "
|
|
80
|
+
"compiler, CMake and OpenCV. Install them and reinstall it without the "
|
|
81
|
+
"cache: pip install --force-reinstall --no-deps --no-cache-dir pypatchmatch"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_library(path: Path | None = None) -> ctypes.CDLL:
|
|
86
|
+
"""Load the installed library, or the build at ``path``."""
|
|
87
|
+
lib = ctypes.CDLL(str(path or find_library()))
|
|
88
|
+
|
|
89
|
+
lib.PM_set_random_seed.argtypes = [ctypes.c_uint]
|
|
90
|
+
lib.PM_set_random_seed.restype = None
|
|
91
|
+
lib.PM_set_verbose.argtypes = [ctypes.c_int]
|
|
92
|
+
lib.PM_set_verbose.restype = None
|
|
93
|
+
lib.PM_free_pymat.argtypes = [CMatT]
|
|
94
|
+
lib.PM_free_pymat.restype = None
|
|
95
|
+
lib.PM_last_error.argtypes = []
|
|
96
|
+
lib.PM_last_error.restype = ctypes.c_char_p
|
|
97
|
+
|
|
98
|
+
lib.PM_inpaint.argtypes = [CMatT, CMatT, ctypes.c_int]
|
|
99
|
+
lib.PM_inpaint2.argtypes = [CMatT, CMatT, CMatT, ctypes.c_int]
|
|
100
|
+
lib.PM_inpaint_regularity.argtypes = [
|
|
101
|
+
CMatT,
|
|
102
|
+
CMatT,
|
|
103
|
+
CMatT,
|
|
104
|
+
ctypes.c_int,
|
|
105
|
+
ctypes.c_float,
|
|
106
|
+
]
|
|
107
|
+
lib.PM_inpaint2_regularity.argtypes = [
|
|
108
|
+
CMatT,
|
|
109
|
+
CMatT,
|
|
110
|
+
CMatT,
|
|
111
|
+
CMatT,
|
|
112
|
+
ctypes.c_int,
|
|
113
|
+
ctypes.c_float,
|
|
114
|
+
]
|
|
115
|
+
for func in (
|
|
116
|
+
lib.PM_inpaint,
|
|
117
|
+
lib.PM_inpaint2,
|
|
118
|
+
lib.PM_inpaint_regularity,
|
|
119
|
+
lib.PM_inpaint2_regularity,
|
|
120
|
+
):
|
|
121
|
+
func.restype = CMatT
|
|
122
|
+
|
|
123
|
+
return lib
|
patchmatch/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '2.2.0rc2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 0, 'rc2')
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# File : patch_match.py
|
|
2
|
+
# Author : Jiayuan Mao
|
|
3
|
+
# Email : maojiayuan@gmail.com
|
|
4
|
+
# Date : 01/09/2020
|
|
5
|
+
#
|
|
6
|
+
# Distributed under terms of the MIT license.
|
|
7
|
+
#
|
|
8
|
+
# Additional modifications by Kyle Schouviller and Matthias Wild
|
|
9
|
+
|
|
10
|
+
"""PatchMatch based inpainting.
|
|
11
|
+
|
|
12
|
+
PatchMatch: A Randomized Correspondence Algorithm for Structural Image Editing.
|
|
13
|
+
C. Barnes, E. Shechtman, A. Finkelstein and Dan B. Goldman. SIGGRAPH 2009.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import ctypes
|
|
19
|
+
import logging
|
|
20
|
+
import operator
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
from typing import SupportsIndex, TypeAlias
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
from PIL import Image
|
|
26
|
+
|
|
27
|
+
# CMatT and CShapeT are re-exported for backwards compatibility
|
|
28
|
+
from ._lib import CMatT as CMatT
|
|
29
|
+
from ._lib import CShapeT as CShapeT
|
|
30
|
+
from ._lib import load_library, np_to_pymat, pymat_to_np
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"inpaint",
|
|
34
|
+
"inpaint_regularity",
|
|
35
|
+
"patchmatch_available",
|
|
36
|
+
"set_random_seed",
|
|
37
|
+
"set_verbose",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
ImageLike: TypeAlias = np.ndarray | Image.Image
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
_lib: ctypes.CDLL | None = load_library()
|
|
46
|
+
except (OSError, AttributeError) as e:
|
|
47
|
+
# AttributeError: a library without the PM_* functions, e.g. one that 1.x
|
|
48
|
+
# compiled into the package directory and that pip does not remove on upgrade
|
|
49
|
+
_lib = None
|
|
50
|
+
logger.warning("patchmatch failed to load: %s", e)
|
|
51
|
+
|
|
52
|
+
# True if the native library was loaded successfully.
|
|
53
|
+
patchmatch_available = _lib is not None
|
|
54
|
+
|
|
55
|
+
# The native code computes 2 * patch_size + 1 as a C int.
|
|
56
|
+
_MAX_PATCH_SIZE = (2**31 - 2) // 2
|
|
57
|
+
|
|
58
|
+
# The native code takes a C float, which turns larger weights into infinity.
|
|
59
|
+
_MAX_GUIDE_WEIGHT = float(np.finfo(np.float32).max)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_lib() -> ctypes.CDLL:
|
|
63
|
+
if _lib is None:
|
|
64
|
+
raise RuntimeError("the patchmatch native library is not available")
|
|
65
|
+
return _lib
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def set_random_seed(seed: int) -> None:
|
|
69
|
+
"""Set the seed of the randomized search of all following inpaintings.
|
|
70
|
+
|
|
71
|
+
Every inpainting starts from this seed, so equal inputs give equal results. The
|
|
72
|
+
native code keeps it as a 32-bit unsigned int, so ``seed`` is taken modulo
|
|
73
|
+
``2**32``: ``2**32`` behaves like 0 and -1 like ``2**32 - 1``.
|
|
74
|
+
"""
|
|
75
|
+
_get_lib().PM_set_random_seed(ctypes.c_uint(seed))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def set_verbose(verbose: bool) -> None:
|
|
79
|
+
"""Print the progress of all following inpaintings to stderr if ``verbose``."""
|
|
80
|
+
_get_lib().PM_set_verbose(ctypes.c_int(verbose))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def inpaint(
|
|
84
|
+
image: ImageLike,
|
|
85
|
+
mask: ImageLike | None = None,
|
|
86
|
+
*,
|
|
87
|
+
global_mask: ImageLike | None = None,
|
|
88
|
+
patch_size: SupportsIndex = 15,
|
|
89
|
+
) -> np.ndarray:
|
|
90
|
+
"""Fill the masked regions of ``image`` using PatchMatch.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
image: 3-channel uint8 RGB/BGR image.
|
|
94
|
+
mask: 1-channel uint8 or bool mask of the hole(s) to fill (non-zero = hole),
|
|
95
|
+
with the same height and width as ``image``. If ``None``, all pure
|
|
96
|
+
white pixels (255, 255, 255) are treated as holes.
|
|
97
|
+
global_mask: mask like ``mask`` of pixels that are neither filled nor used as
|
|
98
|
+
a source; they keep their values from ``image``.
|
|
99
|
+
patch_size: radius of the compared patches, which span
|
|
100
|
+
``(2 * patch_size + 1) ** 2`` pixels. Larger patches follow larger
|
|
101
|
+
structures but are much slower; the examples use 3.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
The repaired image, with the same shape as ``image``. Only the holes are
|
|
105
|
+
filled; all other pixels keep their values from ``image``.
|
|
106
|
+
"""
|
|
107
|
+
lib = _get_lib()
|
|
108
|
+
patch_size = _check_patch_size(patch_size)
|
|
109
|
+
image, mask, global_mask = _prepare_inputs(image, mask, global_mask)
|
|
110
|
+
if not _has_holes(mask, global_mask):
|
|
111
|
+
return image.copy()
|
|
112
|
+
|
|
113
|
+
if global_mask is None:
|
|
114
|
+
return _call(lib.PM_inpaint, image, mask, ctypes.c_int(patch_size))
|
|
115
|
+
return _call(lib.PM_inpaint2, image, mask, global_mask, ctypes.c_int(patch_size))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def inpaint_regularity(
|
|
119
|
+
image: ImageLike,
|
|
120
|
+
mask: ImageLike | None,
|
|
121
|
+
ijmap: np.ndarray,
|
|
122
|
+
*,
|
|
123
|
+
global_mask: ImageLike | None = None,
|
|
124
|
+
patch_size: SupportsIndex = 15,
|
|
125
|
+
guide_weight: float = 0.25,
|
|
126
|
+
) -> np.ndarray:
|
|
127
|
+
"""Like :func:`inpaint`, additionally guided by a regularity map.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
ijmap: HxWx3 float32 array of finite values with the regularity coordinates
|
|
131
|
+
of each pixel in the first two channels; the third channel is unused.
|
|
132
|
+
A map of a different size is scaled to the image.
|
|
133
|
+
guide_weight: non-negative weight of the regularity term relative to the
|
|
134
|
+
patch distance.
|
|
135
|
+
"""
|
|
136
|
+
lib = _get_lib()
|
|
137
|
+
patch_size = _check_patch_size(patch_size)
|
|
138
|
+
image, mask, global_mask = _prepare_inputs(image, mask, global_mask)
|
|
139
|
+
|
|
140
|
+
if not (
|
|
141
|
+
isinstance(ijmap, np.ndarray)
|
|
142
|
+
and ijmap.ndim == 3
|
|
143
|
+
and ijmap.shape[2] == 3
|
|
144
|
+
and ijmap.dtype == np.float32
|
|
145
|
+
and ijmap.size > 0
|
|
146
|
+
):
|
|
147
|
+
raise ValueError("ijmap must be a non-empty HxWx3 float32 array")
|
|
148
|
+
# NaN and infinity turn into out-of-range patch distances in the native code
|
|
149
|
+
if not np.isfinite(ijmap).all():
|
|
150
|
+
raise ValueError("ijmap must only contain finite values")
|
|
151
|
+
ijmap = np.ascontiguousarray(ijmap)
|
|
152
|
+
|
|
153
|
+
# the native code divides by 1 + guide_weight and indexes a table with the result;
|
|
154
|
+
# the comparison also rejects NaN
|
|
155
|
+
guide_weight = float(guide_weight)
|
|
156
|
+
if not 0 <= guide_weight <= _MAX_GUIDE_WEIGHT:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"guide_weight must be between 0 and {_MAX_GUIDE_WEIGHT:g}, "
|
|
159
|
+
f"got {guide_weight}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if not _has_holes(mask, global_mask):
|
|
163
|
+
return image.copy()
|
|
164
|
+
|
|
165
|
+
args = (ijmap, ctypes.c_int(patch_size), ctypes.c_float(guide_weight))
|
|
166
|
+
if global_mask is None:
|
|
167
|
+
return _call(lib.PM_inpaint_regularity, image, mask, *args)
|
|
168
|
+
return _call(lib.PM_inpaint2_regularity, image, mask, global_mask, *args)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _check_patch_size(patch_size: SupportsIndex) -> int:
|
|
172
|
+
try:
|
|
173
|
+
patch_size = operator.index(patch_size)
|
|
174
|
+
except TypeError:
|
|
175
|
+
raise TypeError(
|
|
176
|
+
f"patch_size must be an integer, got {type(patch_size).__name__}"
|
|
177
|
+
) from None
|
|
178
|
+
# The native code crashes for 0 and never terminates for negative sizes. ctypes
|
|
179
|
+
# silently wraps larger values into the C int range, e.g. 2**31 to a negative size.
|
|
180
|
+
if not 1 <= patch_size <= _MAX_PATCH_SIZE:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"patch_size must be between 1 and {_MAX_PATCH_SIZE}, got {patch_size}"
|
|
183
|
+
)
|
|
184
|
+
return patch_size
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _prepare_inputs(
|
|
188
|
+
image: ImageLike,
|
|
189
|
+
mask: ImageLike | None,
|
|
190
|
+
global_mask: ImageLike | None,
|
|
191
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray | None]:
|
|
192
|
+
"""Validate the inputs and convert them to contiguous arrays."""
|
|
193
|
+
image = _canonize_image_array(image)
|
|
194
|
+
mask = _default_mask(image) if mask is None else _canonize_mask_array(mask)
|
|
195
|
+
if global_mask is not None:
|
|
196
|
+
global_mask = _canonize_mask_array(global_mask)
|
|
197
|
+
|
|
198
|
+
# the native code indexes the masks with the image coordinates
|
|
199
|
+
for name, m in (("mask", mask), ("global_mask", global_mask)):
|
|
200
|
+
if m is not None and m.shape[:2] != image.shape[:2]:
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"{name} must have the same height and width as the image, "
|
|
203
|
+
f"got {m.shape[:2]} for an image of {image.shape[:2]}"
|
|
204
|
+
)
|
|
205
|
+
return image, mask, global_mask
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _has_holes(mask: np.ndarray, global_mask: np.ndarray | None) -> bool:
|
|
209
|
+
"""Whether a hole lies outside the global mask.
|
|
210
|
+
|
|
211
|
+
Otherwise the result equals the input, but the native code still runs every
|
|
212
|
+
level of the pyramid. This also covers empty images, which OpenCV 4.6 would
|
|
213
|
+
return with the shape (0, 0, 1).
|
|
214
|
+
"""
|
|
215
|
+
holes = mask != 0
|
|
216
|
+
if global_mask is not None:
|
|
217
|
+
holes &= global_mask == 0
|
|
218
|
+
return bool(holes.any())
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _call(func: Callable[..., CMatT], *args: object) -> np.ndarray:
|
|
222
|
+
"""Call ``func`` with arrays converted to pymats and copy the result."""
|
|
223
|
+
lib = _get_lib()
|
|
224
|
+
c_args = [np_to_pymat(a) if isinstance(a, np.ndarray) else a for a in args]
|
|
225
|
+
ret = func(*c_args)
|
|
226
|
+
if not ret.data_ptr:
|
|
227
|
+
# the error message is kept per thread, like the call itself
|
|
228
|
+
message = lib.PM_last_error().decode(errors="replace").strip()
|
|
229
|
+
raise RuntimeError(f"patchmatch failed: {message}")
|
|
230
|
+
try:
|
|
231
|
+
return pymat_to_np(ret)
|
|
232
|
+
finally:
|
|
233
|
+
lib.PM_free_pymat(ret)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _canonize_image_array(image: ImageLike) -> np.ndarray:
|
|
237
|
+
image = np.asarray(image)
|
|
238
|
+
if not (image.ndim == 3 and image.shape[2] == 3 and image.dtype == np.uint8):
|
|
239
|
+
raise ValueError(
|
|
240
|
+
"image must be an HxWx3 uint8 array, "
|
|
241
|
+
f"got shape {image.shape} and dtype {image.dtype}"
|
|
242
|
+
)
|
|
243
|
+
return np.ascontiguousarray(image)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _canonize_mask_array(mask: ImageLike) -> np.ndarray:
|
|
247
|
+
mask = np.asarray(mask)
|
|
248
|
+
if mask.dtype == np.bool_: # e.g. boolean arrays or PIL images in mode "1"
|
|
249
|
+
mask = mask.astype(np.uint8)
|
|
250
|
+
if mask.ndim == 2:
|
|
251
|
+
mask = mask[..., np.newaxis]
|
|
252
|
+
if not (mask.ndim == 3 and mask.shape[2] == 1 and mask.dtype == np.uint8):
|
|
253
|
+
raise ValueError(
|
|
254
|
+
"mask must be an HxW or HxWx1 uint8 array, "
|
|
255
|
+
f"got shape {mask.shape} and dtype {mask.dtype}"
|
|
256
|
+
)
|
|
257
|
+
return np.ascontiguousarray(mask)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _default_mask(image: np.ndarray) -> np.ndarray:
|
|
261
|
+
"""Treat all pure white pixels as holes."""
|
|
262
|
+
mask: np.ndarray = (image == 255).all(axis=2, keepdims=True)
|
|
263
|
+
return mask.astype(np.uint8)
|
|
Binary file
|
patchmatch/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyPatchMatch
|
|
3
|
+
Version: 2.2.0rc2
|
|
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.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
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.11
|
|
27
|
+
Requires-Dist: numpy>=1.23.2
|
|
28
|
+
Requires-Dist: pillow>=9.2.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# PatchMatch based Inpainting
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/PyPatchMatch/)
|
|
34
|
+
[](https://pypi.org/project/PyPatchMatch/)
|
|
35
|
+
[](https://pepy.tech/projects/pypatchmatch)
|
|
36
|
+
[](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
|
|
37
|
+
|
|
38
|
+
[](https://github.com/mauwii/PyPatchMatch/actions/workflows/ci.yml?query=branch%3Amain)
|
|
39
|
+
[](https://sonarcloud.io/component_measures?id=mauwii_PyPatchMatch&metric=coverage)
|
|
40
|
+
[](https://sonarcloud.io/summary/new_code?id=mauwii_PyPatchMatch)
|
|
41
|
+
|
|
42
|
+
[](https://github.com/astral-sh/uv)
|
|
43
|
+
[](https://github.com/astral-sh/ruff)
|
|
44
|
+
[](https://github.com/DietrichGebert/ponytail)
|
|
45
|
+
|
|
46
|
+
This library implements the PatchMatch based inpainting algorithm. It provides both C++
|
|
47
|
+
and Python interfaces. This implementation is heavily based on the implementation by
|
|
48
|
+
Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
|
|
49
|
+
with some bug fixes, and updates. This repository continues
|
|
50
|
+
[vacancy/PyPatchMatch](https://github.com/vacancy/PyPatchMatch) by Jiayuan Mao, through
|
|
51
|
+
the forks [lkwq007/PyPatchMatch](https://github.com/lkwq007/PyPatchMatch) and
|
|
52
|
+
[invoke-ai/PyPatchMatch](https://github.com/invoke-ai/PyPatchMatch).
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
From left to right: the photo, the input of
|
|
57
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)
|
|
58
|
+
with the birdhouses painted out in white, and its result.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pip install PyPatchMatch
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
|
|
67
|
+
(x64 and ARM64) ship the compiled library with a statically linked OpenCV, so no
|
|
68
|
+
compiler or OpenCV installation is needed. On other platforms, e.g. Alpine Linux, pip
|
|
69
|
+
builds from the source distribution, which requires a C++17 compiler, CMake and the
|
|
70
|
+
OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
|
|
71
|
+
|
|
72
|
+
If that build fails, PyPatchMatch is installed without the native library instead of
|
|
73
|
+
blocking the installation: `patchmatch.patchmatch_available` is `False` and the
|
|
74
|
+
inpainting functions raise `RuntimeError`. After installing the missing tools, reinstall
|
|
75
|
+
it without the cached build:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
pip install --force-reinstall --no-deps --no-cache-dir PyPatchMatch
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
Python (see
|
|
84
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import patchmatch
|
|
88
|
+
|
|
89
|
+
image = ... # HxWx3 uint8 numpy array or PIL image
|
|
90
|
+
mask = ... # HxW uint8 or bool numpy array or PIL image, non-zero marks the holes
|
|
91
|
+
result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
95
|
+
pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
|
|
96
|
+
their values, and the colors under the holes have no influence on the fill. Holes need
|
|
97
|
+
hard edges: an anti-aliased brush leaves light pixels along the edge of its strokes that
|
|
98
|
+
are not pure white, so they count as known and the fill continues them. Paint without
|
|
99
|
+
anti-aliasing or pass a mask that covers those pixels as well. The optional keyword
|
|
100
|
+
argument `global_mask`, in the same format, marks pixels that are neither filled nor
|
|
101
|
+
used as a source; they keep their values as well, and their colors have no influence on
|
|
102
|
+
the fill (see
|
|
103
|
+
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
104
|
+
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
105
|
+
The previous import path `from patchmatch import patch_match` keeps working.
|
|
106
|
+
|
|
107
|
+
`patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
|
|
108
|
+
pixels in each direction. Larger patches follow larger structures but are much slower;
|
|
109
|
+
the default of 15 compares 31x31 patches, the examples use 3. Below 5, the coarse levels
|
|
110
|
+
that lay out the fill are also tried with a radius of 5, which is kept where it fits
|
|
111
|
+
the surroundings better, so that repeating patterns continue across larger holes. Above
|
|
112
|
+
7, coarse levels narrower than six patches use the largest radius that fits, but at
|
|
113
|
+
least 7: larger patches found no sources there and left the fill flat.
|
|
114
|
+
|
|
115
|
+
`patchmatch.set_random_seed(seed)` sets the seed of the randomized search, modulo
|
|
116
|
+
`2**32`, and `patchmatch.set_verbose(True)` prints the progress of the native code to
|
|
117
|
+
stderr. `patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the
|
|
118
|
+
search with a regularity map, an HxWx3 float32 array with the regularity coordinates of
|
|
119
|
+
each pixel in its first two channels. Its `guide_weight` must be between 0 and the
|
|
120
|
+
largest float32, about 3.4e38.
|
|
121
|
+
|
|
122
|
+
C++ (see
|
|
123
|
+
[examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
|
|
124
|
+
build and run it with
|
|
125
|
+
[examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
|
|
126
|
+
|
|
127
|
+
```cpp
|
|
128
|
+
#include "inpaint.h"
|
|
129
|
+
|
|
130
|
+
int main() {
|
|
131
|
+
cv::Mat image = ...;
|
|
132
|
+
cv::Mat mask = ...;
|
|
133
|
+
|
|
134
|
+
auto metric = PatchSSDDistanceMetric(5);
|
|
135
|
+
cv::Mat result = Inpainting(image, mask, &metric).run();
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The library is built with CMake; `PATCHMATCH_BUILD_EXAMPLES` builds the example.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
The project is managed with [uv](https://docs.astral.sh/uv/):
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
uv sync # create .venv, build the library, install dev deps
|
|
147
|
+
uv run pre-commit install # enable ruff and the other hooks on commit
|
|
148
|
+
uv run pytest # run the test suite
|
|
149
|
+
uv run pytest -m "not e2e" # only the fast unit tests
|
|
150
|
+
uv run python scripts/evaluate_inpainting.py # measure the fill quality
|
|
151
|
+
uv run --group gui gui/app.py # draw masks in the browser, http://127.0.0.1:7860
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The GUI tries new images quickly, without writing a script for each one, e.g. to pick
|
|
155
|
+
new examples or to verify edge cases: load an image, paint the holes (every painted
|
|
156
|
+
pixel is one) and compare the original with the fill. Its test runs with
|
|
157
|
+
`uv run --group gui pytest gui --no-cov`.
|
|
158
|
+
|
|
159
|
+
The evaluation cuts holes into the images of `examples/images`, compares the fills with
|
|
160
|
+
the original content and writes `examples/images/evaluation.html`, which shows them side
|
|
161
|
+
by side. `--baseline` evaluates another build of the library as well, e.g. of the main
|
|
162
|
+
branch; the docstring of the script explains the columns.
|
|
163
|
+
Pull requests that change the C++ code get this comparison with their base commit in the
|
|
164
|
+
summary of the workflow "Fill quality", and the HTML report as its artifact.
|
|
165
|
+
|
|
166
|
+
Releases are published to PyPI by creating a GitHub release; the version is taken from
|
|
167
|
+
its tag (e.g. `v2.0.0`). Every released wheel and sdist carries a build provenance
|
|
168
|
+
attestation: `gh attestation verify <file> -R mauwii/PyPatchMatch`.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
PyPatchMatch is released under the
|
|
173
|
+
[MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
|
|
174
|
+
contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
|
|
175
|
+
third-party code; their licenses are included in the `licenses/opencv` directory of the
|
|
176
|
+
wheel's `.dist-info`, and a CycloneDX SBOM of OpenCV (PEP 770) in
|
|
177
|
+
`sboms/opencv.cdx.json`, so that vulnerability scanners can find it.
|
|
178
|
+
|
|
179
|
+
## README and COPYRIGHT by Younesse ANDAM
|
|
180
|
+
|
|
181
|
+
@Author: Younesse ANDAM
|
|
182
|
+
|
|
183
|
+
@Contact: <younesse.andam@gmail.com>
|
|
184
|
+
|
|
185
|
+
Description:
|
|
186
|
+
|
|
187
|
+
This project is a personal implementation of an algorithm called PATCHMATCH
|
|
188
|
+
that restores missing areas in an image. The algorithm is presented in the following
|
|
189
|
+
paper PatchMatch A Randomized Correspondence Algorithm for Structural Image Editing by
|
|
190
|
+
C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graphics
|
|
191
|
+
(Proc. SIGGRAPH), vol.28, aug-2009
|
|
192
|
+
|
|
193
|
+
For more information please refer to
|
|
194
|
+
<https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
|
|
195
|
+
|
|
196
|
+
Copyright (c) 2010-2011
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
patchmatch/__init__.py,sha256=Xg_AyDjCY9obrk95tzu6h0ox1fttEczi_OKu3ycsSvM,345
|
|
2
|
+
patchmatch/_lib.py,sha256=IpCdxc_mG2rT8RzPBTMcZgWV_JSkfwFa_JL8o6F-qls,3836
|
|
3
|
+
patchmatch/_version.py,sha256=4gRpHk6EKJHIovSB1EqnjgsNLMsqQ7EFFDDUPxPRz-A,554
|
|
4
|
+
patchmatch/patch_match.py,sha256=TlmBMxgqk5vdepAoZgduy2axJJBHHYLFuMENc3DnIv4,9424
|
|
5
|
+
patchmatch/patchmatch.dll,sha256=mkA3BrRO6x0g9VemPX9Nk8NFInWunLTNDRIIoDzrqvU,1342464
|
|
6
|
+
patchmatch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
pypatchmatch-2.2.0rc2.dist-info/METADATA,sha256=JEOUhVW09izw50vsQTly3w4fJcCC-x1DwaU-0aJNSyc,10165
|
|
8
|
+
pypatchmatch-2.2.0rc2.dist-info/WHEEL,sha256=D69HPxC3SlbabnD-f_ROoJhjrQemtxP7K3VXxdgu2k4,102
|
|
9
|
+
pypatchmatch-2.2.0rc2.dist-info/licenses/LICENSE,sha256=6VGSBzoFC-2RWaUpfSom3IlBW3AZfOwDuZrVzOmfN_E,1101
|
|
10
|
+
pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/dlpack-LICENSE,sha256=Po9rw5J2pYbWDIxars4v-YkmIYHPKz0qEo21j5epMAw,11345
|
|
11
|
+
pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/flatbuffers-LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
12
|
+
pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/opencv-LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
13
|
+
pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/SoftFloat-COPYING.txt,sha256=DFsINlE22lJjnXd_Hr_AOYiwFp2AZVyQxZ8bP6PlJqI,1732
|
|
14
|
+
pypatchmatch-2.2.0rc2.dist-info/licenses/opencv/zlib-LICENSE,sha256=4y_04A2dlJMFN2NSkdo55-YScDM0v2_ejH8Whv6KRaI,1002
|
|
15
|
+
pypatchmatch-2.2.0rc2.dist-info/sboms/opencv.cdx.json,sha256=ATex-BysD8SgsmzwbF8T3jwHedHA_RBYyqgJlC-Amew,866
|
|
16
|
+
pypatchmatch-2.2.0rc2.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Matthias Wild
|
|
4
|
+
Copyright (c) 2020 Jiayuan Mao
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|