Repository: scikit-image/scikit-image

Issue:
Visual image comparison 
## Description

Various techniques exist to help the eye to compare images. I haven't done a full bibliography on the topic, but we can list checkerboards, differences, fading...

See https://fr.mathworks.com/help/images/ref/imshowpair.html
 or play with https://www.diffchecker.com/image-diff

This can be used by users but also in our gallery to show edge detection, geometrical transformations...

Discussions are open on methods, API, etc.



Discussion:
Cool, I like this idea. It should go wherever we put matplotlib-related stuff... =)
I'll give this a shot.
@lagru I started to write a functions doing something similar to the matlab feature. Have you made some progress? I can open a PR to discuss on a concrete code. There are room for debates on the api.
@sciunto I've played around with it a little bit but got nothing worth a PR. Please go ahead and feel free to tag me!
Ok, it's not very advanced on my side too, and your comments will be welcome.

Resolving patch:
diff --git a/doc/examples/applications/plot_image_comparison.py b/doc/examples/applications/plot_image_comparison.py
new file mode 100644
--- /dev/null
+++ b/doc/examples/applications/plot_image_comparison.py
@@ -0,0 +1,100 @@
+"""
+=======================
+Visual image comparison
+=======================
+
+Image comparison is particularly useful when performing image processing tasks
+such as exposure manipulations, filtering, and restauration.
+
+This example shows how to easily compare two images with various approaches.
+
+"""
+import matplotlib.pyplot as plt
+from matplotlib.gridspec import GridSpec
+
+from skimage import data, transform, exposure
+from skimage.util import compare_images
+
+
+img1 = data.coins()
+img1_equalized = exposure.equalize_hist(img1)
+img2 = transform.rotate(img1, 5)
+
+
+comp_equalized = compare_images(img1, img1_equalized, method='checkerboard')
+diff_rotated = compare_images(img1, img2, method='diff')
+blend_rotated = compare_images(img1, img2, method='blend')
+
+
+######################################################################
+# Checkerboard
+# ============
+#
+# The `checkerboard` method alternates tiles from the first and the second
+# images.
+
+fig = plt.figure(figsize=(8, 9))
+
+gs = GridSpec(3, 2)
+ax0 = fig.add_subplot(gs[0, 0])
+ax1 = fig.add_subplot(gs[0, 1])
+ax2 = fig.add_subplot(gs[1:, :])
+
+ax0.imshow(img1, cmap='gray')
+ax0.set_title('Original')
+ax1.imshow(img1_equalized, cmap='gray')
+ax1.set_title('Equalized')
+ax2.imshow(comp_equalized, cmap='gray')
+ax2.set_title('Checkerboard comparison')
+for a in (ax0, ax1, ax2):
+    a.axis('off')
+plt.tight_layout()
+plt.plot()
+
+######################################################################
+# Diff
+# ====
+#
+# The `diff` method computes the absolute difference between the two images.
+
+fig = plt.figure(figsize=(8, 9))
+
+gs = GridSpec(3, 2)
+ax0 = fig.add_subplot(gs[0, 0])
+ax1 = fig.add_subplot(gs[0, 1])
+ax2 = fig.add_subplot(gs[1:, :])
+
+ax0.imshow(img1, cmap='gray')
+ax0.set_title('Original')
+ax1.imshow(img1_equalized, cmap='gray')
+ax1.set_title('Rotated')
+ax2.imshow(diff_rotated, cmap='gray')
+ax2.set_title('Diff comparison')
+for a in (ax0, ax1, ax2):
+    a.axis('off')
+plt.tight_layout()
+plt.plot()
+
+######################################################################
+# Blend
+# =====
+#
+# `blend` is the result of the average of the two images.
+
+fig = plt.figure(figsize=(8, 9))
+
+gs = GridSpec(3, 2)
+ax0 = fig.add_subplot(gs[0, 0])
+ax1 = fig.add_subplot(gs[0, 1])
+ax2 = fig.add_subplot(gs[1:, :])
+
+ax0.imshow(img1, cmap='gray')
+ax0.set_title('Original')
+ax1.imshow(img1_equalized, cmap='gray')
+ax1.set_title('Rotated')
+ax2.imshow(blend_rotated, cmap='gray')
+ax2.set_title('Blend comparison')
+for a in (ax0, ax1, ax2):
+    a.axis('off')
+plt.tight_layout()
+plt.plot()
diff --git a/skimage/util/__init__.py b/skimage/util/__init__.py
--- a/skimage/util/__init__.py
+++ b/skimage/util/__init__.py
@@ -6,6 +6,7 @@
 from .apply_parallel import apply_parallel
 
 from .arraycrop import crop
