patchmatch-cython 0.1.4__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.4/src/patchmatch_cython.egg-info → patchmatch_cython-0.2.0}/PKG-INFO +74 -27
  2. patchmatch_cython-0.2.0/README.md +184 -0
  3. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/local_build.py +2 -2
  4. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/pyproject.toml +4 -3
  5. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/setup.py +36 -5
  6. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython/__init__.py +1 -1
  7. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython/cython_solver.py +33 -8
  8. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython/cython_solver_impl.cpp +26285 -15825
  9. patchmatch_cython-0.2.0/src/patchmatch_cython/cython_solver_impl.pyx +628 -0
  10. patchmatch_cython-0.2.0/src/patchmatch_cython/imageops.py +719 -0
  11. patchmatch_cython-0.2.0/src/patchmatch_cython/inpaint.py +368 -0
  12. patchmatch_cython-0.2.0/src/patchmatch_cython/python_solver.py +402 -0
  13. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython/solver.py +13 -2
  14. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0/src/patchmatch_cython.egg-info}/PKG-INFO +74 -27
  15. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/SOURCES.txt +7 -1
  16. patchmatch_cython-0.2.0/tests/test_basic_functionality.py +29 -0
  17. patchmatch_cython-0.2.0/tests/test_imageops.py +300 -0
  18. patchmatch_cython-0.2.0/tests/test_inpaint_api.py +137 -0
  19. patchmatch_cython-0.2.0/tests/test_quality_options.py +263 -0
  20. patchmatch_cython-0.2.0/tests/test_questionable_behaviors.py +241 -0
  21. patchmatch_cython-0.2.0/tests/test_solvers.py +137 -0
  22. patchmatch_cython-0.2.0/tests/test_validation.py +75 -0
  23. patchmatch_cython-0.1.4/README.md +0 -138
  24. patchmatch_cython-0.1.4/src/patchmatch_cython/cython_solver_impl.pyx +0 -297
  25. patchmatch_cython-0.1.4/src/patchmatch_cython/imageops.py +0 -200
  26. patchmatch_cython-0.1.4/src/patchmatch_cython/inpaint.py +0 -139
  27. patchmatch_cython-0.1.4/src/patchmatch_cython/python_solver.py +0 -318
  28. patchmatch_cython-0.1.4/tests/test_basic_functionality.py +0 -71
  29. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/LICENSE +0 -0
  30. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/MANIFEST.in +0 -0
  31. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/setup.cfg +0 -0
  32. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/dependency_links.txt +0 -0
  33. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/not-zip-safe +0 -0
  34. {patchmatch_cython-0.1.4 → patchmatch_cython-0.2.0}/src/patchmatch_cython.egg-info/requires.txt +0 -0
  35. {patchmatch_cython-0.1.4 → 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.4
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
@@ -45,6 +45,7 @@ Classifier: Programming Language :: Python :: 3.10
45
45
  Classifier: Programming Language :: Python :: 3.11
46
46
  Classifier: Programming Language :: Python :: 3.12
47
47
  Classifier: Programming Language :: Python :: 3.13
48
+ Classifier: Programming Language :: Python :: 3.14
48
49
  Classifier: Programming Language :: Cython
49
50
  Classifier: Topic :: Scientific/Engineering :: Image Processing
50
51
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -69,16 +70,11 @@ Dynamic: requires-python
69
70
 
70
71
  # PatchMatch Cython
71
72
 
72
- High-performance PatchMatch implementation for pyramidical image inpainting in cython.
73
+ High-performance PatchMatch implementation for pyramidical image inpainting in Cython.
73
74
 
74
- 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.
75
76
 
76
- Installing OpenCV results in this packaging using it for upsampling (`INTER_LINEAR`).
77
-
78
- When OpenCV is not installed, an implementation of bilinear upscaling using numpy will be used.
79
-
80
- This package provides a pure python solver and a cython solver, the cython solver is
81
- 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.
82
78
 
83
79
  ## Installation
84
80
 
@@ -101,13 +97,10 @@ pip install .
101
97
  import cv2
102
98
  from patchmatch_cython import inpaint_pyramid
103
99
 
104
- # Load image and mask
105
100
  image = cv2.imread('image.jpg')
106
- mask = cv2.imread('mask.png', 0) > 128
101
+ mask = cv2.imread('mask.png', 0) > 128 # True = hole to fill
107
102
 
108
- # Inpaint
109
103
  result = inpaint_pyramid(image, mask)
110
-
111
104
  cv2.imwrite('result.jpg', result)
112
105
  ```
113
106
 
@@ -118,27 +111,84 @@ import numpy as np
118
111
  from PIL import Image
119
112
  from patchmatch_cython import inpaint_pyramid
120
113
 
121
- # Load image and mask
122
114
  image = np.array(Image.open('image.jpg'))
123
115
  mask = np.array(Image.open('mask.png').convert('L')) > 128
124
116
 
125
- # Inpaint (auto-selects fastest available solver)
126
- result = inpaint_pyramid(image, mask)
127
-
117
+ result = inpaint_pyramid(image, mask) # auto-selects fastest available solver
128
118
  Image.fromarray(result).save('result.jpg')
129
119
  ```
130
120
 
131
- ### Explicit Solver Selection
121
+ ### Explicit solver selection
132
122
 
133
123
  ```python
134
124
  from patchmatch_cython import PythonSolver, CythonSolver, inpaint_pyramid
135
125
 
136
- # Force specific solver
137
126
  result = inpaint_pyramid(image, mask, solver_class=CythonSolver) # Fast
138
127
  result = inpaint_pyramid(image, mask, solver_class=PythonSolver) # Fallback
139
128
  ```
140
129
 
141
- ## 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
142
192
 
143
193
  **For examples:**
144
194
 
@@ -162,18 +212,16 @@ To build wheels locally for distribution or development:
162
212
 
163
213
  ### Prerequisites
164
214
 
165
- Install build dependencies:
166
-
167
215
  ```bash
168
216
  pip install cibuildwheel
169
217
  ```
170
218
 
171
- ### Build Wheels
219
+ ### Build wheels
172
220
 
173
- Use the included `local_build.py` script to build locally:
221
+ Use the included `local_build.py` script:
174
222
 
175
223
  ```bash
176
- # Build for all supported Python versions (3.10-3.13)
224
+ # Build for all supported Python versions (3.10-3.14)
177
225
  python local_build.py
178
226
 
179
227
  # Build for specific Python versions
@@ -192,14 +240,13 @@ python local_build.py --no-clean
192
240
  python local_build.py --help
193
241
  ```
194
242
 
195
- ### Build Options
243
+ ### Build options
196
244
 
197
245
  - `--python`: Specify Python versions to build for (e.g., `3.11 3.12`)
198
246
  - `--platform`: Target platform (`auto`, `linux`, `macos`, `windows`)
199
247
  - `--test`: Test wheel installation after building
200
248
  - `--no-clean`: Skip cleaning build artifacts before building
201
249
 
202
-
203
250
  ## Requirements
204
251
 
205
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)
@@ -77,8 +77,8 @@ def build_wheels(python_versions=None, platforms=None, clean=True):
77
77
  print(f"Building for Python versions: {python_versions}")
