diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..c4639fb --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,5 @@ +editorconfig: true +overrides: + - files: '*.{yaml,yml}' + options: + singleQuote: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d4982eb..5a46d87 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -10,5 +10,7 @@ "ms-python.vscode-pylance", "redhat.vscode-yaml", "tamasfe.even-better-toml", + "github.vscode-github-actions", + "esbenp.prettier-vscode" ] } diff --git a/patchmatch/__init__.py b/patchmatch/__init__.py index 815717a..70725a3 100644 --- a/patchmatch/__init__.py +++ b/patchmatch/__init__.py @@ -1,3 +1,3 @@ __app_id__ = "mauwii/PyPatchMatch" __app_name__ = "PyPatchMatch" -__version__ = "1.0.0" +__version__ = "1.0.1" diff --git a/pyproject.toml b/pyproject.toml index 48a5802..c866783 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dynamic=["version"] license={file="LICENSE"} name='PyPatchMatch' readme={content-type="text/markdown", file="README.md"} -requires-python=">=3.9" +requires-python=">=3.9,<3.11" [project.urls] 'Source Code'='https://github.com/mauwii/PyPatchMatch' @@ -47,13 +47,9 @@ include=["patchmatch", "patchmatch.csrc"] exclude=''' /( .git - | .hg - | .mypy_cache - | .tox | .venv - | _build - | buck-out | build + | csrc | dist )/ ''' @@ -62,6 +58,9 @@ line-length=88 source=['examples', 'patchmatch'] target-version=['py39'] +[tool.coverage.report] +fail_under=75 + [tool.isort] profile="black" diff --git a/test_patch_match.py b/test_patch_match.py new file mode 100644 index 0000000..3384fba --- /dev/null +++ b/test_patch_match.py @@ -0,0 +1,90 @@ +import tempfile + +import numpy as np +import pytest +from PIL import Image + +from patchmatch import patch_match + + +@pytest.fixture +def input_image(): + return Image.open("./examples/images/forest_pruned.bmp") + + +@pytest.fixture +def mask(input_image): + return np.zeros((input_image.height, input_image.width), dtype=np.uint8) + + +@pytest.fixture +def ijmap(input_image): + return np.zeros((input_image.height, input_image.width, 3), dtype=np.float32) + + +def test_inpaint_custom_patch_size(input_image): + result = Image.fromarray(patch_match.inpaint(image=input_image, patch_size=3)) + assert isinstance(result, Image.Image) + + +def test_inpaint_global_mask(input_image): + patch_match.set_verbose(True) + source = np.array(input_image) + source[:100, :100] = 255 + global_mask = np.zeros_like(source[..., 0]) + global_mask[:100, :100] = 1 + result = Image.fromarray( + patch_match.inpaint(image=source, global_mask=global_mask, patch_size=3) + ) + assert isinstance(result, Image.Image) + + +def test_download_url_to_file(): + url = "https://github.com/mauwii/PyPatchMatch/raw/main/examples/images/forest.bmp" + dst = tempfile.mkstemp().__dir__ + patch_match.download_url_to_file(url=url, dst=str(dst)) + read_image = Image.open(str(dst)) + assert isinstance(read_image, Image.Image) + + +def test_inpaint_regularity(input_image, ijmap): + ijmap = ijmap + result = patch_match.inpaint_regularity(input_image, None, ijmap, patch_size=3) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_guide_weight(input_image, ijmap): + ijmap = ijmap + result = patch_match.inpaint_regularity(input_image, None, ijmap, guide_weight=0.5) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_global_mask(input_image, mask, ijmap): + ijmap = ijmap + global_mask = mask + result = patch_match.inpaint_regularity( + input_image, None, ijmap, global_mask=global_mask + ) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_mask(input_image, mask, ijmap): + ijmap = ijmap + mask = mask + result = patch_match.inpaint_regularity(input_image, mask, ijmap) + assert isinstance(result, np.ndarray) + + +def test_canonize_mask_array_2d_uint8(): + mask = np.zeros((10, 10), dtype=np.uint8) + result = patch_match._canonize_mask_array(mask) + assert isinstance(result, np.ndarray) + assert result.ndim == 3 + assert result.shape[2] == 1 + assert result.dtype == np.uint8 + + +def test_canonize_mask_array_invalid_input(): + with pytest.raises(AssertionError): + mask = np.zeros((10, 10, 3), dtype=np.uint8) + patch_match._canonize_mask_array(mask) diff --git a/tests/test_inpaint.py b/tests/test_inpaint.py deleted file mode 100644 index e52a702..0000000 --- a/tests/test_inpaint.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -""" -Unit tests for inpainting -""" -import unittest - -from PIL import Image - -from patchmatch import patch_match - -img_input = Image.open("./examples/images/forest_pruned.bmp") -result = patch_match.inpaint(img_input, patch_size=3) -img_result = Image.fromarray(result) - - -class TestInpaint(unittest.TestCase): - def test_result_type(self, img=img_result): - assert isinstance(img, Image.Image) - - def test_result_size(self, image=img_input, img=img_result): - assert img.size == image.size - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_patch_match.py b/tests/test_patch_match.py new file mode 100644 index 0000000..5a099e7 --- /dev/null +++ b/tests/test_patch_match.py @@ -0,0 +1,133 @@ +import os + +import numpy as np +import pytest +from PIL import Image + +from patchmatch import patch_match + + +@pytest.fixture +def input_image(): + return Image.open("./examples/images/forest_pruned.bmp") + + +@pytest.fixture +def ijmap(input_image): + return np.zeros((input_image.height, input_image.width, 3), dtype=np.float32) + + +@pytest.fixture +def mask(input_image): + return np.zeros((input_image.height, input_image.width), dtype=np.uint8) + + +@pytest.fixture +def temp_file(): + filename = "temp.txt" + with open(filename, "w") as f: + f.write("test") + yield filename + os.remove(filename) + + +def test_inpaint_custom_patch_size(input_image, mask): + result = patch_match.inpaint(input_image, mask, patch_size=1) + assert isinstance(result, np.ndarray) + + +def test_inpaint_global_mask(input_image): + source = np.array(input_image) + source[:100, :100] = 255 + global_mask = np.zeros_like(source[..., 0]) + global_mask[:100, :100] = 1 + result = Image.fromarray( + patch_match.inpaint(image=source, global_mask=global_mask, patch_size=3) + ) + assert isinstance(result, Image.Image) + + +def test_np_to_pymat(): + npmat = np.zeros((10, 10, 3), dtype=np.uint8) + result = patch_match.np_to_pymat(npmat) + assert isinstance(result, patch_match.CMatT) + + +def test_pymat_to_np(): + pymat = patch_match.CMatT(None, patch_match.CShapeT(10, 10, 3), 0) + npmat = np.zeros((10, 10, 3), dtype=np.uint8) + pymat.data_ptr = npmat.ctypes.data + result = patch_match.pymat_to_np(pymat) + assert isinstance(result, np.ndarray) + assert result.shape == npmat.shape + assert result.dtype == npmat.dtype + assert np.allclose(result, npmat) + + +def test_canonize_mask_array_2d_uint8(): + mask = np.zeros((10, 10), dtype=np.uint8) + result = patch_match._canonize_mask_array(mask) + assert isinstance(result, np.ndarray) + assert result.ndim == 3 + assert result.shape[2] == 1 + assert result.dtype == np.uint8 + + +def test_canonize_mask_array_3d_uint8(): + mask = np.zeros((10, 10, 1), dtype=np.uint8) + result = patch_match._canonize_mask_array(mask) + assert isinstance(result, np.ndarray) + assert result.ndim == 3 + assert result.shape[2] == 1 + assert result.dtype == np.uint8 + + +def test_canonize_mask_array_pil_image(): + mask = Image.new("L", (10, 10)) + result = patch_match._canonize_mask_array(mask) + assert isinstance(result, np.ndarray) + assert result.ndim == 3 + assert result.shape[2] == 1 + assert result.dtype == np.uint8 + + +def test_canonize_mask_array_invalid_input(): + with pytest.raises(AssertionError): + mask = np.zeros((10, 10, 3), dtype=np.uint8) + patch_match._canonize_mask_array(mask) + + +def test_inpaint_regularity(input_image, ijmap): + result = patch_match.inpaint_regularity(input_image, None, ijmap, patch_size=3) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_patch_size(input_image, ijmap): + result = patch_match.inpaint_regularity(input_image, None, ijmap, patch_size=5) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_guide_weight(input_image, ijmap): + result = patch_match.inpaint_regularity( + input_image, None, ijmap, patch_size=3, guide_weight=0.5 + ) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_global_mask(input_image, ijmap, mask): + global_mask = mask + result = patch_match.inpaint_regularity( + input_image, None, ijmap, global_mask=global_mask, patch_size=3 + ) + assert isinstance(result, np.ndarray) + + +def test_inpaint_regularity_custom_mask(input_image, ijmap, mask): + result = patch_match.inpaint_regularity(input_image, mask, ijmap, patch_size=3) + assert isinstance(result, np.ndarray) + + +def test_download_url_to_file(temp_file): + url = "https://www.google.com" + patch_match.download_url_to_file(url, temp_file) + assert os.path.exists(temp_file)