patchmatch-cython 0.1.5__tar.gz → 0.2.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.
Files changed (35) hide show
  1. {patchmatch_cython-0.1.5/src/patchmatch_cython.egg-info → patchmatch_cython-0.2.0}/PKG-INFO +72 -26
  2. patchmatch_cython-0.2.0/README.md +184 -0
  3. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/pyproject.toml +1 -1
  4. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/setup.py +6 -5
  5. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython/__init__.py +1 -1
  6. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython/cython_solver.py +33 -8
  7. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython/cython_solver_impl.cpp +9720 -2900
  8. patchmatch_cython-0.2.0/src/patchmatch_cython/cython_solver_impl.pyx +628 -0
  9. patchmatch_cython-0.2.0/src/patchmatch_cython/imageops.py +719 -0
  10. patchmatch_cython-0.2.0/src/patchmatch_cython/inpaint.py +368 -0
  11. patchmatch_cython-0.2.0/src/patchmatch_cython/python_solver.py +402 -0
  12. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython/solver.py +13 -2
  13. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0/src/patchmatch_cython.egg-info}/PKG-INFO +72 -26
  14. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/SOURCES.txt +7 -1
  15. patchmatch_cython-0.2.0/tests/test_basic_functionality.py +29 -0
  16. patchmatch_cython-0.2.0/tests/test_imageops.py +300 -0
  17. patchmatch_cython-0.2.0/tests/test_inpaint_api.py +137 -0
  18. patchmatch_cython-0.2.0/tests/test_quality_options.py +263 -0
  19. patchmatch_cython-0.2.0/tests/test_questionable_behaviors.py +241 -0
  20. patchmatch_cython-0.2.0/tests/test_solvers.py +137 -0
  21. patchmatch_cython-0.2.0/tests/test_validation.py +75 -0
  22. patchmatch_cython-0.1.5/README.md +0 -138
  23. patchmatch_cython-0.1.5/src/patchmatch_cython/cython_solver_impl.pyx +0 -297
  24. patchmatch_cython-0.1.5/src/patchmatch_cython/imageops.py +0 -200
  25. patchmatch_cython-0.1.5/src/patchmatch_cython/inpaint.py +0 -139
  26. patchmatch_cython-0.1.5/src/patchmatch_cython/python_solver.py +0 -318
  27. patchmatch_cython-0.1.5/tests/test_basic_functionality.py +0 -71
  28. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/LICENSE +0 -0
  29. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/MANIFEST.in +0 -0
  30. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/local_build.py +0 -0
  31. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/setup.cfg +0 -0
  32. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/dependency_links.txt +0 -0
  33. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/not-zip-safe +0 -0
  34. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/requires.txt +0 -0
  35. {patchmatch_cython-0.1.5 → patchmatch_cython-0.2.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.1.5
3
+ Version: 0.2.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
@@ -70,16 +70,11 @@ 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.
74
74
 
75
- This package can function without `python-opencv` / `python-opencv-headless` with only slightly reduced quality.
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
 
77
- Installing OpenCV results in this packaging using it for upsampling (`INTER_LINEAR`).
78
-
79
- When OpenCV is not installed, an implementation of bilinear upscaling using numpy will be used.
80
-
81
- This package provides a pure python solver and a cython solver, the cython solver is
82
- around 30 to 50 times faster than the python solver depending on patch size.
77
+ The package provides a pure Python solver and a Cython solver; the Cython solver is typically about 30–50× faster depending on patch size.
83
78
 
84
79
  ## Installation
85
80
 
@@ -102,13 +97,10 @@ pip install .
102
97
  import cv2
103
98
  from patchmatch_cython import inpaint_pyramid
104
99
 
105
- # Load image and mask
106
100
  image = cv2.imread('image.jpg')
107
- mask = cv2.imread('mask.png', 0) > 128
101
+ mask = cv2.imread('mask.png', 0) > 128 # True = hole to fill
108
102
 
109
- # Inpaint
110
103
  result = inpaint_pyramid(image, mask)
111
-
112
104
  cv2.imwrite('result.jpg', result)
113
105
  ```
114
106
 
@@ -119,27 +111,84 @@ import numpy as np
119
111
  from PIL import Image
120
112
  from patchmatch_cython import inpaint_pyramid
121
113
 
122
- # Load image and mask
123
114
  image = np.array(Image.open('image.jpg'))
124
115
  mask = np.array(Image.open('mask.png').convert('L')) > 128
125
116
 
126
- # Inpaint (auto-selects fastest available solver)
127
- result = inpaint_pyramid(image, mask)
128
-
117
+ result = inpaint_pyramid(image, mask) # auto-selects fastest available solver
129
118
  Image.fromarray(result).save('result.jpg')
130
119
  ```
131
120
 
132
- ### Explicit Solver Selection
121
+ ### Explicit solver selection
133
122
 
134
123
  ```python
135
124
  from patchmatch_cython import PythonSolver, CythonSolver, inpaint_pyramid
136
125
 
137
- # Force specific solver
138
126
  result = inpaint_pyramid(image, mask, solver_class=CythonSolver) # Fast
139
127
  result = inpaint_pyramid(image, mask, solver_class=PythonSolver) # Fallback
140
128
  ```
141
129
 
142
- ## Package Extras
130
+ ### Quality / compose options
131
+
132
+ Both `inpaint_pyramid` and `inpaint_single` accept the same optional knobs.
133
+ Defaults preserve a sensible baseline; turn options on when you need more structure,
134
+ completeness, or softer seams.
135
+
136
+ ```python
137
+ from patchmatch_cython import inpaint_pyramid
138
+
139
+ result = inpaint_pyramid(
140
+ image,
141
+ mask,
142
+ patch_size=5, # odd int; square patch side
143
+ seed=0, # reproducible RNG (optional)
144
+ em_loops=2, # coarse match→vote→rematch loops (pyramid: coarse levels only)
145
+ edge_weighted=True, # structure-aware distance (boosted gradients)
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
150
+ )
151
+ ```
152
+
153
+ | Parameter | Default | Description |
154
+ |---|---|---|
155
+ | `patch_size` | `5` | Odd positive int; square patch size for matching / voting. |
156
+ | `seed` | `None` | RNG seed for reproducible NNF init / search. |
157
+ | `em_loops` | `1` | Outer match→paint→rematch loops. On pyramids, applied at coarse levels only (finest stays `1`). |
158
+ | `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
+ | `reverse_search` | `False` | After each EM loop, try assigning underused near-hole sources to better queries (completeness). |
160
+ | `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). |
161
+ | `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
+ | `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
+
164
+ `inpaint_single` is the same API plus `iterations` (PatchMatch passes at one scale):
165
+
166
+ ```python
167
+ from patchmatch_cython import inpaint_single
168
+
169
+ result = inpaint_single(
170
+ image,
171
+ mask,
172
+ iterations=7,
173
+ em_loops=2,
174
+ reverse_search=True,
175
+ hole_refine=2,
176
+ mask_feather=4.0,
177
+ poisson_blend=False, # feather handles seams when mask_feather > 0
178
+ )
179
+ ```
180
+
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
+ **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
143
192
 
