Skip to content

Commit

Permalink
Merge pull request #7322 from mkcor/image0-image1
Browse files Browse the repository at this point in the history
Standardize function signature of compare_images.
  • Loading branch information
stefanv committed May 14, 2024
2 parents f5f4b0c + e406346 commit 765e568
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 18 deletions.
3 changes: 3 additions & 0 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Version 0.25
and remove `test_gaussian.py::test_deprecated_gaussian_output`.
Make arguments after the deprecated `output` parameter, keyword only:
`gaussian(image, sigma, *, ...)`.
* In `skimage/util/compare.py`, remove deprecated parameter `image2` as
well as `test_compare_images_replaced_param` in `skimage/util/tests/test_compare.py`
(and all `pytest.warns(FutureWarning)` context managers there).

Version 0.26
------------
Expand Down
83 changes: 76 additions & 7 deletions skimage/util/compare.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,85 @@
import functools
import warnings
from itertools import product

import numpy as np

from .dtype import img_as_float
from itertools import product


def compare_images(image1, image2, method='diff', *, n_tiles=(8, 8)):
def _rename_image_params(func):
wm_images = (
"Since version 0.24, the two input images are named `image0` and "
"`image1` (instead of `image1` and `image2`, respectively). Please use "
"`image0, image1` to avoid this warning for now, and avoid an error "
"from version 0.26 onwards."
)

wm_method = (
"Starting in version 0.24, all arguments following `image0, image1` "
"(including `method`) will be keyword-only. Please pass `method=` "
"in the function call to avoid this warning for now, and avoid an error "
"from version 0.26 onwards."
)

@functools.wraps(func)
def wrapper(*args, **kwargs):
# Turn all args into kwargs
for i, (value, param) in enumerate(
zip(args, ["image0", "image1", "method", "n_tiles"])
):
if i >= 2:
warnings.warn(wm_method, category=FutureWarning, stacklevel=2)
if param in kwargs:
raise ValueError(
f"{param} passed both as positional and keyword argument."
)
else:
kwargs[param] = value
args = tuple()

# Account for `image2` if given
if "image2" in kwargs.keys():
warnings.warn(wm_images, category=FutureWarning, stacklevel=2)

# Safely move `image2` to `image1` if that's empty
if "image1" in kwargs.keys():
# Safely move `image1` to `image0`
if "image0" in kwargs.keys():
raise ValueError(
"Three input images given; please use only `image0` "
"and `image1`."
)
kwargs["image0"] = kwargs.pop("image1")
kwargs["image1"] = kwargs.pop("image2")

return func(*args, **kwargs)

return wrapper


@_rename_image_params
def compare_images(image0, image1, *, method='diff', n_tiles=(8, 8)):
"""
Return an image showing the differences between two images.
.. versionadded:: 0.16
Parameters
----------
image1, image2 : ndarray, shape (M, N)
image0, image1 : ndarray, shape (M, N)
Images to process, must be of the same shape.
.. versionchanged:: 0.24
`image1` and `image2` were renamed into `image0` and `image1`
respectively.
method : string, optional
Method used for the comparison.
Valid values are {'diff', 'blend', 'checkerboard'}.
Details are provided in the note section.
.. versionchanged:: 0.24
This parameter and following ones are keyword-only.
n_tiles : tuple, optional
Used only for the `checkerboard` method. Specifies the number
of tiles (row, column) to divide the image.
Expand All @@ -32,19 +94,26 @@ def compare_images(image1, image2, method='diff', *, n_tiles=(8, 8)):
``'diff'`` computes the absolute difference between the two images.
``'blend'`` computes the mean value.
``'checkerboard'`` makes tiles of dimension `n_tiles` that display
alternatively the first and the second image.
alternatively the first and the second image. Note that images must be
2-dimensional to be compared with the checkerboard method.
"""
if image1.shape != image2.shape:

if image1.shape != image0.shape:
raise ValueError('Images must have the same shape.')

img1 = img_as_float(image1)
img2 = img_as_float(image2)
img1 = img_as_float(image0)
img2 = img_as_float(image1)

