PyPatchMatch 2.1.0__tar.gz → 2.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 (32) hide show
  1. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/CLAUDE.md +22 -24
  2. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/CMakeLists.txt +2 -1
  3. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/PKG-INFO +29 -11
  4. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/README.md +27 -9
  5. pypatchmatch-2.2.0/examples/images/camera_aged.png +0 -0
  6. pypatchmatch-2.2.0/examples/images/camera_damaged.png +0 -0
  7. pypatchmatch-2.2.0/examples/images/camera_damaged_mask.png +0 -0
  8. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/examples/images/forest_pruned.bmp +0 -0
  9. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/_lib.py +3 -2
  10. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/_version.py +3 -3
  11. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/inpaint.cpp +441 -48
  12. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/inpaint.h +11 -0
  13. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/masked_image.cpp +34 -18
  14. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/masked_image.h +49 -21
  15. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/nnf.cpp +212 -126
  16. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/nnf.h +59 -28
  17. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/pyinterface.cpp +35 -42
  18. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/pyproject.toml +10 -4
  19. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/scripts/evaluate_inpainting.py +154 -76
  20. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/tests/test_e2e.py +103 -34
  21. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/tests/test_patch_match.py +67 -25
  22. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/LICENSE +0 -0
  23. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/SECURITY.md +0 -0
  24. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/examples/images/chelsea.png +0 -0
  25. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/__init__.py +0 -0
  26. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/csrc/pyinterface.h +0 -0
  27. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/patch_match.py +0 -0
  28. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/patchmatch/py.typed +0 -0
  29. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/scripts/build_opencv.py +0 -0
  30. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/scripts/render_readme_example.py +0 -0
  31. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/tests/conftest.py +0 -0
  32. {pypatchmatch-2.1.0 → pypatchmatch-2.2.0}/tests/test_properties.py +0 -0
@@ -1,10 +1,9 @@
1
1
  # CLAUDE.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in
4
- this repository. The details of the native code, the C interface, packaging and CI are
5
- path-scoped rules in `.claude/rules/`, which load when a matching file is read or
6
- edited; read the rule before changing such a file through Bash. The sanitizer, C++
7
- coverage and fill-quality runs are skills in `.claude/skills/`.
3
+ The details of the native code, the C interface, packaging and CI are path-scoped rules
4
+ in `.claude/rules/`, which load when a matching file is read or edited; read the rule
5
+ before changing such a file through Bash. The sanitizer, C++ coverage and fill-quality
6
+ runs are skills in `.claude/skills/`.
8
7
 
9
8
  ## Project
10
9
 
@@ -27,24 +26,18 @@ uv run pre-commit install
27
26
  uv run pytest # all tests, with the coverage gate
28
27
  uv run pytest -m "not e2e" # fast unit tests only
29
28
  uv run pytest -m e2e --no-cov # end-to-end tests only
30
- uv run pytest tests/test_patch_match.py::test_inpaint_global_mask --no-cov
31
- uv run pytest -k regularity --no-cov
32
29
  uv run mypy # strict, also a pre-commit hook
33
30
  uv run pre-commit run --all-files # what the CI lint job runs
34
- uv run ruff check --fix . && uv run ruff format .
35
31
  uv build # sdist and wheel into dist/