144
193
  **For examples:**
145
194
 
@@ -163,15 +212,13 @@ To build wheels locally for distribution or development:
163
212
 
164
213
  ### Prerequisites
165
214
 
166
- Install build dependencies:
167
-
168
215
  ```bash
169
216
  pip install cibuildwheel
170
217
  ```
171
218
 
172
- ### Build Wheels
219
+ ### Build wheels
173
220
 
174
- Use the included `local_build.py` script to build locally:
221
+ Use the included `local_build.py` script:
175
222
 
176
223
  ```bash
177
224
  # Build for all supported Python versions (3.10-3.14)
@@ -193,14 +240,13 @@ python local_build.py --no-clean
193
240
  python local_build.py --help
194
241
  ```
195
242
 
196
- ### Build Options
243
+ ### Build options
197
244
 
198
245
  - `--python`: Specify Python versions to build for (e.g., `3.11 3.12`)
199
246
  - `--platform`: Target platform (`auto`, `linux`, `macos`, `windows`)
200
247
  - `--test`: Test wheel installation after building
201
248
  - `--no-clean`: Skip cleaning build artifacts before building
202
249
 
203
-
204
250
  ## Requirements
205
251
 
206
252
  - Python 3.10+
@@ -0,0 +1,184 @@
1
+ # PatchMatch Cython
2
+
3
+ High-performance PatchMatch implementation for pyramidical image inpainting 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
+ ### With OpenCV
25
+
26
+ ```python
27
+ import cv2
28
+ from patchmatch_cython import inpaint_pyramid
29
+
30
+ image = cv2.imread('image.jpg')
31
+ mask = cv2.imread('mask.png', 0) > 128 # True = hole to fill
32
+
33
+ result = inpaint_pyramid(image, mask)
34
+ cv2.imwrite('result.jpg', result)
35
+ ```
36
+
37
+ ### With Pillow
38
+
39
+ ```python
40
+ import numpy as np
41
+ from PIL import Image
42
+ from patchmatch_cython import inpaint_pyramid
43
+
44
+ image = np.array(Image.open('image.jpg'))
45
+ mask = np.array(Image.open('mask.png').convert('L')) > 128
46
+
47
+ result = inpaint_pyramid(image, mask) # auto-selects fastest available solver
48
+ Image.fromarray(result).save('result.jpg')
49
+ ```
50
+
51
+ ### Explicit solver selection
52
+
53
+ ```python
54
+ from patchmatch_cython import PythonSolver, CythonSolver, inpaint_pyramid
55
+
56
+ result = inpaint_pyramid(image, mask, solver_class=CythonSolver) # Fast
57
+ result = inpaint_pyramid(image, mask, solver_class=PythonSolver) # Fallback
58
+ ```
59
+
60
+ ### Quality / compose options
61
+
62
+ Both `inpaint_pyramid` and `inpaint_single` accept the same optional knobs.
63
+ Defaults preserve a sensible baseline; turn options on when you need more structure,
64
+ completeness, or softer seams.
65
+
66
+ ```python
67
+ from patchmatch_cython import inpaint_pyramid
68
+
69
+ result = inpaint_pyramid(
70
+ image,
71
+ mask,
72
+ patch_size=5, # odd int; square patch side
73
+ seed=0, # reproducible RNG (optional)
74
+ em_loops=2, # coarse match→vote→rematch loops (pyramid: coarse levels only)
75
+ edge_weighted=True, # structure-aware distance (boosted gradients)
76
+ reverse_search=True, # source→query completeness pass + stronger usage penalty
77
+ poisson_blend=True, # harmonic seam soften (default ~8px band)
78
+ hole_refine=3, # extra full-res PatchMatch iterations after the main solve
79
+ mask_feather=6.0, # soft compose width in px from the hole boundary
80
+ )
81
+ ```
82
+
83
+ | Parameter | Default | Description |
84
+ |---|---|---|
85
+ | `patch_size` | `5` | Odd positive int; square patch size for matching / voting. |
86
+ | `seed` | `None` | RNG seed for reproducible NNF init / search. |
87
+ | `em_loops` | `1` | Outer match→paint→rematch loops. On pyramids, applied at coarse levels only (finest stays `1`). |
88
+ | `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. |
89
+ | `reverse_search` | `False` | After each EM loop, try assigning underused near-hole sources to better queries (completeness). |
90
+ | `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). |
91
+ | `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. |
92
+ | `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. |
93
+
94
+ `inpaint_single` is the same API plus `iterations` (PatchMatch passes at one scale):
95
+
96
+ ```python
97
+ from patchmatch_cython import inpaint_single
98
+
99
+ result = inpaint_single(
100
+ image,
101
+ mask,
102
+ iterations=7,
103
+ em_loops=2,
104
+ reverse_search=True,
105
+ hole_refine=2,
106
+ mask_feather=4.0,
107
+ poisson_blend=False, # feather handles seams when mask_feather > 0
108
+ )
109
+ ```
110
+
111
+ **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.
112
+
113
+ **OpenCV runtime toggle** (optional):
114
+
115
+ ```python
116
+ from patchmatch_cython.imageops import set_use_opencv, opencv_available
117
+
118
+ set_use_opencv(False) # force NumPy fallbacks even if OpenCV is installed
119
+ ```
120
+
121
+ ## Package extras
122
+
123
+ **For examples:**
124
+
125
+ ```bash
126
+ pip install patchmatch-cython[visualization]
127
+ ```
128
+
129
+ Adds: `matplotlib`, `Pillow`
130
+
131
+ **For development:**
132
+
133
+ ```bash
134
+ pip install patchmatch-cython[dev]
135
+ ```
136
+
137
+ Adds: `pytest`, `build`, `cibuildwheel`, `setuptools`, `wheel`
138
+
139
+ ## Building
140
+
141
+ To build wheels locally for distribution or development:
142
+
143
+ ### Prerequisites
144
+
145
+ ```bash
146
+ pip install cibuildwheel
147
+ ```
148
+
149
+ ### Build wheels
150
+
151
+ Use the included `local_build.py` script:
152
+
153
+ ```bash
154
+ # Build for all supported Python versions (3.10-3.14)
155
+ python local_build.py
156
+
157
+ # Build for specific Python versions
158
+ python local_build.py --python 3.11 3.12
159
+
160
+ # Build for specific platform
161
+ python local_build.py --platform linux
162
+
163
+ # Build and test wheel installation
164
+ python local_build.py --test
165
+
166
+ # Build without cleaning previous artifacts
167
+ python local_build.py --no-clean
168
+
169
+ # View all options
170
+ python local_build.py --help
171
+ ```
172
+
173
+ ### Build options
174
+
175
+ - `--python`: Specify Python versions to build for (e.g., `3.11 3.12`)
176
+ - `--platform`: Target platform (`auto`, `linux`, `macos`, `windows`)
177
+ - `--test`: Test wheel installation after building
178
+ - `--no-clean`: Skip cleaning build artifacts before building
179
+
180
+ ## Requirements
181
+
182
+ - Python 3.10+
183
+ - NumPy (auto-installed)
184
+ - C++ compiler (for building from source)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "patchmatch-cython"
7
- version = "0.1.5"
7
+ version = "0.2.0"
8
8
  description = "High-performance PatchMatch implementation for image inpainting using Cython"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -47,12 +47,13 @@ def get_compiler_args():