if method == 'diff':
comparison = np.abs(img2 - img1)
elif method == 'blend':
comparison = 0.5 * (img2 + img1)
elif method == 'checkerboard':
if img1.ndim != 2:
raise ValueError(
'Images must be 2-dimensional to be compared with the '
'checkerboard method.'
)
shapex, shapey = img1.shape
mask = np.full((shapex, shapey), False)
stepx = int(shapex / n_tiles[0])
Expand Down
61 changes: 50 additions & 11 deletions skimage/util/tests/test_compare.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import numpy as np

from skimage._shared.testing import assert_array_equal
from skimage._shared import testing
import pytest

from skimage.util.compare import compare_images
from skimage._shared.testing import assert_stacklevel


def test_compate_images_ValueError_shape():
def test_compare_images_ValueError_shape():
img1 = np.zeros((10, 10), dtype=np.uint8)
img2 = np.zeros((10, 1), dtype=np.uint8)
with testing.raises(ValueError):
with pytest.raises(ValueError):
compare_images(img1, img2)


def test_compare_images_ValueError_args():
a = np.ones((10, 10)) * 3
b = np.zeros((10, 10))
with pytest.raises(ValueError):
compare_images(a, b, method="unknown")


def test_compare_images_diff():
img1 = np.zeros((10, 10), dtype=np.uint8)
img1[3:8, 3:8] = 255
Expand All @@ -21,7 +27,40 @@ def test_compare_images_diff():
expected_result = np.zeros_like(img1, dtype=np.float64)
expected_result[3:8, 0:3] = 1
result = compare_images(img1, img2, method='diff')
assert_array_equal(result, expected_result)
np.testing.assert_array_equal(result, expected_result)


def test_compare_images_replaced_param():
img1 = np.zeros((10, 10), dtype=np.uint8)
img1[3:8, 3:8] = 255
img2 = np.zeros_like(img1)
img2[3:8, 0:8] = 255
expected_result = np.zeros_like(img1, dtype=np.float64)
expected_result[3:8, 0:3] = 1

regex = ".*Please use `image0, image1`.*"
with pytest.warns(FutureWarning, match=regex) as record:
result = compare_images(image1=img1, image2=img2)
assert_stacklevel(record)
np.testing.assert_array_equal(result, expected_result)

with pytest.warns(FutureWarning, match=regex) as record:
result = compare_images(image0=img1, image2=img2)
assert_stacklevel(record)
np.testing.assert_array_equal(result, expected_result)

with pytest.warns(FutureWarning, match=regex) as record:
result = compare_images(img1, image2=img2)
assert_stacklevel(record)
np.testing.assert_array_equal(result, expected_result)

# Test making "method" keyword-only here as well
# so whole test can be removed in one go
regex = ".*Please pass `method=`.*"
with pytest.warns(FutureWarning, match=regex) as record:
result = compare_images(img1, img2, "diff")
assert_stacklevel(record)
np.testing.assert_array_equal(result, expected_result)


def test_compare_images_blend():
Expand All @@ -33,7 +72,7 @@ def test_compare_images_blend():
expected_result[3:8, 3:8] = 1
expected_result[3:8, 0:3] = 0.5
result = compare_images(img1, img2, method='blend')
assert_array_equal(result, expected_result)
np.testing.assert_array_equal(result, expected_result)


def test_compare_images_checkerboard_default():
Expand All @@ -45,9 +84,9 @@ def test_compare_images_checkerboard_default():
exp_row2 = np.array([1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0.])
# fmt: on
for i in (0, 1, 4, 5, 8, 9, 12, 13):
assert_array_equal(res[i, :], exp_row1)
np.testing.assert_array_equal(res[i, :], exp_row1)
for i in (2, 3, 6, 7, 10, 11, 14, 15):
assert_array_equal(res[i, :], exp_row2)
np.testing.assert_array_equal(res[i, :], exp_row2)


def test_compare_images_checkerboard_tuple():
Expand All @@ -61,6 +100,6 @@ def test_compare_images_checkerboard_tuple():
[1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0]
)
for i in (0, 1, 2, 3, 8, 9, 10, 11):
assert_array_equal(res[i, :], exp_row1)
np.testing.assert_array_equal(res[i, :], exp_row1)
for i in (4, 5, 6, 7, 12, 13, 14, 15):
assert_array_equal(res[i, :], exp_row2)
np.testing.assert_array_equal(res[i, :], exp_row2)

0 comments on commit 765e568

Please sign in to comment.