+from .compare import compare_images
 from ._regular_grid import regular_grid, regular_seeds
 from .unique import unique_rows
 from ._invert import invert
@@ -29,6 +30,7 @@
            'view_as_windows',
            'pad',
            'crop',
+           'compare_images',
            'montage',
            'random_noise',
            'regular_grid',
diff --git a/skimage/util/compare.py b/skimage/util/compare.py
new file mode 100644
--- /dev/null
+++ b/skimage/util/compare.py
@@ -0,0 +1,60 @@
+import numpy as np
+from ..util import img_as_float
+from itertools import product
+
+
+def compare_images(image1, image2, method='diff', *, n_tiles=(8, 8)):
+    """
+    Return an image showing the differences between two images.
+
+    .. versionadded:: 0.16
+
+    Parameters
+    ----------
+    image1, image2 : 2-D array
+        Images to process, must be of the same shape.
+    method : string, optional
+        Method used for the comparison.
+        Valid values are {'diff', 'blend', 'checkerboard'}.
+        Details are provided in the note section.
+    n_tiles : tuple, optional
+        Used only for the `checkerboard` method. Specifies the number
+        of tiles (row, column) to divide the image.
+
+    Returns
+    -------
+    comparison : 2-D array
+        Image showing the differences.
+
+    Notes
+    -----
+    `diff` computes the absolute difference between the two images.
+    `bend` computes the mean value.
+    `checkerboard` makes tiles of dimension `n_tiles` that display
+    alternatively the first and the second image.
+    """
+    if image1.shape != image2.shape:
+        raise ValueError('Images must have the same shape.')
+
+    img1 = img_as_float(image1)
+    img2 = img_as_float(image2)
+
+    if method == 'diff':
+        comparison = np.abs(img2 - img1)
+    elif method == 'blend':
+        comparison = 0.5 * (img2 + img1)
+    elif method == 'checkerboard':
+        shapex, shapey = img1.shape
+        mask = np.full((shapex, shapey), False)
+        stepx = int(shapex / n_tiles[0])
+        stepy = int(shapey / n_tiles[1])
+        for i, j in product(range(n_tiles[0]), range(n_tiles[1])):
+            if (i + j) % 2 == 0:
+                mask[i * stepx:(i + 1)*stepx, j * stepy:(j + 1) * stepy] = True
+        comparison = np.zeros_like(img1)
+        comparison[mask] = img1[mask]
+        comparison[~mask] = img2[~mask]
+    else:
+        raise ValueError('Wrong value for `method`. '
+                         'Must be either "diff", "blend" or "checkerboard".')
+    return comparison


Test patch:
diff --git a/skimage/util/tests/test_compare.py b/skimage/util/tests/test_compare.py
new file mode 100644
--- /dev/null
+++ b/skimage/util/tests/test_compare.py
@@ -0,0 +1,64 @@
+import numpy as np
+
+from skimage._shared.testing import assert_array_equal
+from skimage._shared import testing
+
+from skimage.util.compare import compare_images
+
+
+def test_compate_images_ValueError_shape():
+    img1 = np.zeros((10, 10), dtype=np.uint8)
+    img2 = np.zeros((10, 1), dtype=np.uint8)
+    with testing.raises(ValueError):
+        compare_images(img1, img2)
+
+
+def test_compare_images_diff():
+    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
+    result = compare_images(img1, img2, method='diff')
+    assert_array_equal(result, expected_result)
+
+
+def test_compare_images_blend():
+    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, 3:8] = 1
+    expected_result[3:8, 0:3] = 0.5
+    result = compare_images(img1, img2, method='blend')
+    assert_array_equal(result, expected_result)
+
+
+def test_compare_images_checkerboard_default():
+    img1 = np.zeros((2**4, 2**4), dtype=np.uint8)
+    img2 = np.full(img1.shape, fill_value=255, dtype=np.uint8)
+    res = compare_images(img1, img2, method='checkerboard')
+    exp_row1 = np.array([0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1.])
+    exp_row2 = np.array([1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0.])
+    for i in (0, 1, 4, 5, 8, 9, 12, 13):
+        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)
+
+
+def test_compare_images_checkerboard_tuple():
+    img1 = np.zeros((2**4, 2**4), dtype=np.uint8)
+    img2 = np.full(img1.shape, fill_value=255, dtype=np.uint8)
+    res = compare_images(img1, img2, method='checkerboard', n_tiles=(4, 8))
+    exp_row1 = np.array(
+        [0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1.]
+    )
+    exp_row2 = np.array(
+        [1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0.]
+    )
+    for i in (0, 1, 2, 3, 8, 9, 10, 11):
+        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)