47
47
  # with "unsupported option '-mcpu='".
48
48
  if archs == ["arm64"]:
49
49
  return ["-O3", "-mcpu=apple-m1"]
50
- # -march=native is only safe when actually compiling on Intel.
51
- if archs == ["x86_64"] and platform.machine().lower() in ("x86_64", "amd64"):
52
- return ["-O3", "-march=native"]
50
+ # Avoid -march=native so wheels remain portable across CPUs.
53
51
  return ["-O3"]
54
52
  else: # Linux and other Unix-like
55
- return ["-O3", "-march=native"]
53
+ # Portable default; set PATCHMATCH_NATIVE=1 for local native builds.
54
+ if os.environ.get("PATCHMATCH_NATIVE") == "1":
55
+ return ["-O3", "-march=native"]
56
+ return ["-O3"]
56
57
 
57
58
  # Build from Cython source only
58
59
  extensions = [
@@ -82,7 +83,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
82
83
 
83
84
  setup(
84
85
  name="patchmatch-cython",
85
- version="0.1.5",
86
+ version="0.2.0",
86
87
  author="Teriks",
87
88
  author_email="Teriks999@gmail.com",
88
89
  description="High-performance PatchMatch implementation for image inpainting using Cython",
@@ -1,6 +1,6 @@
1
1
  import typing
2
2
 
3
- __version__: str = "0.1.5"
3
+ __version__: str = "0.2.0"
4
4
 
5
5
  # Core algorithm imports (always available)
6
6
  from .python_solver import PythonSolver
@@ -41,14 +41,22 @@ class CythonSolver(Solver):
41
41
  self,
42
42
  image: np.ndarray,
43
43
  mask: np.ndarray,
44
- patch_size: int
44
+ patch_size: int,
45
+ edge_weighted: bool = False,
45
46
  ) -> None:
46
- super().__init__(image, mask, patch_size)
47
+ # Normalize mask to {0,1} uint8. Plain astype(uint8) leaves {0,255} masks
48
+ # unchanged; boolean indexing bugs are avoided by going through != 0.
49
+ image_f32 = np.ascontiguousarray(image, dtype=np.float32)
50
+ mask_u8 = (np.asarray(mask) != 0).astype(np.uint8)
51
+ super().__init__(
52
+ image_f32, mask_u8.astype(bool), patch_size, edge_weighted=edge_weighted
53
+ )
47
54
 
48
55
  self._solver: typing.Any = CythonSolverImpl(
49
- image.astype(np.float32),
50
- mask.astype(np.uint8),
51
- patch_size
56
+ image_f32,
57
+ np.ascontiguousarray(mask_u8),
58
+ patch_size,
59
+ edge_weighted=bool(edge_weighted),
52
60
  )
53
61
 
54
62
  @property
@@ -64,7 +72,9 @@ class CythonSolver(Solver):
64
72
  iterations: int = 5,
65
73
  randomize_nnf: bool = True,
66
74
  nnf: typing.Optional[np.ndarray] = None,
67
- seed: typing.Optional[int] = None
75
+ seed: typing.Optional[int] = None,
76
+ em_loops: int = 1,
77
+ reverse_search: bool = False,
68
78
  ) -> np.ndarray:
69
79
  """
70
80
  Run the PatchMatch algorithm using Cython acceleration.
@@ -85,8 +95,23 @@ class CythonSolver(Solver):
85
95
  :type nnf: numpy.ndarray or None
86
96
  :param seed: Random seed for this specific run (overrides instance seed)
87
97
  :type seed: int or None
88
- :return: Inpainted image with masked regions filled, converted to uint8
98
+ :param em_loops: Outer match→vote→rematch loops (1 preserves prior behavior)
99
+ :type em_loops: int
100
+ :param reverse_search: Enable reverse/completeness search after each EM loop
101
+ :type reverse_search: bool
102
+ :return: Inpainted image with masked regions filled (float32)
89
103
  :rtype: numpy.ndarray
90
104
  """
91
105
 
92
- return self._solver(iterations, randomize_nnf, nnf, seed).astype(np.uint8)
106
+ # Keep float32 here so callers (and inpaint_*) can round/saturate once.
107
+ return np.asarray(
108
+ self._solver(
109
+ iterations,
110
+ randomize_nnf,
111
+ nnf,
112
+ seed,
113
+ em_loops,
114
+ reverse_search,
115
+ ),
116
+ dtype=np.float32,
117
+ )