78
78
  else:
79
79
  # Build all supported versions (default)
80
- env["CIBW_BUILD"] = "cp310-* cp311-* cp312-* cp313-*"
81
- print("Building for all supported Python versions (3.10-3.13)")
80
+ env["CIBW_BUILD"] = "cp310-* cp311-* cp312-* cp313-* cp314-*"
81
+ print("Building for all supported Python versions (3.10-3.14)")
82
82
 
83
83
 
84
84
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "patchmatch-cython"
7
- version = "0.1.4"
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 = [
@@ -20,6 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.11",
21
21
  "Programming Language :: Python :: 3.12",
22
22
  "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
23
24
  "Programming Language :: Cython",
24
25
  "Topic :: Scientific/Engineering :: Image Processing",
25
26
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
@@ -57,8 +58,8 @@ include = ["patchmatch_cython*"]
57
58
  patchmatch_cython = ["*.pyx", "*.pxd", "*.pyd", "*.so", "*.cpp"]
58
59
 
59
60
  [tool.cibuildwheel]
60
- # Build for Python 3.10-3.13 only (skip older versions)
61
- build = "cp310-* cp311-* cp312-* cp313-*"
61
+ # Build for Python 3.10-3.14 only (skip older versions)
62
+ build = "cp310-* cp311-* cp312-* cp313-* cp314-*"
62
63
 
63
64
  # Configure for different platforms
64
65
  [tool.cibuildwheel.linux]
@@ -13,17 +13,47 @@ try:
13
13
  except ImportError:
14
14
  raise RuntimeError("Cython is required to build this package. Install with: pip install Cython")
15
15
 
16
+ def macos_target_archs():
17
+ """Return the macOS architectures this compilation is targeting.
18
+
19
+ cibuildwheel cross-compiles by setting ARCHFLAGS (for example
20
+ ``-arch x86_64``) on an Apple Silicon runner. platform.machine() still
21
+ reports the host CPU in that case, so it cannot be used to choose flags.
22
+ """
23
+ archs = []
24
+ parts = os.environ.get("ARCHFLAGS", "").split()
25
+ for index, part in enumerate(parts):
26
+ if part == "-arch" and index + 1 < len(parts):
27
+ archs.append(parts[index + 1])
28
+ if archs:
29
+ return archs
30
+
31
+ machine = platform.machine().lower()
32
+ if machine in ("arm64", "aarch64"):
33
+ return ["arm64"]
34
+ if machine in ("x86_64", "amd64"):
35
+ return ["x86_64"]
36
+ return [machine]
37
+
38
+
16
39
  def get_compiler_args():
17
40
  """Get platform-appropriate compiler arguments"""
18
41
  if os.name == 'nt': # Windows
19
42
  return ["/O2"]
20
43
  elif platform.system() == 'Darwin': # macOS
21
- if platform.machine() in ('arm64', 'aarch64'): # Apple Silicon
44
+ archs = macos_target_archs()
45
+ # -mcpu=apple-m1 is valid only for arm64. cibuildwheel's x86_64 builds
46
+ # run on Apple Silicon runners, and passing that flag makes clang fail
47
+ # with "unsupported option '-mcpu='".
48
+ if archs == ["arm64"]:
22
49
  return ["-O3", "-mcpu=apple-m1"]
23
- else: # Intel Mac
24
- return ["-O3", "-march=native"]
50
+ # Avoid -march=native so wheels remain portable across CPUs.
51
+ return ["-O3"]
25
52
  else: # Linux and other Unix-like
26
- 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"]
27
57
 
28
58
  # Build from Cython source only
29
59
  extensions = [
@@ -53,7 +83,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
53
83
 
54
84
  setup(
55
85
  name="patchmatch-cython",
56
- version="0.1.4",
86
+ version="0.2.0",
57
87
  author="Teriks",
58
88
  author_email="Teriks999@gmail.com",
59
89
  description="High-performance PatchMatch implementation for image inpainting using Cython",
@@ -72,6 +102,7 @@ setup(
72
102
  "Programming Language :: Python :: 3.11",
73
103
  "Programming Language :: Python :: 3.12",
74
104
  "Programming Language :: Python :: 3.13",
105
+ "Programming Language :: Python :: 3.14",
75
106
  "Programming Language :: Cython",
76
107
  "Topic :: Scientific/Engineering :: Image Processing",
77
108
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
@@ -1,6 +1,6 @@
1
1
  import typing
2
2
 
3
- __version__: str = "0.1.4"
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
+ )