36
32
  ```
37
33
 
38
34
  - Pass `--no-cov` for any partial run: `addopts` always enables coverage, and
39
35
  `fail_under = 95` (branch coverage, subprocesses included through
40
36
  `patch = ["subprocess"]`) fails the run otherwise.
41
- - Use `uv run pytest`, not `python -m pytest`. The latter puts the repository root on
42
- `sys.path`, which imports the source tree `patchmatch/` without the compiled library;
43
- the symptoms are the warning `patchmatch failed to load: libpatchmatch.* not found`
44
- and `RuntimeError: the patchmatch native library is not available` in every test.
45
- - `--cov=patchmatch` measures the source tree, so coverage only works with the editable
46
- install that `uv sync` creates. With a non-editable install (`uv sync --no-editable`,
47
- `uv pip install .`) it reports 0 % and the gate fails.
37
+ - Keep the editable install that `uv sync` creates. With a non-editable one
38
+ (`uv sync --no-editable`, `uv pip install .`), `--cov=patchmatch` measures the unused
39
+ source tree and reports 0 %, and `python -m pytest` imports the source tree from the
40
+ repository root, without the compiled library.
48
41
  - `uv sync` rebuilds the library only when a `tool.uv.cache-keys` entry changes
49
42
  (`CMakeLists.txt`, `patchmatch/csrc/*`, `pyproject.toml`, git commit or tags). Force it
50
43
  with `uv sync --reinstall-package pypatchmatch`.
@@ -69,11 +62,11 @@ Other entry points:
69
62
  - `uv run python scripts/render_readme_example.py` runs `py_example.py` and renders the
70
63
  picture at the top of the README, `examples/images/readme_example.jpg`; rerun it and
71
64
  commit the picture after changes that affect the fills.
72
- - CMake options: `PATCHMATCH_BUILD_EXAMPLES` (OFF), `PATCHMATCH_FAST_MATH` (ON,
73
- `-ffast-math` or `/fp:fast`), `PATCHMATCH_SANITIZE` (OFF, ASan and UBSan with
74
- `float-cast-overflow`, every finding aborts; turns off fast math, which lets the
75
- compiler drop checks; GCC and Clang only), `PATCHMATCH_COVERAGE` (OFF, gcov counters
76
- at `-Og`; GCC and Clang only).
65
+ - `uv run --group gui gui/app.py` starts the Gradio GUI (paint holes, compare the fill).
66
+ No CI job installs gradio, so after API changes run its test with
67
+ `uv run --group gui pytest gui --no-cov` and try the GUI by hand.
68
+ - CMake options: the `option()` lines of `CMakeLists.txt`. `PATCHMATCH_SANITIZE` turns
69
+ off fast math, which lets the compiler drop checks.
77
70
 
78
71
  ## Architecture
79
72
 
@@ -109,12 +102,15 @@ CMake installs the shared library into the package directory.
109
102
  members, `k` constants. Commit a reformat separately and add its hash to
110
103
  `.git-blame-ignore-revs`.
111
104
  - The package ships `py.typed`; public functions are fully annotated. mypy checks
112
- `patchmatch` and `tests` in strict mode (`[tool.mypy]`); the tests have to use the
113
- API correctly but need no annotations of their own. A deliberately wrong argument
105
+ `examples`, `patchmatch`, `scripts` and `tests` in strict mode (`[tool.mypy]`); the
106
+ tests have to use the API correctly but need no annotations of their own. Only the
107
+ examples annotate their locals and pass arguments by keyword, for readers who copy
108
+ them. A deliberately wrong argument
114
109
  goes through a test parameter instead of a `# type: ignore`.
115
110
  - Backwards compatibility: `from patchmatch import patch_match` and
116
111
  `patch_match.CShapeT`/`CMatT` keep working (tested).
117
- - Comments explain why: which crash a check prevents, why a construct is avoided.
112
+ - Comments explain why, in one short line where the code would otherwise be misread or
113
+ wrongly simplified; details and measurements go into the commit message.
118
114
  - No `NOSONAR` markers: change the code so that the SonarCloud rule is satisfied, and ask
119
115
  when that seems impossible. The only exception is `nnf.cpp`: cpp:S2245 flags every
120
116
  declaration of `std::mt19937`, which only drives the randomized search.
@@ -140,4 +136,6 @@ CMake installs the shared library into the package directory.
140
136
  `invoke-ai/PyPatchMatch`. `gh` resolves the default repository to upstream, so pass
141
137
  `-R mauwii/PyPatchMatch`.
142
138
  - Releases: creating a GitHub release with a tag like `v2.0.0` (or `v2.0.0rc2` for
143
- pre-releases) builds the wheels and publishes them to PyPI.
139
+ pre-releases) builds the wheels and publishes them to PyPI. The workflow `Fill
140
+ quality` compares the release with the last stable one; read its summary before
141
+ approving the job `publish`.
@@ -57,8 +57,9 @@ if(PATCHMATCH_SANITIZE)
57
57
  endif()
58
58
  # GCC's -fsanitize=undefined leaves out float-cast-overflow
59
59
  set(PATCHMATCH_SANITIZERS -fsanitize=address,undefined,float-cast-overflow)
60
+ # -UNDEBUG keeps the asserts, which check the incremental distances of the propagation
60
61
  target_compile_options(patchmatch PRIVATE ${PATCHMATCH_SANITIZERS}
61
- -fno-sanitize-recover=all -fno-omit-frame-pointer -g)
62
+ -fno-sanitize-recover=all -fno-omit-frame-pointer -g -UNDEBUG)
62
63
  target_link_options(patchmatch PRIVATE ${PATCHMATCH_SANITIZERS})
63
64
  endif()
64
65
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPatchMatch
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: This library implements the PatchMatch based inpainting algorithm.
5
5
  Keywords: inpainting,patchmatch,image-processing,opencv
6
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>
@@ -30,20 +30,28 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  # PatchMatch based Inpainting
32
32
 
33
- [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=mauwii_PyPatchMatch&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=mauwii_PyPatchMatch)
34
- [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
35
33
  [![PyPI](https://img.shields.io/pypi/v/PyPatchMatch)](https://pypi.org/project/PyPatchMatch/)
36
- [![Downloads](https://static.pepy.tech/badge/pypatchmatch)](https://pepy.tech/projects/pypatchmatch)
34
+ [![Python](https://img.shields.io/pypi/pyversions/PyPatchMatch)](https://pypi.org/project/PyPatchMatch/)
35
+ [![Downloads](https://img.shields.io/pepy/dt/pypatchmatch)](https://pepy.tech/projects/pypatchmatch)
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
37
+
38
+ [![Tests](https://img.shields.io/github/actions/workflow/status/mauwii/PyPatchMatch/ci.yml?branch=main&label=tests)](https://github.com/mauwii/PyPatchMatch/actions/workflows/ci.yml?query=branch%3Amain)
39
+ [![Coverage](https://img.shields.io/sonar/coverage/mauwii_PyPatchMatch?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/component_measures?id=mauwii_PyPatchMatch&metric=coverage)
40
+ [![Quality Gate](https://img.shields.io/sonar/quality_gate/mauwii_PyPatchMatch?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/summary/new_code?id=mauwii_PyPatchMatch)
41
+
42
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
37
43
  [![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)
38
44
  [![ponytail](https://img.shields.io/badge/over--engineering-ponytail_audited-111111)](https://github.com/DietrichGebert/ponytail)
39
- [![caveman](https://img.shields.io/badge/agent-caveman-F0A63C)](https://github.com/JuliusBrussee/caveman)
40
45
 
41
46
  This library implements the PatchMatch based inpainting algorithm. It provides both C++
42
47
  and Python interfaces. This implementation is heavily based on the implementation by
43
48
  Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
44
- with some bug fixes, and updates.
49
+ with some bug fixes, and updates. This repository continues
50
+ [vacancy/PyPatchMatch](https://github.com/vacancy/PyPatchMatch) by Jiayuan Mao, through
51
+ the forks [lkwq007/PyPatchMatch](https://github.com/lkwq007/PyPatchMatch) and
52
+ [invoke-ai/PyPatchMatch](https://github.com/invoke-ai/PyPatchMatch).
45
53
 
46
- ![Birdhouses removed from a photo by patchmatch.inpaint](https://raw.githubusercontent.com/mauwii/PyPatchMatch/v2.1.0/examples/images/readme_example.jpg)
54
+ ![Birdhouses removed from a photo by patchmatch.inpaint](https://raw.githubusercontent.com/mauwii/PyPatchMatch/v2.2.0/examples/images/readme_example.jpg)
47
55
 
48
56
  From left to right: the photo, the input of
49
57
  [examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)
@@ -56,9 +64,9 @@ pip install PyPatchMatch
56
64
  ```
57
65
 
58
66
  Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
59
- (x64) ship the compiled library with a statically linked OpenCV, so no compiler or
60
- OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
61
- pip builds from the source distribution, which requires a C++17 compiler, CMake and the
67
+ (x64 and ARM64) ship the compiled library with a statically linked OpenCV, so no
68
+ compiler or OpenCV installation is needed. On other platforms, e.g. Alpine Linux, pip
69
+ builds from the source distribution, which requires a C++17 compiler, CMake and the
62
70
  OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
63
71
 
64
72
  If that build fails, PyPatchMatch is installed without the native library instead of
@@ -98,7 +106,11 @@ The previous import path `from patchmatch import patch_match` keeps working.
98
106
 
99
107
  `patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
100
108
  pixels in each direction. Larger patches follow larger structures but are much slower;
101
- the default of 15 compares 31x31 patches, the examples use 3.
109
+ the default of 15 compares 31x31 patches, the examples use 3. Below 5, the coarse levels
110
+ that lay out the fill are also tried with a radius of 5, which is kept where it fits
111
+ the surroundings better, so that repeating patterns continue across larger holes. Above
112
+ 7, coarse levels narrower than six patches use the largest radius that fits, but at
113
+ least 7: larger patches found no sources there and left the fill flat.
102
114
 
103
115
  `patchmatch.set_random_seed(seed)` sets the seed of the randomized search, modulo
104
116
  `2**32`, and `patchmatch.set_verbose(True)` prints the progress of the native code to
@@ -136,8 +148,14 @@ uv run pre-commit install # enable ruff and the other hooks on commit
136
148
  uv run pytest # run the test suite
137
149
  uv run pytest -m "not e2e" # only the fast unit tests
138
150
  uv run python scripts/evaluate_inpainting.py # measure the fill quality
151
+ uv run --group gui gui/app.py # draw masks in the browser, http://127.0.0.1:7860
139
152
  ```
140
153
 
154
+ The GUI tries new images quickly, without writing a script for each one, e.g. to pick
155
+ new examples or to verify edge cases: load an image, paint the holes (every painted
156
+ pixel is one) and compare the original with the fill. Its test runs with
157
+ `uv run --group gui pytest gui --no-cov`.
158
+
141
159
  The evaluation cuts holes into the images of `examples/images`, compares the fills with
142
160
  the original content and writes `examples/images/evaluation.html`, which shows them side
143
161
  by side. `--baseline` evaluates another build of the library as well, e.g. of the main
@@ -1,17 +1,25 @@
1
1
  # PatchMatch based Inpainting
2
2
 
3
- [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=mauwii_PyPatchMatch&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=mauwii_PyPatchMatch)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
5
3
  [![PyPI](https://img.shields.io/pypi/v/PyPatchMatch)](https://pypi.org/project/PyPatchMatch/)
6
- [![Downloads](https://static.pepy.tech/badge/pypatchmatch)](https://pepy.tech/projects/pypatchmatch)
4
+ [![Python](https://img.shields.io/pypi/pyversions/PyPatchMatch)](https://pypi.org/project/PyPatchMatch/)
5
+ [![Downloads](https://img.shields.io/pepy/dt/pypatchmatch)](https://pepy.tech/projects/pypatchmatch)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
7
+
8
+ [![Tests](https://img.shields.io/github/actions/workflow/status/mauwii/PyPatchMatch/ci.yml?branch=main&label=tests)](https://github.com/mauwii/PyPatchMatch/actions/workflows/ci.yml?query=branch%3Amain)
9
+ [![Coverage](https://img.shields.io/sonar/coverage/mauwii_PyPatchMatch?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/component_measures?id=mauwii_PyPatchMatch&metric=coverage)
10
+ [![Quality Gate](https://img.shields.io/sonar/quality_gate/mauwii_PyPatchMatch?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/summary/new_code?id=mauwii_PyPatchMatch)
11
+
12
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
7
13
  [![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)
8
14
  [![ponytail](https://img.shields.io/badge/over--engineering-ponytail_audited-111111)](https://github.com/DietrichGebert/ponytail)
9
- [![caveman](https://img.shields.io/badge/agent-caveman-F0A63C)](https://github.com/JuliusBrussee/caveman)
10
15
 
11
16
  This library implements the PatchMatch based inpainting algorithm. It provides both C++
12
17
  and Python interfaces. This implementation is heavily based on the implementation by
13
18
  Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
14
- with some bug fixes, and updates.
19
+ with some bug fixes, and updates. This repository continues
20
+ [vacancy/PyPatchMatch](https://github.com/vacancy/PyPatchMatch) by Jiayuan Mao, through
21
+ the forks [lkwq007/PyPatchMatch](https://github.com/lkwq007/PyPatchMatch) and
22
+ [invoke-ai/PyPatchMatch](https://github.com/invoke-ai/PyPatchMatch).
15
23
 
16
24
  ![Birdhouses removed from a photo by patchmatch.inpaint](examples/images/readme_example.jpg)
17
25
 
@@ -26,9 +34,9 @@ pip install PyPatchMatch
26
34
  ```
27
35
 
28
36
  Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
29
- (x64) ship the compiled library with a statically linked OpenCV, so no compiler or
30
- OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
31
- pip builds from the source distribution, which requires a C++17 compiler, CMake and the
37
+ (x64 and ARM64) ship the compiled library with a statically linked OpenCV, so no
38
+ compiler or OpenCV installation is needed. On other platforms, e.g. Alpine Linux, pip
39
+ builds from the source distribution, which requires a C++17 compiler, CMake and the
32
40
  OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
33
41
 
34
42
  If that build fails, PyPatchMatch is installed without the native library instead of
@@ -68,7 +76,11 @@ The previous import path `from patchmatch import patch_match` keeps working.
68
76
 
69
77
  `patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
70
78
  pixels in each direction. Larger patches follow larger structures but are much slower;
71
- the default of 15 compares 31x31 patches, the examples use 3.
79
+ the default of 15 compares 31x31 patches, the examples use 3. Below 5, the coarse levels
80
+ that lay out the fill are also tried with a radius of 5, which is kept where it fits
81
+ the surroundings better, so that repeating patterns continue across larger holes. Above
82
+ 7, coarse levels narrower than six patches use the largest radius that fits, but at
83
+ least 7: larger patches found no sources there and left the fill flat.
72
84
 
73
85
  `patchmatch.set_random_seed(seed)` sets the seed of the randomized search, modulo
74
86
  `2**32`, and `patchmatch.set_verbose(True)` prints the progress of the native code to
@@ -106,8 +118,14 @@ uv run pre-commit install # enable ruff and the other hooks on commit
106
118
  uv run pytest # run the test suite
107
119
  uv run pytest -m "not e2e" # only the fast unit tests
108
120
  uv run python scripts/evaluate_inpainting.py # measure the fill quality
121
+ uv run --group gui gui/app.py # draw masks in the browser, http://127.0.0.1:7860
109
122
  ```
110
123
 
124
+ The GUI tries new images quickly, without writing a script for each one, e.g. to pick
125
+ new examples or to verify edge cases: load an image, paint the holes (every painted
126
+ pixel is one) and compare the original with the fill. Its test runs with
127
+ `uv run --group gui pytest gui --no-cov`.
128
+
111
129
  The evaluation cuts holes into the images of `examples/images`, compares the fills with
112
130
  the original content and writes `examples/images/evaluation.html`, which shows them side
113
131
  by side. `--baseline` evaluates another build of the library as well, e.g. of the main
@@ -82,8 +82,9 @@ def find_library() -> Path:
82
82
  )
83
83
 
84
84
 
85
- def load_library() -> ctypes.CDLL:
86
- lib = ctypes.CDLL(str(find_library()))
85
+ def load_library(path: Path | None = None) -> ctypes.CDLL:
86
+ """Load the installed library, or the build at ``path``."""
87
+ lib = ctypes.CDLL(str(path or find_library()))
87
88
 
88
89
  lib.PM_set_random_seed.argtypes = [ctypes.c_uint]
89
90
  lib.PM_set_random_seed.restype = None
@@ -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.1.0'
22
- __version_tuple__ = version_tuple = (2, 1, 0)
21
+ __version__ = version = '2.2.0'
22
+ __version_tuple__ = version_tuple = (2, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g2f558d40a'
24
+ __commit_id__ = commit_id = 'gd6affc5f5'