PyPatchMatch 2.0.0b1__tar.gz → 2.0.0rc1__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 (23) hide show
  1. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/CMakeLists.txt +13 -0
  2. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/PKG-INFO +55 -19
  3. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/README.md +45 -17
  4. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/_lib.py +8 -2
  5. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/_version.py +3 -3
  6. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/inpaint.cpp +25 -13
  7. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/masked_image.h +1 -0
  8. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/nnf.cpp +40 -30
  9. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/nnf.h +8 -29
  10. pypatchmatch-2.0.0rc1/patchmatch/csrc/pyinterface.cpp +214 -0
  11. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/pyinterface.h +6 -0
  12. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/patch_match.py +56 -9
  13. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/pyproject.toml +18 -0
  14. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/scripts/build_opencv.py +32 -3
  15. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/tests/test_e2e.py +39 -11
  16. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/tests/test_patch_match.py +110 -4
  17. pypatchmatch-2.0.0b1/patchmatch/csrc/pyinterface.cpp +0 -133
  18. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/LICENSE +0 -0
  19. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/examples/images/forest_pruned.bmp +0 -0
  20. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/__init__.py +0 -0
  21. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/inpaint.h +0 -0
  22. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/masked_image.cpp +0 -0
  23. {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/py.typed +0 -0
@@ -61,3 +61,16 @@ install(TARGETS patchmatch
61
61
  LIBRARY DESTINATION patchmatch
62
62
  RUNTIME DESTINATION patchmatch
63
63
  )
64
+
65
+ # A static OpenCV becomes part of the library, so a wheel has to ship its licenses,
66
+ # which scripts/build_opencv.py collects in <prefix>/licenses.
67
+ if(DEFINED SKBUILD_METADATA_DIR AND NOT OpenCV_SHARED)
68
+ set(PATCHMATCH_OPENCV_LICENSES "${OpenCV_INSTALL_PATH}/licenses")
69
+ if(EXISTS "${PATCHMATCH_OPENCV_LICENSES}/opencv-LICENSE")
70
+ install(DIRECTORY "${PATCHMATCH_OPENCV_LICENSES}/"
71
+ DESTINATION "${SKBUILD_METADATA_DIR}/licenses/opencv")
72
+ else()
73
+ message(WARNING "No OpenCV licenses in ${PATCHMATCH_OPENCV_LICENSES}, "
74
+ "the wheel will not include them")
75
+ endif()
76
+ endif()
@@ -1,10 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPatchMatch
3
- Version: 2.0.0b1
3
+ Version: 2.0.0rc1
4
4
  Summary: This library implements the PatchMatch based inpainting algorithm.
5
- Author-Email: The InvokeAI Project <lincoln.stein@gmail.com>, Kyle Schouviller <kyle0654@hotmail.com>, Matthias Wild <mauwii@outlook.de>, Younesse ANDAM <younesse.andam@gmail.com>
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>
6
7
  License-Expression: MIT
7
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
8
13
  Classifier: Programming Language :: C++
9
14
  Classifier: Programming Language :: Python :: 3
10
15
  Classifier: Programming Language :: Python :: 3.10
@@ -14,7 +19,10 @@ Classifier: Programming Language :: Python :: 3.13
14
19
  Classifier: Programming Language :: Python :: 3.14
15
20
  Classifier: Topic :: Multimedia :: Graphics
16
21
  Classifier: Topic :: Scientific/Engineering :: Image Processing
22
+ Classifier: Typing :: Typed
17
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
18
26
  Requires-Python: >=3.10
19
27
  Requires-Dist: numpy
20
28
  Requires-Dist: pillow
@@ -24,7 +32,7 @@ Description-Content-Type: text/markdown
24
32
 
25
33
  [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
26
34
  [![PyPI](https://img.shields.io/pypi/v/PyPatchMatch)](https://pypi.org/project/PyPatchMatch/)
27
- [![Downloads](https://static.pepy.tech/badge/pypatchmatch)](https://pepy.tech/project/pypatchmatch)
35
+ [![Downloads](https://static.pepy.tech/badge/pypatchmatch)](https://pepy.tech/projects/pypatchmatch)
28
36
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
29
37
 
30
38
  This library implements the PatchMatch based inpainting algorithm. It provides both C++
@@ -38,14 +46,25 @@ with some bug fixes, and updates.
38
46
  pip install PyPatchMatch
39
47
  ```
40
48
 
41
- Wheels for Linux, macOS and Windows ship the compiled library including the required
42
- OpenCV runtime, so no compiler or OpenCV installation is needed. On other platforms pip
43
- builds from the source distribution, which requires a C++17 compiler, CMake and the
49
+ Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
50
+ (x64) ship the compiled library with a statically linked OpenCV, so no compiler or
51
+ OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
52
+ pip builds from the source distribution, which requires a C++17 compiler, CMake and the
44
53
  OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
45
54
 
55
+ If that build fails, PyPatchMatch is installed without the native library instead of
56
+ blocking the installation: `patchmatch.patchmatch_available` is `False` and the
57
+ inpainting functions raise `RuntimeError`. After installing the missing tools, reinstall
58
+ it without the cached build:
59
+
60
+ ```sh
61
+ pip install --force-reinstall --no-deps --no-cache-dir PyPatchMatch
62
+ ```
63
+
46
64
  ## Usage
47
65
 
48
- Python (see [examples/py_example.py](examples/py_example.py)):
66
+ Python (see
67
+ [examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
49
68
 
50
69
  ```python
51
70
  import patchmatch
@@ -56,12 +75,27 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
56
75
  ```
57
76
 
58
77
  The mask must have the same height and width as the image. If `mask` is omitted, all
59
- pure white pixels are treated as holes.
78
+ pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
79
+ their values. The optional keyword argument `global_mask`, in the same format, marks
80
+ pixels that are neither filled nor used as a source; they keep their values as well (see
81
+ [examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
60
82
  `patchmatch.patchmatch_available` tells whether the native library could be loaded.
61
83
  The previous import path `from patchmatch import patch_match` keeps working.
62
84
 
63
- C++ (see [examples/cpp_example.cpp](examples/cpp_example.cpp), build and run it with
64
- [examples/cpp_example_run.sh](examples/cpp_example_run.sh)):
85
+ `patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
86
+ pixels in each direction. Larger patches follow larger structures but are much slower;
87
+ the default of 15 compares 31x31 patches, the examples use 3.
88
+
89
+ `patchmatch.set_random_seed(seed)` sets the seed of the randomized search and
90
+ `patchmatch.set_verbose(True)` prints the progress of the native code to stderr.
91
+ `patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the search with
92
+ a regularity map, an HxWx3 float32 array with the regularity coordinates of each pixel
93
+ in its first two channels. Its `guide_weight` must be at least 0.
94
+
95
+ C++ (see
96
+ [examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
97
+ build and run it with
98
+ [examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
65
99
 
66
100
  ```cpp
67
101
  #include "inpaint.h"
@@ -90,13 +124,21 @@ uv run pytest -m "not e2e" # only the fast unit tests
90
124
  ```
91
125
 
92
126
  Releases are published to PyPI by creating a GitHub release; the version is taken from
93
- its tag (e.g. `v1.1.0`).
127
+ its tag (e.g. `v2.0.0`).
128
+
129
+ ## License
130
+
131
+ PyPatchMatch is released under the
132
+ [MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
133
+ contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
134
+ third-party code; their licenses are included in the `licenses/opencv` directory of the
135
+ wheel's `.dist-info`.
94
136
 
95
137
  ## README and COPYRIGHT by Younesse ANDAM
96
138
 
97
139
  @Author: Younesse ANDAM
98
140
 
99
- @Contact: younesse.andam@gmail.com
141
+ @Contact: <younesse.andam@gmail.com>
100
142
 
101
143
  Description:
102
144
 
@@ -107,12 +149,6 @@ C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graph
107
149
  (Proc. SIGGRAPH), vol.28, aug-2009
108
150
 
109
151
  For more information please refer to
110
- http://www.cs.princeton.edu/gfx/pubs/Barnes_2009_PAR/index.php
152
+ <https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
111
153
 
112
154
  Copyright (c) 2010-2011
113
-
114
- ## Requirements
115
-
116
- To run the project you need to install Opencv library and link it to your project.
117
- Opencv can be download it here
118
- http://opencv.org/downloads.html
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
4
4
  [![PyPI](https://img.shields.io/pypi/v/PyPatchMatch)](https://pypi.org/project/PyPatchMatch/)
5
- [![Downloads](https://static.pepy.tech/badge/pypatchmatch)](https://pepy.tech/project/pypatchmatch)
5
+ [![Downloads](https://static.pepy.tech/badge/pypatchmatch)](https://pepy.tech/projects/pypatchmatch)
6
6
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
7
 
8
8
  This library implements the PatchMatch based inpainting algorithm. It provides both C++
@@ -16,14 +16,25 @@ with some bug fixes, and updates.
16
16
  pip install PyPatchMatch
17
17
  ```
18
18
 
19
- Wheels for Linux, macOS and Windows ship the compiled library including the required
20
- OpenCV runtime, so no compiler or OpenCV installation is needed. On other platforms pip
21
- builds from the source distribution, which requires a C++17 compiler, CMake and the
19
+ Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
20
+ (x64) ship the compiled library with a statically linked OpenCV, so no compiler or
21
+ OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
22
+ pip builds from the source distribution, which requires a C++17 compiler, CMake and the
22
23
  OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
23
24
 
25
+ If that build fails, PyPatchMatch is installed without the native library instead of
26
+ blocking the installation: `patchmatch.patchmatch_available` is `False` and the
27
+ inpainting functions raise `RuntimeError`. After installing the missing tools, reinstall
28
+ it without the cached build:
29
+
30
+ ```sh
31
+ pip install --force-reinstall --no-deps --no-cache-dir PyPatchMatch
32
+ ```
33
+
24
34
  ## Usage
25
35
 
26
- Python (see [examples/py_example.py](examples/py_example.py)):
36
+ Python (see
37
+ [examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
27
38
 
28
39
  ```python
29
40
  import patchmatch
@@ -34,12 +45,27 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
34
45
  ```
35
46
 
36
47
  The mask must have the same height and width as the image. If `mask` is omitted, all
37
- pure white pixels are treated as holes.
48
+ pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
49
+ their values. The optional keyword argument `global_mask`, in the same format, marks
50
+ pixels that are neither filled nor used as a source; they keep their values as well (see
51
+ [examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
38
52
  `patchmatch.patchmatch_available` tells whether the native library could be loaded.
39
53
  The previous import path `from patchmatch import patch_match` keeps working.
40
54
 
41
- C++ (see [examples/cpp_example.cpp](examples/cpp_example.cpp), build and run it with
42
- [examples/cpp_example_run.sh](examples/cpp_example_run.sh)):
55
+ `patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
56
+ pixels in each direction. Larger patches follow larger structures but are much slower;
57
+ the default of 15 compares 31x31 patches, the examples use 3.
58
+
59
+ `patchmatch.set_random_seed(seed)` sets the seed of the randomized search and
60
+ `patchmatch.set_verbose(True)` prints the progress of the native code to stderr.
61
+ `patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the search with
62
+ a regularity map, an HxWx3 float32 array with the regularity coordinates of each pixel
63
+ in its first two channels. Its `guide_weight` must be at least 0.
64
+
65
+ C++ (see
66
+ [examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
67
+ build and run it with
68
+ [examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
43
69
 
44
70
  ```cpp
45
71
  #include "inpaint.h"
@@ -68,13 +94,21 @@ uv run pytest -m "not e2e" # only the fast unit tests
68
94
  ```
69
95
 
70
96
  Releases are published to PyPI by creating a GitHub release; the version is taken from
71
- its tag (e.g. `v1.1.0`).
97
+ its tag (e.g. `v2.0.0`).
98
+
99
+ ## License
100
+
101
+ PyPatchMatch is released under the
102
+ [MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
103
+ contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
104
+ third-party code; their licenses are included in the `licenses/opencv` directory of the
105
+ wheel's `.dist-info`.
72
106
 
73
107
  ## README and COPYRIGHT by Younesse ANDAM
74
108
 
75
109
  @Author: Younesse ANDAM
76
110
 
77
- @Contact: younesse.andam@gmail.com
111
+ @Contact: <younesse.andam@gmail.com>
78
112
 
79
113
  Description:
80
114
 
@@ -85,12 +119,6 @@ C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graph
85
119
  (Proc. SIGGRAPH), vol.28, aug-2009
86
120
 
87
121
  For more information please refer to
88
- http://www.cs.princeton.edu/gfx/pubs/Barnes_2009_PAR/index.php
122
+ <https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
89
123
 
90
124
  Copyright (c) 2010-2011
91
-
92
- ## Requirements
93
-
94
- To run the project you need to install Opencv library and link it to your project.
95
- Opencv can be download it here
96
- http://opencv.org/downloads.html
@@ -80,9 +80,13 @@ def find_library() -> Path:
80
80
  candidate = Path(directory) / LIBRARY_NAME
81
81
  if candidate.is_file():
82
82
  return candidate
83
+ # Without a wheel for the platform, a failed build from the sdist installs the
84
+ # package without the library, see the overrides in pyproject.toml.
83
85
  raise OSError(
84
- f"{LIBRARY_NAME} not found in {list(package.__path__)}; "
85
- "reinstall PyPatchMatch or build it from source with OpenCV available"
86
+ f"{LIBRARY_NAME} not found in {list(package.__path__)}. Without a wheel for "
87
+ "this platform, PyPatchMatch is built from source, which needs a C++ "
88
+ "compiler, CMake and OpenCV. Install them and reinstall it without the "
89
+ "cache: pip install --force-reinstall --no-deps --no-cache-dir pypatchmatch"
86
90
  )
87
91
 
88
92
 
@@ -95,6 +99,8 @@ def load_library() -> ctypes.CDLL:
95
99
  lib.PM_set_verbose.restype = None
96
100
  lib.PM_free_pymat.argtypes = [CMatT]
97
101
  lib.PM_free_pymat.restype = None
102
+ lib.PM_last_error.argtypes = []
103
+ lib.PM_last_error.restype = ctypes.c_char_p
98
104
 
99
105
  lib.PM_inpaint.argtypes = [CMatT, CMatT, ctypes.c_int]
100
106
  lib.PM_inpaint2.argtypes = [CMatT, CMatT, CMatT, ctypes.c_int]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.0.0b1'
22
- __version_tuple__ = version_tuple = (2, 0, 0, 'b1')
21
+ __version__ = version = '2.0.0rc1'
22
+ __version_tuple__ = version_tuple = (2, 0, 0, 'rc1')
23
23
 
24
- __commit_id__ = commit_id = 'g987b0e524'
24
+ __commit_id__ = commit_id = 'g75098309c'
@@ -9,13 +9,11 @@
9
9
 
10
10
  namespace
11
11
  {
12
- static std::vector<double> kDistance2Similarity;
13
-
14
- void init_kDistance2Similarity()
12
+ std::vector<double> make_distance2similarity()
15
13
  {
16
14
  double base[11] = {1.0, 0.99, 0.96, 0.83, 0.38, 0.11, 0.02, 0.005, 0.0006, 0.0001, 0};
17
15
  int length = (PatchDistanceMetric::kDistanceScale + 1);
18
- kDistance2Similarity.resize(length);
16
+ std::vector<double> table(length);
19
17
  for (int i = 0; i < length; ++i)
20
18
  {
21
19
  double t = (double)i / length;
@@ -23,8 +21,17 @@ namespace
23
21
  int k = j + 1;
24
22
  double vj = (j < 11) ? base[j] : 0;
25
23
  double vk = (k < 11) ? base[k] : 0;
26
- kDistance2Similarity[i] = vj + (100 * t - j) * (vk - vj);
24
+ table[i] = vj + (100 * t - j) * (vk - vj);
27
25
  }
26
+ return table;
27
+ }
28
+
29
+ // Built on first use. The initialization of a local static is thread-safe, which
30
+ // matters because the Python bindings release the GIL during inpainting.
31
+ const std::vector<double> &distance2similarity()
32
+ {
33
+ static const std::vector<double> table = make_distance2similarity();
34
+ return table;
28
35
  }
29
36
 
30
37
  inline void _weighted_copy(const MaskedImage &source, int ys, int xs, cv::Mat &target, int yt, int xt, double weight)
@@ -37,7 +44,6 @@ namespace
37
44
  auto source_ptr = source.get_image(ys, xs);
38
45
  auto target_ptr = target.ptr<double>(yt, xt);
39
46
 
40
- #pragma unroll
41
47
  for (int c = 0; c < 3; ++c)
42
48
  target_ptr[c] += static_cast<double>(source_ptr[c]) * weight;
43
49
  target_ptr[3] += weight;
@@ -69,16 +75,11 @@ void Inpainting::_initialize_pyramid()
69
75
  source = source.downsample();
70
76
  m_pyramid.push_back(source);
71
77
  }
72
-
73
- if (kDistance2Similarity.size() == 0)
74
- {
75
- init_kDistance2Similarity();
76
- }
77
78
  }
78
79
 
79
80
  cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int random_seed)
80
81
  {
81
- srand(random_seed);
82
+ NearestNeighborField::seed_random(random_seed);
82
83
  const int nr_levels = m_pyramid.size();
83
84
 
84
85
  MaskedImage source, target;
@@ -124,7 +125,17 @@ cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int rando
124
125
  target = _expectation_maximization(source, target, level, verbose);
125
126
  }
126
127
 
127
- return target.image();
128
+ // Only the holes are filled. The votes of overlapping patches also change the known
129
+ // pixels near the holes, and the pyramid leaves globally masked pixels black, which
130
+ // are neither filled nor used as a source. Keep the input values of both.
131
+ cv::Mat result = target.image();
132
+ if (result.empty()) // OpenCV rejects empty operands in matrix expressions
133
+ return result;
134
+ cv::Mat keep = m_initial.mask() == 0;
135
+ if (!m_initial.global_mask().empty())
136
+ keep |= m_initial.global_mask() != 0;
137
+ m_initial.image().copyTo(result, keep);
138
+ return result;
128
139
  }
129
140
 
130
141
  // EM-Like algorithm (see "PatchMatch" - page 6).
@@ -211,6 +222,7 @@ void Inpainting::_expectation_step(
211
222
  auto source_size = nnf.source_size();
212
223
  auto target_size = nnf.target_size();
213
224
  const int patch_size = m_distance_metric->patch_size();
225
+ const auto &kDistance2Similarity = distance2similarity();
214
226
 
215
227
  for (int i = 0; i < source_size.height; ++i)
216
228
  {
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <cassert>
3
4
  #include <opencv2/core.hpp>
4
5
 
5
6
  class MaskedImage
@@ -1,6 +1,7 @@
1
1
  #include <algorithm>
2
2
  #include <iostream>
3
3
  #include <cmath>
4
+ #include <random>
4
5
 
5
6
  #include "masked_image.h"
6
7
  #include "nnf.h"
@@ -17,9 +18,35 @@ T clamp(T value, T min_value, T max_value)
17
18
  return std::min(std::max(value, min_value), max_value);
18
19
  }
19
20
 
21
+ namespace
22
+ {
23
+ // One generator per thread, so concurrent inpaintings with the same seed give the
24
+ // same results. It only drives the randomized search, which has to be reproducible
25
+ // from a seed, so a standard generator is the right choice. SonarCloud's PRNG rule
26
+ // (cpp:S2245) flags every declaration of its type, hence the NOSONAR markers.
27
+ std::mt19937 &random_engine() // NOSONAR
28
+ {
29
+ thread_local std::mt19937 engine; // NOSONAR
30
+ return engine;
31
+ }
32
+
33
+ // The modulo instead of std::uniform_int_distribution keeps the sequence identical
34
+ // on all platforms.
35
+ inline int random_int(int n)
36
+ {
37
+ return static_cast<int>(random_engine()() % static_cast<unsigned int>(n));
38
+ }
39
+ }
40
+
41
+ void NearestNeighborField::seed_random(unsigned int seed)
42
+ {
43
+ random_engine().seed(seed);
44
+ }
45
+
20
46
  void NearestNeighborField::_randomize_field(int max_retry, bool reset)
21
47
  {
22
48
  auto this_size = source_size();
49
+ auto this_target_size = target_size();
23
50
  for (int i = 0; i < this_size.height; ++i)
24
51
  {
25
52
  for (int j = 0; j < this_size.width; ++j)
@@ -37,8 +64,8 @@ void NearestNeighborField::_randomize_field(int max_retry, bool reset)
37
64
  int i_target = 0, j_target = 0;
38
65
  for (int t = 0; t < max_retry; ++t)
39
66
  {
40
- i_target = rand() % this_size.height;
41
- j_target = rand() % this_size.width;
67
+ i_target = random_int(this_target_size.height);
68
+ j_target = random_int(this_target_size.width);
42
69
  if (m_target.is_globally_masked(i_target, j_target))
43
70
  continue;
44
71
 
@@ -138,8 +165,8 @@ void NearestNeighborField::_minimize_link(int y, int x, int direction)
138
165
  int random_scale = (std::min(this_target_size.height, this_target_size.width) - 1) / 2;
139
166
  while (random_scale > 0)
140
167
  {
141
- int yp = this_ptr[0] + (rand() % (2 * random_scale + 1) - random_scale);
142
- int xp = this_ptr[1] + (rand() % (2 * random_scale + 1) - random_scale);
168
+ int yp = this_ptr[0] + (random_int(2 * random_scale + 1) - random_scale);
169
+ int xp = this_ptr[1] + (random_int(2 * random_scale + 1) - random_scale);
143
170
  yp = clamp(yp, 0, target_size().height - 1);
144
171
  xp = clamp(xp, 0, target_size().width - 1);
145
172
 
@@ -201,10 +228,9 @@ namespace
201
228
  const unsigned char *p_sgm = nullptr;
202
229
  const unsigned char *p_tgm = nullptr;
203
230
  if (!source.global_mask().empty())
204
- {
205
231
  p_sgm = source.global_mask().ptr<unsigned char>(yys, 0);
232
+ if (!target.global_mask().empty())
206
233
  p_tgm = target.global_mask().ptr<unsigned char>(yyt, 0);
207
- }
208
234
 
209
235
  const auto *p_sgy = source.grady().ptr<unsigned char>(yys, 0);
210
236
  const auto *p_tgy = target.grady().ptr<unsigned char>(yyt, 0);
@@ -216,7 +242,7 @@ namespace
216
242
  int xxs = xs + dx, xxt = xt + dx;
217
243
  wsum += 1;
218
244
 
219
- if (xxs <= 0 || xxs >= source_size.width - 1 || xxt <= 0 || xxt >= source_size.width - 1)
245
+ if (xxs <= 0 || xxs >= source_size.width - 1 || xxt <= 0 || xxt >= target_size.width - 1)
220
246
  {
221
247
  distance += PatchSSDDistanceMetric::kSSDScale;
222
248
  continue;
@@ -261,27 +287,6 @@ int PatchSSDDistanceMetric::operator()(const MaskedImage &source, int source_y,
261
287
  return distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
262
288
  }
263
289
 
264
- int DebugPatchSSDDistanceMetric::operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const
265
- {
266
- fprintf(stderr, "DebugPatchSSDDistanceMetric: %d %d %d %d\n", source.size().width, source.size().height, m_width, m_height);
267
- return distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
268
- }
269
-
270
- int RegularityGuidedPatchDistanceMetricV1::operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const
271
- {
272
- double dx = remainder(double(source_x - target_x) / source.size().width, m_dx1);
273
- double dy = remainder(double(source_y - target_y) / source.size().height, m_dy2);
274
-
275
- double score1 = sqrt(dx * dx + dy * dy) / m_scale;
276
- if (score1 < 0 || score1 > 1)
277
- score1 = 1;
278
- score1 *= PatchDistanceMetric::kDistanceScale;
279
-
280
- double score2 = distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
281
- double score = score1 * m_weight + score2 / (1 + m_weight);
282
- return static_cast<int>(score / (1 + m_weight));
283
- }
284
-
285
290
  int RegularityGuidedPatchDistanceMetricV2::operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const
286
291
  {
287
292
  if (target_y < 0 || target_y >= target.size().height || target_x < 0 || target_x >= target.size().width)
@@ -312,6 +317,11 @@ int RegularityGuidedPatchDistanceMetricV2::operator()(const MaskedImage &source,
312
317
  }
313
318
 
314
319
  double score2 = distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
315
- double score = score1 * m_weight + score2;
316
- return int(score / (1 + m_weight));
320
+ double score = (score1 * m_weight + score2) / (1 + m_weight);
321
+ // The distance indexes the similarity table in Inpainting, so keep it in range.
322
+ if (!(score > 0))
323
+ return 0;
324
+ if (score >= PatchDistanceMetric::kDistanceScale)
325
+ return PatchDistanceMetric::kDistanceScale;
326
+ return static_cast<int>(score);
317
327
  }
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <stdexcept>
3
4
  #include <opencv2/core.hpp>
4
5
  #include "masked_image.h"
5
6
 
@@ -87,6 +88,9 @@ public:
87
88
 
88
89
  void minimize(int nr_pass);
89
90
 
91
+ // Seeds the random search of the calling thread.
92
+ static void seed_random(unsigned int seed);
93
+
90
94
  private:
91
95
  inline int _distance(int source_y, int source_x, int target_y, int target_x)
92
96
  {
@@ -111,44 +115,19 @@ public:
111
115
  static const int kSSDScale;
112
116
  };
113
117
 
114
- class DebugPatchSSDDistanceMetric : public PatchDistanceMetric
115
- {
116
- public:
117
- DebugPatchSSDDistanceMetric(int patch_size, int width, int height) : PatchDistanceMetric(patch_size), m_width(width), m_height(height) {}
118
- virtual int operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const;
119
-
120
- protected:
121
- int m_width, m_height;
122
- };
123
-
124
- class RegularityGuidedPatchDistanceMetricV1 : public PatchDistanceMetric
125
- {
126
- public:
127
- RegularityGuidedPatchDistanceMetricV1(int patch_size, double dx1, double dy1, double dx2, double dy2, double weight)
128
- : PatchDistanceMetric(patch_size), m_dx1(dx1), m_dy1(dy1), m_dx2(dx2), m_dy2(dy2), m_weight(weight)
129
- {
130
-
131
- assert(m_dy1 == 0);
132
- assert(m_dx2 == 0);
133
- m_scale = sqrt(m_dx1 * m_dx1 + m_dy2 * m_dy2) / 4;
134
- }
135
- virtual int operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const;
136
-
137
- protected:
138
- double m_dx1, m_dy1, m_dx2, m_dy2;
139
- double m_scale, m_weight;
140
- };
141
-
142
118
  class RegularityGuidedPatchDistanceMetricV2 : public PatchDistanceMetric
143
119
  {
144
120
  public:
145
121
  RegularityGuidedPatchDistanceMetricV2(int patch_size, cv::Mat ijmap, double weight)
146
122
  : PatchDistanceMetric(patch_size), m_ijmap(ijmap), m_weight(weight)
147
123
  {
124
+ // The distance is divided by 1 + weight, so a negative weight flips its sign.
125
+ if (!(weight >= 0))
126
+ throw std::invalid_argument("the guide weight must be >= 0");
148
127
  }
149
128
  virtual int operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const;
150
129
 
151
130
  protected:
152
131
  cv::Mat m_ijmap;
153
- double m_width, m_height, m_weight;
132
+ double m_weight;
154
133
  };