patchmatch-cython 0.2.0__tar.gz → 1.0.0__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.
- {patchmatch_cython-0.2.0/src/patchmatch_cython.egg-info → patchmatch_cython-1.0.0}/PKG-INFO +113 -43
- patchmatch_cython-1.0.0/README.md +254 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/local_build.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/pyproject.toml +2 -2
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/setup.py +44 -8
- patchmatch_cython-1.0.0/src/patchmatch_cython/__init__.py +51 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/cython_solver.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/cython_solver_impl.cpp +1604 -1567
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/cython_solver_impl.pyx +29 -0
- patchmatch_cython-1.0.0/src/patchmatch_cython/exceptions.py +46 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/imageops.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/inpaint.py +173 -63
- patchmatch_cython-1.0.0/src/patchmatch_cython/philippeau.cpp +65351 -0
- patchmatch_cython-1.0.0/src/patchmatch_cython/philippeau.pyx +1612 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/python_solver.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython/solver.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0/src/patchmatch_cython.egg-info}/PKG-INFO +113 -43
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython.egg-info/SOURCES.txt +4 -0
- patchmatch_cython-1.0.0/tests/test_basic_functionality.py +58 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/tests/test_imageops.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/tests/test_inpaint_api.py +29 -0
- patchmatch_cython-1.0.0/tests/test_philippeau.py +171 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/tests/test_quality_options.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/tests/test_questionable_behaviors.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/tests/test_solvers.py +29 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/tests/test_validation.py +29 -0
- patchmatch_cython-0.2.0/README.md +0 -184
- patchmatch_cython-0.2.0/src/patchmatch_cython/__init__.py +0 -19
- patchmatch_cython-0.2.0/tests/test_basic_functionality.py +0 -29
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/LICENSE +0 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/MANIFEST.in +0 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/setup.cfg +0 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython.egg-info/dependency_links.txt +0 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython.egg-info/not-zip-safe +0 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython.egg-info/requires.txt +0 -0
- {patchmatch_cython-0.2.0 → patchmatch_cython-1.0.0}/src/patchmatch_cython.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchmatch-cython
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: High-performance PatchMatch implementation for image inpainting using Cython
|
|
5
5
|
Home-page: https://github.com/Teriks/patchmatch-cython
|
|
6
6
|
Author: Teriks
|
|
@@ -37,7 +37,7 @@ Project-URL: Homepage, https://github.com/Teriks/patchmatch-cython
|
|
|
37
37
|
Project-URL: Repository, https://github.com/Teriks/patchmatch-cython
|
|
38
38
|
Project-URL: Issues, https://github.com/Teriks/patchmatch-cython/issues
|
|
39
39
|
Keywords: image-processing,inpainting,patchmatch,cython,computer-vision
|
|
40
|
-
Classifier: Development Status ::
|
|
40
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
41
41
|
Classifier: Intended Audience :: Developers
|
|
42
42
|
Classifier: Intended Audience :: Science/Research
|
|
43
43
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -70,7 +70,7 @@ Dynamic: requires-python
|
|
|
70
70
|
|
|
71
71
|
# PatchMatch Cython
|
|
72
72
|
|
|
73
|
-
High-performance PatchMatch implementation for pyramidical image inpainting in Cython.
|
|
73
|
+
High-performance PatchMatch implementation for pyramidical image inpainting in Cython. The default solver is the Barnes-style pyramid below. `algorithm="philippeau"` runs the Xavier Philippeau pyramid, also in Cython.
|
|
74
74
|
|
|
75
75
|
This package can function without OpenCV (`opencv-python` / `opencv-python-headless`) with only slightly reduced quality and speed in a few helpers. When OpenCV is installed it is used for resize (`INTER_LINEAR` / `INTER_AREA`), morphology for valid/query centers, and distance transforms for seam feathering.
|
|
76
76
|
|
|
@@ -91,69 +91,101 @@ pip install .
|
|
|
91
91
|
|
|
92
92
|
## Usage
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Images are `HxWxC`. Masks are `HxW`: `True` or any non-zero value is the hole. The result is `uint8`, and pixels outside the hole are unchanged. `inpaint_pyramid` picks `CythonSolver` when the extension is built, and `PythonSolver` otherwise. Pass `solver_class=PythonSolver` to force the pure Python path.
|
|
95
|
+
|
|
96
|
+
### Remove a region from a photo
|
|
95
97
|
|
|
96
98
|
```python
|
|
97
99
|
import cv2
|
|
98
100
|
from patchmatch_cython import inpaint_pyramid
|
|
99
101
|
|
|
100
|
-
image = cv2.imread(
|
|
101
|
-
mask = cv2.imread(
|
|
102
|
+
image = cv2.imread("photo.jpg") # BGR uint8
|
|
103
|
+
mask = cv2.imread("hole.png", cv2.IMREAD_GRAYSCALE) > 128
|
|
102
104
|
|
|
103
|
-
result = inpaint_pyramid(image, mask)
|
|
104
|
-
cv2.imwrite(
|
|
105
|
+
result = inpaint_pyramid(image, mask, seed=42)
|
|
106
|
+
cv2.imwrite("filled.jpg", result)
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
The same job with Pillow. Convert to RGB so a PNG with an alpha channel stays three channels.
|
|
108
110
|
|
|
109
111
|
```python
|
|
110
112
|
import numpy as np
|
|
111
113
|
from PIL import Image
|
|
112
114
|
from patchmatch_cython import inpaint_pyramid
|
|
113
115
|
|
|
114
|
-
image = np.array(Image.open(
|
|
115
|
-
mask = np.array(Image.open(
|
|
116
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
117
|
+
mask = np.array(Image.open("hole.png").convert("L")) > 128
|
|
116
118
|
|
|
117
|
-
result = inpaint_pyramid(image, mask)
|
|
118
|
-
Image.fromarray(result).save(
|
|
119
|
+
result = inpaint_pyramid(image, mask, seed=42)
|
|
120
|
+
Image.fromarray(result).save("filled.jpg")
|
|
119
121
|
```
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
A rectangle is enough when you do not have a mask file. `seed` makes the fill repeatable; omit it and each run can differ.
|
|
122
124
|
|
|
123
125
|
```python
|
|
124
|
-
|
|
126
|
+
import numpy as np
|
|
127
|
+
from PIL import Image
|
|
128
|
+
from patchmatch_cython import inpaint_pyramid
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
131
|
+
mask = np.zeros(image.shape[:2], dtype=bool)
|
|
132
|
+
mask[120:200, 80:160] = True
|
|
133
|
+
|
|
134
|
+
result = inpaint_pyramid(image, mask, seed=42)
|
|
135
|
+
Image.fromarray(result).save("filled.jpg")
|
|
128
136
|
```
|
|
129
137
|
|
|
130
|
-
###
|
|
138
|
+
### Thin scratch, one scale
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
Defaults preserve a sensible baseline; turn options on when you need more structure,
|
|
134
|
-
completeness, or softer seams.
|
|
140
|
+
`inpaint_single` stays at full resolution. Use it for a narrow mark, where a pyramid has little to add. `iterations` is the number of match passes.
|
|
135
141
|
|
|
136
142
|
```python
|
|
137
|
-
|
|
143
|
+
import numpy as np
|
|
144
|
+
from PIL import Image
|
|
145
|
+
from patchmatch_cython import inpaint_single
|
|
146
|
+
|
|
147
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
148
|
+
mask = np.zeros(image.shape[:2], dtype=bool)
|
|
149
|
+
mask[240:244, 40:400] = True
|
|
150
|
+
|
|
151
|
+
result = inpaint_single(image, mask, iterations=5, seed=42)
|
|
152
|
+
Image.fromarray(result).save("scratch.jpg")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Lines, texture, and a soft rim
|
|
156
|
+
|
|
157
|
+
These keywords apply to `algorithm="patchmatch"`. Passing any of them with `algorithm="philippeau"` raises `UnsupportedOption`, including when the value is the usual default. An unknown keyword raises `TypeError`.
|
|
138
158
|
|
|
159
|
+
Turn on structure matching when the hole crosses an edge or a repeating texture. `edge_weighted` matches gradients as well as color. `reverse_search` pulls in source patches the first pass underused. `hole_refine` runs a few extra full-resolution passes on the hole only.
|
|
160
|
+
|
|
161
|
+
```python
|
|
139
162
|
result = inpaint_pyramid(
|
|
140
163
|
image,
|
|
141
164
|
mask,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
reverse_search=True, # source→query completeness pass + stronger usage penalty
|
|
147
|
-
poisson_blend=True, # harmonic seam soften (default ~8px band)
|
|
148
|
-
hole_refine=3, # extra full-res PatchMatch iterations after the main solve
|
|
149
|
-
mask_feather=6.0, # soft compose width in px from the hole boundary
|
|
165
|
+
seed=42,
|
|
166
|
+
edge_weighted=True,
|
|
167
|
+
reverse_search=True,
|
|
168
|
+
hole_refine=2,
|
|
150
169
|
)
|
|
151
170
|
```
|
|
152
171
|
|
|
172
|
+
The default already blends a narrow band along the hole rim (`poisson_blend=True`). Widen that band when the seam is still visible. `mask_feather` is a width in pixels and replaces the default rim blend.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
result = inpaint_pyramid(image, mask, seed=42, mask_feather=8.0)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`patch_size` is the square patch side and must be odd. A larger side sees more context and costs more. `em_loops` repeats match and vote on the coarse pyramid levels.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
result = inpaint_pyramid(image, mask, patch_size=7, em_loops=2, seed=42)
|
|
182
|
+
```
|
|
183
|
+
|
|
153
184
|
| Parameter | Default | Description |
|
|
154
185
|
|---|---|---|
|
|
155
|
-
| `patch_size` | `5` | Odd positive int; square patch
|
|
156
|
-
| `seed` | `None` | RNG seed
|
|
186
|
+
| `patch_size` | `5` | Odd positive int; square patch side for matching and voting. |
|
|
187
|
+
| `seed` | `None` | RNG seed. `None` leaves the default solver unseeded. |
|
|
188
|
+
| `solver_class` | `None` | `None` uses `CythonSolver` when the extension is available, otherwise `PythonSolver`. |
|
|
157
189
|
| `em_loops` | `1` | Outer match→paint→rematch loops. On pyramids, applied at coarse levels only (finest stays `1`). |
|
|
158
190
|
| `edge_weighted` | `False` | `False` = color-only matching. `True` = color + boosted gradient channels. Hole-touching gradients are suppressed so zeroed holes do not create fake edges. |
|
|
159
191
|
| `reverse_search` | `False` | After each EM loop, try assigning underused near-hole sources to better queries (completeness). |
|
|
@@ -161,25 +193,63 @@ result = inpaint_pyramid(
|
|
|
161
193
|
| `hole_refine` | `0` | Extra full-resolution PatchMatch iterations after the main solve (`0` disables). Only hole pixels are painted; the user mask is not modified. |
|
|
162
194
|
| `mask_feather` | `0.0` | Soft compose width in pixels from the hole boundary. Uses a distance ramp toward a harmonic rim pull (not the original hole content). Does **not** change the mask used for matching. |
|
|
163
195
|
|
|
164
|
-
`inpaint_single`
|
|
196
|
+
`inpaint_single` takes the same keywords, plus `iterations` (default `5`).
|
|
197
|
+
|
|
198
|
+
Masks may be `bool`, `{0,1}`, or `{0,255}`. The library does not dilate or erode the mask used for matching. `mask_feather` only changes the final composite.
|
|
199
|
+
|
|
200
|
+
### Xavier Philippeau
|
|
201
|
+
|
|
202
|
+
`algorithm="philippeau"`, or `inpaint_philippeau`, runs Xavier Philippeau's pyramid: bidirectional nearest-neighbor fields, a texture-energy channel, split votes on the fine levels, and a short border blend. The implementation is Cython and does not use OpenCV. Known pixels stay exact. Passing a PatchMatch keyword (`solver_class`, `em_loops`, `edge_weighted`, `reverse_search`, `poisson_blend`, `hole_refine`, `mask_feather`, or `iterations`) raises `UnsupportedOption`.
|
|
203
|
+
|
|
204
|
+
In this mode `patch_size` is a **radius**: each patch spans `2 * patch_size + 1` pixels. Any integer from 1 up is valid, including even values. A radius of 3 is a 7×7 patch. `inpaint_philippeau` defaults to radius 15. `inpaint_pyramid` and `inpaint_single` still default `patch_size` to 5, and that 5 is a radius here.
|
|
205
|
+
|
|
206
|
+
Pass `seed` for a repeatable fill. If it is omitted, a 32-bit seed is drawn for that run.
|
|
207
|
+
|
|
208
|
+
`inpaint_philippeau` requires an HxWx3 `uint8` image. `inpaint_pyramid` and `inpaint_single` round other numeric images into `uint8` and clip them to 0–255 before filling. The same `uint8` image, mask, radius, and seed produce the same fill from either call.
|
|
209
|
+
|
|
210
|
+
Fill a photo. Radius 7 is a 15×15 patch, a practical size for a region a few dozen pixels across. Radius 15 is the default on `inpaint_philippeau` and suits a larger hole.
|
|
165
211
|
|
|
166
212
|
```python
|
|
167
|
-
|
|
213
|
+
import numpy as np
|
|
214
|
+
from PIL import Image
|
|
215
|
+
from patchmatch_cython import inpaint_philippeau
|
|
216
|
+
|
|
217
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
218
|
+
mask = np.array(Image.open("hole.png").convert("L")) > 0
|
|
219
|
+
|
|
220
|
+
result = inpaint_philippeau(image, mask, patch_size=7, seed=42)
|
|
221
|
+
Image.fromarray(result).save("filled.jpg")
|
|
222
|
+
```
|
|
168
223
|
|
|
169
|
-
|
|
224
|
+
`inpaint_pyramid(..., algorithm="philippeau")` is the same fill when the image is already `uint8` and `patch_size` and `seed` match.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from patchmatch_cython import inpaint_pyramid
|
|
228
|
+
|
|
229
|
+
result = inpaint_pyramid(image, mask, patch_size=7, seed=42, algorithm="philippeau")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
If `mask` is omitted, pure white pixels (`255, 255, 255`) are the holes. Use that for a plate or sky that was painted white.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
result = inpaint_philippeau(image, patch_size=7, seed=42)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`global_mask` marks pixels that must not be filled and must not be copied from, such as a logo or a frame. `ijmap` is an optional HxWx3 `float32` regularity map; the first two channels are read, and `guide_weight` (default `0.25`) sets its weight. `inpaint_pyramid` and `inpaint_single` do not take these arguments.
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
keep_out = np.zeros(image.shape[:2], dtype=bool)
|
|
242
|
+
keep_out[0:40, 0:120] = True # logo in the corner
|
|
243
|
+
|
|
244
|
+
result = inpaint_philippeau(
|
|
170
245
|
image,
|
|
171
246
|
mask,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
hole_refine=2,
|
|
176
|
-
mask_feather=4.0,
|
|
177
|
-
poisson_blend=False, # feather handles seams when mask_feather > 0
|
|
247
|
+
global_mask=keep_out,
|
|
248
|
+
patch_size=7,
|
|
249
|
+
seed=42,
|
|
178
250
|
)
|
|
179
251
|
```
|
|
180
252
|
|
|
181
|
-
**Masks:** accept `bool`, `{0,1}`, or `{0,255}`. `True` / non-zero = region to inpaint. Known (unmasked) pixels are preserved in the output. The library never dilates/erodes the user mask for matching; `mask_feather` only affects final compositing.
|
|
182
|
-
|
|
183
253
|
**OpenCV runtime toggle** (optional):
|
|
184
254
|
|
|
185
255
|
```python
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# PatchMatch Cython
|
|
2
|
+
|
|
3
|
+
High-performance PatchMatch implementation for pyramidical image inpainting in Cython. The default solver is the Barnes-style pyramid below. `algorithm="philippeau"` runs the Xavier Philippeau pyramid, also in Cython.
|
|
4
|
+
|
|
5
|
+
This package can function without OpenCV (`opencv-python` / `opencv-python-headless`) with only slightly reduced quality and speed in a few helpers. When OpenCV is installed it is used for resize (`INTER_LINEAR` / `INTER_AREA`), morphology for valid/query centers, and distance transforms for seam feathering.
|
|
6
|
+
|
|
7
|
+
The package provides a pure Python solver and a Cython solver; the Cython solver is typically about 30–50× faster depending on patch size.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install patchmatch-cython
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**From source:**
|
|
16
|
+
```bash
|
|
17
|
+
git clone <repository-url>
|
|
18
|
+
cd patchmatch-cython
|
|
19
|
+
pip install .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Images are `HxWxC`. Masks are `HxW`: `True` or any non-zero value is the hole. The result is `uint8`, and pixels outside the hole are unchanged. `inpaint_pyramid` picks `CythonSolver` when the extension is built, and `PythonSolver` otherwise. Pass `solver_class=PythonSolver` to force the pure Python path.
|
|
25
|
+
|
|
26
|
+
### Remove a region from a photo
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import cv2
|
|
30
|
+
from patchmatch_cython import inpaint_pyramid
|
|
31
|
+
|
|
32
|
+
image = cv2.imread("photo.jpg") # BGR uint8
|
|
33
|
+
mask = cv2.imread("hole.png", cv2.IMREAD_GRAYSCALE) > 128
|
|
34
|
+
|
|
35
|
+
result = inpaint_pyramid(image, mask, seed=42)
|
|
36
|
+
cv2.imwrite("filled.jpg", result)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The same job with Pillow. Convert to RGB so a PNG with an alpha channel stays three channels.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import numpy as np
|
|
43
|
+
from PIL import Image
|
|
44
|
+
from patchmatch_cython import inpaint_pyramid
|
|
45
|
+
|
|
46
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
47
|
+
mask = np.array(Image.open("hole.png").convert("L")) > 128
|
|
48
|
+
|
|
49
|
+
result = inpaint_pyramid(image, mask, seed=42)
|
|
50
|
+
Image.fromarray(result).save("filled.jpg")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A rectangle is enough when you do not have a mask file. `seed` makes the fill repeatable; omit it and each run can differ.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import numpy as np
|
|
57
|
+
from PIL import Image
|
|
58
|
+
from patchmatch_cython import inpaint_pyramid
|
|
59
|
+
|
|
60
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
61
|
+
mask = np.zeros(image.shape[:2], dtype=bool)
|
|
62
|
+
mask[120:200, 80:160] = True
|
|
63
|
+
|
|
64
|
+
result = inpaint_pyramid(image, mask, seed=42)
|
|
65
|
+
Image.fromarray(result).save("filled.jpg")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Thin scratch, one scale
|
|
69
|
+
|
|
70
|
+
`inpaint_single` stays at full resolution. Use it for a narrow mark, where a pyramid has little to add. `iterations` is the number of match passes.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import numpy as np
|
|
74
|
+
from PIL import Image
|
|
75
|
+
from patchmatch_cython import inpaint_single
|
|
76
|
+
|
|
77
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
78
|
+
mask = np.zeros(image.shape[:2], dtype=bool)
|
|
79
|
+
mask[240:244, 40:400] = True
|
|
80
|
+
|
|
81
|
+
result = inpaint_single(image, mask, iterations=5, seed=42)
|
|
82
|
+
Image.fromarray(result).save("scratch.jpg")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Lines, texture, and a soft rim
|
|
86
|
+
|
|
87
|
+
These keywords apply to `algorithm="patchmatch"`. Passing any of them with `algorithm="philippeau"` raises `UnsupportedOption`, including when the value is the usual default. An unknown keyword raises `TypeError`.
|
|
88
|
+
|
|
89
|
+
Turn on structure matching when the hole crosses an edge or a repeating texture. `edge_weighted` matches gradients as well as color. `reverse_search` pulls in source patches the first pass underused. `hole_refine` runs a few extra full-resolution passes on the hole only.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
result = inpaint_pyramid(
|
|
93
|
+
image,
|
|
94
|
+
mask,
|
|
95
|
+
seed=42,
|
|
96
|
+
edge_weighted=True,
|
|
97
|
+
reverse_search=True,
|
|
98
|
+
hole_refine=2,
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The default already blends a narrow band along the hole rim (`poisson_blend=True`). Widen that band when the seam is still visible. `mask_feather` is a width in pixels and replaces the default rim blend.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
result = inpaint_pyramid(image, mask, seed=42, mask_feather=8.0)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`patch_size` is the square patch side and must be odd. A larger side sees more context and costs more. `em_loops` repeats match and vote on the coarse pyramid levels.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
result = inpaint_pyramid(image, mask, patch_size=7, em_loops=2, seed=42)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Parameter | Default | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `patch_size` | `5` | Odd positive int; square patch side for matching and voting. |
|
|
117
|
+
| `seed` | `None` | RNG seed. `None` leaves the default solver unseeded. |
|
|
118
|
+
| `solver_class` | `None` | `None` uses `CythonSolver` when the extension is available, otherwise `PythonSolver`. |
|
|
119
|
+
| `em_loops` | `1` | Outer match→paint→rematch loops. On pyramids, applied at coarse levels only (finest stays `1`). |
|
|
120
|
+
| `edge_weighted` | `False` | `False` = color-only matching. `True` = color + boosted gradient channels. Hole-touching gradients are suppressed so zeroed holes do not create fake edges. |
|
|
121
|
+
| `reverse_search` | `False` | After each EM loop, try assigning underused near-hole sources to better queries (completeness). |
|
|
122
|
+
| `poisson_blend` | `True` | Soften seams by blending the PatchMatch fill with a harmonic membrane near the hole rim. Ignored when `mask_feather > 0` (feather supplies the band). |
|
|
123
|
+
| `hole_refine` | `0` | Extra full-resolution PatchMatch iterations after the main solve (`0` disables). Only hole pixels are painted; the user mask is not modified. |
|
|
124
|
+
| `mask_feather` | `0.0` | Soft compose width in pixels from the hole boundary. Uses a distance ramp toward a harmonic rim pull (not the original hole content). Does **not** change the mask used for matching. |
|
|
125
|
+
|
|
126
|
+
`inpaint_single` takes the same keywords, plus `iterations` (default `5`).
|
|
127
|
+
|
|
128
|
+
Masks may be `bool`, `{0,1}`, or `{0,255}`. The library does not dilate or erode the mask used for matching. `mask_feather` only changes the final composite.
|
|
129
|
+
|
|
130
|
+
### Xavier Philippeau
|
|
131
|
+
|
|
132
|
+
`algorithm="philippeau"`, or `inpaint_philippeau`, runs Xavier Philippeau's pyramid: bidirectional nearest-neighbor fields, a texture-energy channel, split votes on the fine levels, and a short border blend. The implementation is Cython and does not use OpenCV. Known pixels stay exact. Passing a PatchMatch keyword (`solver_class`, `em_loops`, `edge_weighted`, `reverse_search`, `poisson_blend`, `hole_refine`, `mask_feather`, or `iterations`) raises `UnsupportedOption`.
|
|
133
|
+
|
|
134
|
+
In this mode `patch_size` is a **radius**: each patch spans `2 * patch_size + 1` pixels. Any integer from 1 up is valid, including even values. A radius of 3 is a 7×7 patch. `inpaint_philippeau` defaults to radius 15. `inpaint_pyramid` and `inpaint_single` still default `patch_size` to 5, and that 5 is a radius here.
|
|
135
|
+
|
|
136
|
+
Pass `seed` for a repeatable fill. If it is omitted, a 32-bit seed is drawn for that run.
|
|
137
|
+
|
|
138
|
+
`inpaint_philippeau` requires an HxWx3 `uint8` image. `inpaint_pyramid` and `inpaint_single` round other numeric images into `uint8` and clip them to 0–255 before filling. The same `uint8` image, mask, radius, and seed produce the same fill from either call.
|
|
139
|
+
|
|
140
|
+
Fill a photo. Radius 7 is a 15×15 patch, a practical size for a region a few dozen pixels across. Radius 15 is the default on `inpaint_philippeau` and suits a larger hole.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import numpy as np
|
|
144
|
+
from PIL import Image
|
|
145
|
+
from patchmatch_cython import inpaint_philippeau
|
|
146
|
+
|
|
147
|
+
image = np.array(Image.open("photo.jpg").convert("RGB"))
|
|
148
|
+
mask = np.array(Image.open("hole.png").convert("L")) > 0
|
|
149
|
+
|
|
150
|
+
result = inpaint_philippeau(image, mask, patch_size=7, seed=42)
|
|
151
|
+
Image.fromarray(result).save("filled.jpg")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`inpaint_pyramid(..., algorithm="philippeau")` is the same fill when the image is already `uint8` and `patch_size` and `seed` match.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from patchmatch_cython import inpaint_pyramid
|
|
158
|
+
|
|
159
|
+
result = inpaint_pyramid(image, mask, patch_size=7, seed=42, algorithm="philippeau")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
If `mask` is omitted, pure white pixels (`255, 255, 255`) are the holes. Use that for a plate or sky that was painted white.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
result = inpaint_philippeau(image, patch_size=7, seed=42)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`global_mask` marks pixels that must not be filled and must not be copied from, such as a logo or a frame. `ijmap` is an optional HxWx3 `float32` regularity map; the first two channels are read, and `guide_weight` (default `0.25`) sets its weight. `inpaint_pyramid` and `inpaint_single` do not take these arguments.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
keep_out = np.zeros(image.shape[:2], dtype=bool)
|
|
172
|
+
keep_out[0:40, 0:120] = True # logo in the corner
|
|
173
|
+
|
|
174
|
+
result = inpaint_philippeau(
|
|
175
|
+
image,
|
|
176
|
+
mask,
|
|
177
|
+
global_mask=keep_out,
|
|
178
|
+
patch_size=7,
|
|
179
|
+
seed=42,
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**OpenCV runtime toggle** (optional):
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from patchmatch_cython.imageops import set_use_opencv, opencv_available
|
|
187
|
+
|
|
188
|
+
set_use_opencv(False) # force NumPy fallbacks even if OpenCV is installed
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Package extras
|
|
192
|
+
|
|
193
|
+
**For examples:**
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pip install patchmatch-cython[visualization]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Adds: `matplotlib`, `Pillow`
|
|
200
|
+
|
|
201
|
+
**For development:**
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
pip install patchmatch-cython[dev]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Adds: `pytest`, `build`, `cibuildwheel`, `setuptools`, `wheel`
|
|
208
|
+
|
|
209
|
+
## Building
|
|
210
|
+
|
|
211
|
+
To build wheels locally for distribution or development:
|
|
212
|
+
|
|
213
|
+
### Prerequisites
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pip install cibuildwheel
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Build wheels
|
|
220
|
+
|
|
221
|
+
Use the included `local_build.py` script:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# Build for all supported Python versions (3.10-3.14)
|
|
225
|
+
python local_build.py
|
|
226
|
+
|
|
227
|
+
# Build for specific Python versions
|
|
228
|
+
python local_build.py --python 3.11 3.12
|
|
229
|
+
|
|
230
|
+
# Build for specific platform
|
|
231
|
+
python local_build.py --platform linux
|
|
232
|
+
|
|
233
|
+
# Build and test wheel installation
|
|
234
|
+
python local_build.py --test
|
|
235
|
+
|
|
236
|
+
# Build without cleaning previous artifacts
|
|
237
|
+
python local_build.py --no-clean
|
|
238
|
+
|
|
239
|
+
# View all options
|
|
240
|
+
python local_build.py --help
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Build options
|
|
244
|
+
|
|
245
|
+
- `--python`: Specify Python versions to build for (e.g., `3.11 3.12`)
|
|
246
|
+
- `--platform`: Target platform (`auto`, `linux`, `macos`, `windows`)
|
|
247
|
+
- `--test`: Test wheel installation after building
|
|
248
|
+
- `--no-clean`: Skip cleaning build artifacts before building
|
|
249
|
+
|
|
250
|
+
## Requirements
|
|
251
|
+
|
|
252
|
+
- Python 3.10+
|
|
253
|
+
- NumPy (auto-installed)
|
|
254
|
+
- C++ compiler (for building from source)
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# BSD 3-Clause License
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2025, Teriks
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
|
8
|
+
#
|
|
9
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
# list of conditions and the following disclaimer.
|
|
11
|
+
#
|
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
# and/or other materials provided with the distribution.
|
|
15
|
+
#
|
|
16
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
|
18
|
+
# this software without specific prior written permission.
|
|
19
|
+
#
|
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
2
31
|
"""
|
|
3
32
|
Local wheel building script
|
|
4
33
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "patchmatch-cython"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "1.0.0"
|
|
8
8
|
description = "High-performance PatchMatch implementation for image inpainting using Cython"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
]
|
|
13
13
|
license = {file = "LICENSE"}
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
17
|
"Intended Audience :: Science/Research",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
# BSD 3-Clause License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025, Teriks
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
# list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
# contributors may be used to endorse or promote products derived from
|
|
17
|
+
# this software without specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
1
30
|
from setuptools import setup, Extension, find_packages
|
|
2
31
|
import numpy as np
|
|
3
32
|
import os
|
|
@@ -39,21 +68,21 @@ def macos_target_archs():
|
|
|
39
68
|
def get_compiler_args():
|
|
40
69
|
"""Get platform-appropriate compiler arguments"""
|
|
41
70
|
if os.name == 'nt': # Windows
|
|
42
|
-
return ["/O2"]
|
|
71
|
+
return ["/O2", "/std:c++17", "/EHsc"]
|
|
43
72
|
elif platform.system() == 'Darwin': # macOS
|
|
44
73
|
archs = macos_target_archs()
|
|
45
74
|
# -mcpu=apple-m1 is valid only for arm64. cibuildwheel's x86_64 builds
|
|
46
75
|
# run on Apple Silicon runners, and passing that flag makes clang fail
|
|
47
76
|
# with "unsupported option '-mcpu='".
|
|
48
77
|
if archs == ["arm64"]:
|
|
49
|
-
return ["-O3", "-mcpu=apple-m1"]
|
|
78
|
+
return ["-O3", "-std=c++17", "-mcpu=apple-m1"]
|
|
50
79
|
# Avoid -march=native so wheels remain portable across CPUs.
|
|
51
|
-
return ["-O3"]
|
|
80
|
+
return ["-O3", "-std=c++17"]
|
|
52
81
|
else: # Linux and other Unix-like
|
|
53
82
|
# Portable default; set PATCHMATCH_NATIVE=1 for local native builds.
|
|
54
83
|
if os.environ.get("PATCHMATCH_NATIVE") == "1":
|
|
55
|
-
return ["-O3", "-march=native"]
|
|
56
|
-
return ["-O3"]
|
|
84
|
+
return ["-O3", "-std=c++17", "-march=native"]
|
|
85
|
+
return ["-O3", "-std=c++17"]
|
|
57
86
|
|
|
58
87
|
# Build from Cython source only
|
|
59
88
|
extensions = [
|
|
@@ -63,7 +92,14 @@ extensions = [
|
|
|
63
92
|
include_dirs=[np.get_include()],
|
|
64
93
|
language="c++",
|
|
65
94
|
extra_compile_args=get_compiler_args(),
|
|
66
|
-
)
|
|
95
|
+
),
|
|
96
|
+
Extension(
|
|
97
|
+
"patchmatch_cython.philippeau",
|
|
98
|
+
[os.path.join("src", "patchmatch_cython", "philippeau.pyx")],
|
|
99
|
+
include_dirs=[np.get_include()],
|
|
100
|
+
language="c++",
|
|
101
|
+
extra_compile_args=get_compiler_args(),
|
|
102
|
+
),
|
|
67
103
|
]
|
|
68
104
|
|
|
69
105
|
ext_modules = cythonize(
|
|
@@ -83,7 +119,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
83
119
|
|
|
84
120
|
setup(
|
|
85
121
|
name="patchmatch-cython",
|
|
86
|
-
version="0.
|
|
122
|
+
version="1.0.0",
|
|
87
123
|
author="Teriks",
|
|
88
124
|
author_email="Teriks999@gmail.com",
|
|
89
125
|
description="High-performance PatchMatch implementation for image inpainting using Cython",
|
|
@@ -94,7 +130,7 @@ setup(
|
|
|
94
130
|
package_dir={"": "src"},
|
|
95
131
|
ext_modules=ext_modules,
|
|
96
132
|
classifiers=[
|
|
97
|
-
"Development Status ::
|
|
133
|
+
"Development Status :: 5 - Production/Stable",
|
|
98
134
|
"Intended Audience :: Developers",
|
|
99
135
|
"Intended Audience :: Science/Research",
|
|
100
136
|
"Programming Language :: Python :: 3",
|