We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
metric.structural_similarity throws the wrong error for small sizes
## Description

when using `structural_similarity` when the images are small (<7 in any dimension) a `ValueError` gets thrown, but with the wrong text:

```
ValueError: win_size exceeds image extent.  If the input is a multichannel (color) image, set multichannel=True.
```

Instead of misleading the user into thinking there's something wrong with the way they read in, or converted, their image data, this error should read:

```
ValueError: image data too small to perform SSIM analysis on, please ensure that your images are at least 7x7.
```

## Way to reproduce
```python
import cv2
from skimage.metrics import structural_similarity

def gray(img):
	return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

image = cv2.imread('test.jpg', cv2.IMREAD_COLOR)

# a 7x7 crop will work just fine
crop1 = image[0:7, 0:7]
crop2 = image[8:15, 8:15]
structural_similarity(gray(crop1), gray(crop2))

# a 6x6 crop will cause a ValueError, with the wrong message
crop1 = image[0:6, 0:6]
crop2 = image[8:14, 8:4]
structural_similarity(gray(crop1), gray(crop2))
```


## Version information
```python
# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
```

```python
3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)]
Windows-10-10.0.19041-SP0
scikit-image version: 0.18.1
numpy version: 1.20.2
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `skimage/metrics/_structural_similarity.py`
Content:
```
1 import functools
2 
3 import numpy as np
4 from scipy.ndimage import uniform_filter, gaussian_filter
5 
6 from .._shared import utils
7 from .._shared.utils import _supported_float_type, check_shape_equality, warn
8 from ..util.dtype import dtype_range
9 from ..util.arraycrop import crop
10 
11 
12 __all__ = ['structural_similarity']
13 
14 
15 @utils.deprecate_multichannel_kwarg()
16 def structural_similarity(im1, im2,
17                           *,
18                           win_size=None, gradient=False, data_range=None,
19                           channel_axis=None, multichannel=False,
20                           gaussian_weights=False, full=False, **kwargs):
21     """
22     Compute the mean structural similarity index between two images.
23 
24     Parameters
25     ----------
26     im1, im2 : ndarray
27         Images. Any dimensionality with same shape.
28     win_size : int or None, optional
29         The side-length of the sliding window used in comparison. Must be an
30         odd value. If `gaussian_weights` is True, this is ignored and the
31         window size will depend on `sigma`.
32     gradient : bool, optional
33         If True, also return the gradient with respect to im2.
34     data_range : float, optional
35         The data range of the input image (distance between minimum and
36         maximum possible values). By default, this is estimated from the image
37         data-type.
38     channel_axis : int or None, optional
39         If None, the image is assumed to be a grayscale (single channel) image.
40         Otherwise, this parameter indicates which axis of the array corresponds
41         to channels.
42 
43         .. versionadded:: 0.19
44            ``channel_axis`` was added in 0.19.
45     multichannel : bool, optional
46         If True, treat the last dimension of the array as channels. Similarity
47         calculations are done independently for each channel then averaged.
48         This argument is deprecated: specify `channel_axis` instead.
49     gaussian_weights : bool, optional
50         If True, each patch has its mean and variance spatially weighted by a
51         normalized Gaussian kernel of width sigma=1.5.
52     full : bool, optional
53         If True, also return the full structural similarity image.
54 
55     Other Parameters
56     ----------------
57     use_sample_covariance : bool
58         If True, normalize covariances by N-1 rather than, N where N is the
59         number of pixels within the sliding window.
60     K1 : float
61         Algorithm parameter, K1 (small constant, see [1]_).
62     K2 : float
63         Algorithm parameter, K2 (small constant, see [1]_).
64     sigma : float
65         Standard deviation for the Gaussian when `gaussian_weights` is True.
66 
67     Returns
68     -------
69     mssim : float
70         The mean structural similarity index over the image.
71     grad : ndarray
72         The gradient of the structural similarity between im1 and im2 [2]_.
73         This is only returned if `gradient` is set to True.
74     S : ndarray
75         The full SSIM image.  This is only returned if `full` is set to True.
76 
77     Notes
78     -----
79     To match the implementation of Wang et. al. [1]_, set `gaussian_weights`
80     to True, `sigma` to 1.5, and `use_sample_covariance` to False.
81 
82     .. versionchanged:: 0.16
83         This function was renamed from ``skimage.measure.compare_ssim`` to
84         ``skimage.metrics.structural_similarity``.
85 
86     References
87     ----------
88     .. [1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P.
89        (2004). Image quality assessment: From error visibility to
90        structural similarity. IEEE Transactions on Image Processing,
91        13, 600-612.
92        https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf,
93        :DOI:`10.1109/TIP.2003.819861`
94 
95     .. [2] Avanaki, A. N. (2009). Exact global histogram specification
96        optimized for structural similarity. Optical Review, 16, 613-621.
97        :arxiv:`0901.0065`
98        :DOI:`10.1007/s10043-009-0119-z`
99 
100     """
101     check_shape_equality(im1, im2)
102     float_type = _supported_float_type(im1.dtype)
103 
104     if channel_axis is not None:
105         # loop over channels
106         args = dict(win_size=win_size,
107                     gradient=gradient,
108                     data_range=data_range,
109                     channel_axis=None,
110                     gaussian_weights=gaussian_weights,
111                     full=full)
112         args.update(kwargs)
113         nch = im1.shape[channel_axis]
114         mssim = np.empty(nch, dtype=float_type)
115 
116         if gradient:
117             G = np.empty(im1.shape, dtype=float_type)
118         if full:
119             S = np.empty(im1.shape, dtype=float_type)
120         channel_axis = channel_axis % im1.ndim
121         _at = functools.partial(utils.slice_at_axis, axis=channel_axis)
122         for ch in range(nch):
123             ch_result = structural_similarity(im1[_at(ch)],
124                                               im2[_at(ch)], **args)
125             if gradient and full:
126                 mssim[ch], G[_at(ch)], S[_at(ch)] = ch_result
127             elif gradient:
128                 mssim[ch], G[_at(ch)] = ch_result
129             elif full:
130                 mssim[ch], S[_at(ch)] = ch_result
131             else:
132                 mssim[ch] = ch_result
133         mssim = mssim.mean()
134         if gradient and full:
135             return mssim, G, S
136         elif gradient:
137             return mssim, G
138         elif full:
139             return mssim, S
140         else:
141             return mssim
142 
143     K1 = kwargs.pop('K1', 0.01)
144     K2 = kwargs.pop('K2', 0.03)
145     sigma = kwargs.pop('sigma', 1.5)
146     if K1 < 0:
147         raise ValueError("K1 must be positive")
148     if K2 < 0:
149         raise ValueError("K2 must be positive")
150     if sigma < 0:
151         raise ValueError("sigma must be positive")
152     use_sample_covariance = kwargs.pop('use_sample_covariance', True)
153 
154     if gaussian_weights:
155         # Set to give an 11-tap filter with the default sigma of 1.5 to match
156         # Wang et. al. 2004.
157         truncate = 3.5
158 
159     if win_size is None:
160         if gaussian_weights:
161             # set win_size used by crop to match the filter size
162             r = int(truncate * sigma + 0.5)  # radius as in ndimage
163             win_size = 2 * r + 1
164         else:
165             win_size = 7   # backwards compatibility
166 
167     if np.any((np.asarray(im1.shape) - win_size) < 0):
168         raise ValueError(
169             "win_size exceeds image extent.  If the input is a multichannel "
170             "(color) image, set channel_axis to the axis number corresponding "
171             "to the channels.")
172 
173     if not (win_size % 2 == 1):
174         raise ValueError('Window size must be odd.')
175 
176     if data_range is None:
177         if im1.dtype != im2.dtype:
178             warn("Inputs have mismatched dtype.  Setting data_range based on "
179                  "im1.dtype.", stacklevel=2)
180         dmin, dmax = dtype_range[im1.dtype.type]
181         data_range = dmax - dmin
182 
183     ndim = im1.ndim
184 
185     if gaussian_weights:
186         filter_func = gaussian_filter
187         filter_args = {'sigma': sigma, 'truncate': truncate}
188     else:
189         filter_func = uniform_filter
190         filter_args = {'size': win_size}
191 
192     # ndimage filters need floating point data
193     im1 = im1.astype(float_type, copy=False)
194     im2 = im2.astype(float_type, copy=False)
195 
196     NP = win_size ** ndim
197 
198     # filter has already normalized by NP
199     if use_sample_covariance:
200         cov_norm = NP / (NP - 1)  # sample covariance
201     else:
202         cov_norm = 1.0  # population covariance to match Wang et. al. 2004
203 
204     # compute (weighted) means
205     ux = filter_func(im1, **filter_args)
206     uy = filter_func(im2, **filter_args)
207 
208     # compute (weighted) variances and covariances
209     uxx = filter_func(im1 * im1, **filter_args)
210     uyy = filter_func(im2 * im2, **filter_args)
211     uxy = filter_func(im1 * im2, **filter_args)
212     vx = cov_norm * (uxx - ux * ux)
213     vy = cov_norm * (uyy - uy * uy)
214     vxy = cov_norm * (uxy - ux * uy)
215 
216     R = data_range
217     C1 = (K1 * R) ** 2
218     C2 = (K2 * R) ** 2
219 
220     A1, A2, B1, B2 = ((2 * ux * uy + C1,
221                        2 * vxy + C2,
222                        ux ** 2 + uy ** 2 + C1,
223                        vx + vy + C2))
224     D = B1 * B2
225     S = (A1 * A2) / D
226 
227     # to avoid edge effects will ignore filter radius strip around edges
228     pad = (win_size - 1) // 2
229 
230     # compute (weighted) mean of ssim. Use float64 for accuracy.
231     mssim = crop(S, pad).mean(dtype=np.float64)
232 
233     if gradient:
234         # The following is Eqs. 7-8 of Avanaki 2009.
235         grad = filter_func(A1 / D, **filter_args) * im1
236         grad += filter_func(-S / B2, **filter_args) * im2
237         grad += filter_func((ux * (A2 - A1) - uy * (B2 - B1) * S) / D,
238                             **filter_args)
239         grad *= (2 / im1.size)
240 
241         if full:
242             return mssim, grad, S
243         else:
244             return mssim, grad
245     else:
246         if full:
247             return mssim, S
248         else:
249             return mssim
250
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/skimage/metrics/_structural_similarity.py b/skimage/metrics/_structural_similarity.py
--- a/skimage/metrics/_structural_similarity.py
+++ b/skimage/metrics/_structural_similarity.py
@@ -166,9 +166,14 @@
 
     if np.any((np.asarray(im1.shape) - win_size) < 0):
         raise ValueError(
-            "win_size exceeds image extent.  If the input is a multichannel "
-            "(color) image, set channel_axis to the axis number corresponding "
-            "to the channels.")
+            "win_size exceeds image extent. ",
+            "Either ensure that your images are ",
+            "at least 7x7; or pass win_size explicitly ",
+            "in the function call, with an odd value ",
+            "less than or equal to the smaller side of your ",
+            "images. If your images are multichannel ",
+            "(with color channels), set channel_axis to ",
+            "the axis number corresponding to the channels.")
 
     if not (win_size % 2 == 1):
         raise ValueError('Window size must be odd.')

{"golden_diff": "diff --git a/skimage/metrics/_structural_similarity.py b/skimage/metrics/_structural_similarity.py\n--- a/skimage/metrics/_structural_similarity.py\n+++ b/skimage/metrics/_structural_similarity.py\n@@ -166,9 +166,14 @@\n \n     if np.any((np.asarray(im1.shape) - win_size) < 0):\n         raise ValueError(\n-            \"win_size exceeds image extent.  If the input is a multichannel \"\n-            \"(color) image, set channel_axis to the axis number corresponding \"\n-            \"to the channels.\")\n+            \"win_size exceeds image extent. \",\n+            \"Either ensure that your images are \",\n+            \"at least 7x7; or pass win_size explicitly \",\n+            \"in the function call, with an odd value \",\n+            \"less than or equal to the smaller side of your \",\n+            \"images. If your images are multichannel \",\n+            \"(with color channels), set channel_axis to \",\n+            \"the axis number corresponding to the channels.\")\n \n     if not (win_size % 2 == 1):\n         raise ValueError('Window size must be odd.')\n", "issue": "metric.structural_similarity throws the wrong error for small sizes\n## Description\r\n\r\nwhen using `structural_similarity` when the images are small (<7 in any dimension) a `ValueError` gets thrown, but with the wrong text:\r\n\r\n```\r\nValueError: win_size exceeds image extent.  If the input is a multichannel (color) image, set multichannel=True.\r\n```\r\n\r\nInstead of misleading the user into thinking there's something wrong with the way they read in, or converted, their image data, this error should read:\r\n\r\n```\r\nValueError: image data too small to perform SSIM analysis on, please ensure that your images are at least 7x7.\r\n```\r\n\r\n## Way to reproduce\r\n```python\r\nimport cv2\r\nfrom skimage.metrics import structural_similarity\r\n\r\ndef gray(img):\r\n\treturn cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\r\n\r\nimage = cv2.imread('test.jpg', cv2.IMREAD_COLOR)\r\n\r\n# a 7x7 crop will work just fine\r\ncrop1 = image[0:7, 0:7]\r\ncrop2 = image[8:15, 8:15]\r\nstructural_similarity(gray(crop1), gray(crop2))\r\n\r\n# a 6x6 crop will cause a ValueError, with the wrong message\r\ncrop1 = image[0:6, 0:6]\r\ncrop2 = image[8:14, 8:4]\r\nstructural_similarity(gray(crop1), gray(crop2))\r\n```\r\n\r\n\r\n## Version information\r\n```python\r\n# Paste the output of the following python commands\r\nfrom __future__ import print_function\r\nimport sys; print(sys.version)\r\nimport platform; print(platform.platform())\r\nimport skimage; print(\"scikit-image version: {}\".format(skimage.__version__))\r\nimport numpy; print(\"numpy version: {}\".format(numpy.__version__))\r\n```\r\n\r\n```python\r\n3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)]\r\nWindows-10-10.0.19041-SP0\r\nscikit-image version: 0.18.1\r\nnumpy version: 1.20.2\r\n```\r\n\n", "before_files": [{"content": "import functools\n\nimport numpy as np\nfrom scipy.ndimage import uniform_filter, gaussian_filter\n\nfrom .._shared import utils\nfrom .._shared.utils import _supported_float_type, check_shape_equality, warn\nfrom ..util.dtype import dtype_range\nfrom ..util.arraycrop import crop\n\n\n__all__ = ['structural_similarity']\n\n\n@utils.deprecate_multichannel_kwarg()\ndef structural_similarity(im1, im2,\n                          *,\n                          win_size=None, gradient=False, data_range=None,\n                          channel_axis=None, multichannel=False,\n                          gaussian_weights=False, full=False, **kwargs):\n    \"\"\"\n    Compute the mean structural similarity index between two images.\n\n    Parameters\n    ----------\n    im1, im2 : ndarray\n        Images. Any dimensionality with same shape.\n    win_size : int or None, optional\n        The side-length of the sliding window used in comparison. Must be an\n        odd value. If `gaussian_weights` is True, this is ignored and the\n        window size will depend on `sigma`.\n    gradient : bool, optional\n        If True, also return the gradient with respect to im2.\n    data_range : float, optional\n        The data range of the input image (distance between minimum and\n        maximum possible values). By default, this is estimated from the image\n        data-type.\n    channel_axis : int or None, optional\n        If None, the image is assumed to be a grayscale (single channel) image.\n        Otherwise, this parameter indicates which axis of the array corresponds\n        to channels.\n\n        .. versionadded:: 0.19\n           ``channel_axis`` was added in 0.19.\n    multichannel : bool, optional\n        If True, treat the last dimension of the array as channels. Similarity\n        calculations are done independently for each channel then averaged.\n        This argument is deprecated: specify `channel_axis` instead.\n    gaussian_weights : bool, optional\n        If True, each patch has its mean and variance spatially weighted by a\n        normalized Gaussian kernel of width sigma=1.5.\n    full : bool, optional\n        If True, also return the full structural similarity image.\n\n    Other Parameters\n    ----------------\n    use_sample_covariance : bool\n        If True, normalize covariances by N-1 rather than, N where N is the\n        number of pixels within the sliding window.\n    K1 : float\n        Algorithm parameter, K1 (small constant, see [1]_).\n    K2 : float\n        Algorithm parameter, K2 (small constant, see [1]_).\n    sigma : float\n        Standard deviation for the Gaussian when `gaussian_weights` is True.\n\n    Returns\n    -------\n    mssim : float\n        The mean structural similarity index over the image.\n    grad : ndarray\n        The gradient of the structural similarity between im1 and im2 [2]_.\n        This is only returned if `gradient` is set to True.\n    S : ndarray\n        The full SSIM image.  This is only returned if `full` is set to True.\n\n    Notes\n    -----\n    To match the implementation of Wang et. al. [1]_, set `gaussian_weights`\n    to True, `sigma` to 1.5, and `use_sample_covariance` to False.\n\n    .. versionchanged:: 0.16\n        This function was renamed from ``skimage.measure.compare_ssim`` to\n        ``skimage.metrics.structural_similarity``.\n\n    References\n    ----------\n    .. [1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P.\n       (2004). Image quality assessment: From error visibility to\n       structural similarity. IEEE Transactions on Image Processing,\n       13, 600-612.\n       https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf,\n       :DOI:`10.1109/TIP.2003.819861`\n\n    .. [2] Avanaki, A. N. (2009). Exact global histogram specification\n       optimized for structural similarity. Optical Review, 16, 613-621.\n       :arxiv:`0901.0065`\n       :DOI:`10.1007/s10043-009-0119-z`\n\n    \"\"\"\n    check_shape_equality(im1, im2)\n    float_type = _supported_float_type(im1.dtype)\n\n    if channel_axis is not None:\n        # loop over channels\n        args = dict(win_size=win_size,\n                    gradient=gradient,\n                    data_range=data_range,\n                    channel_axis=None,\n                    gaussian_weights=gaussian_weights,\n                    full=full)\n        args.update(kwargs)\n        nch = im1.shape[channel_axis]\n        mssim = np.empty(nch, dtype=float_type)\n\n        if gradient:\n            G = np.empty(im1.shape, dtype=float_type)\n        if full:\n            S = np.empty(im1.shape, dtype=float_type)\n        channel_axis = channel_axis % im1.ndim\n        _at = functools.partial(utils.slice_at_axis, axis=channel_axis)\n        for ch in range(nch):\n            ch_result = structural_similarity(im1[_at(ch)],\n                                              im2[_at(ch)], **args)\n            if gradient and full:\n                mssim[ch], G[_at(ch)], S[_at(ch)] = ch_result\n            elif gradient:\n                mssim[ch], G[_at(ch)] = ch_result\n            elif full:\n                mssim[ch], S[_at(ch)] = ch_result\n            else:\n                mssim[ch] = ch_result\n        mssim = mssim.mean()\n        if gradient and full:\n            return mssim, G, S\n        elif gradient:\n            return mssim, G\n        elif full:\n            return mssim, S\n        else:\n            return mssim\n\n    K1 = kwargs.pop('K1', 0.01)\n    K2 = kwargs.pop('K2', 0.03)\n    sigma = kwargs.pop('sigma', 1.5)\n    if K1 < 0:\n        raise ValueError(\"K1 must be positive\")\n    if K2 < 0:\n        raise ValueError(\"K2 must be positive\")\n    if sigma < 0:\n        raise ValueError(\"sigma must be positive\")\n    use_sample_covariance = kwargs.pop('use_sample_covariance', True)\n\n    if gaussian_weights:\n        # Set to give an 11-tap filter with the default sigma of 1.5 to match\n        # Wang et. al. 2004.\n        truncate = 3.5\n\n    if win_size is None:\n        if gaussian_weights:\n            # set win_size used by crop to match the filter size\n            r = int(truncate * sigma + 0.5)  # radius as in ndimage\n            win_size = 2 * r + 1\n        else:\n            win_size = 7   # backwards compatibility\n\n    if np.any((np.asarray(im1.shape) - win_size) < 0):\n        raise ValueError(\n            \"win_size exceeds image extent.  If the input is a multichannel \"\n            \"(color) image, set channel_axis to the axis number corresponding \"\n            \"to the channels.\")\n\n    if not (win_size % 2 == 1):\n        raise ValueError('Window size must be odd.')\n\n    if data_range is None:\n        if im1.dtype != im2.dtype:\n            warn(\"Inputs have mismatched dtype.  Setting data_range based on \"\n                 \"im1.dtype.\", stacklevel=2)\n        dmin, dmax = dtype_range[im1.dtype.type]\n        data_range = dmax - dmin\n\n    ndim = im1.ndim\n\n    if gaussian_weights:\n        filter_func = gaussian_filter\n        filter_args = {'sigma': sigma, 'truncate': truncate}\n    else:\n        filter_func = uniform_filter\n        filter_args = {'size': win_size}\n\n    # ndimage filters need floating point data\n    im1 = im1.astype(float_type, copy=False)\n    im2 = im2.astype(float_type, copy=False)\n\n    NP = win_size ** ndim\n\n    # filter has already normalized by NP\n    if use_sample_covariance:\n        cov_norm = NP / (NP - 1)  # sample covariance\n    else:\n        cov_norm = 1.0  # population covariance to match Wang et. al. 2004\n\n    # compute (weighted) means\n    ux = filter_func(im1, **filter_args)\n    uy = filter_func(im2, **filter_args)\n\n    # compute (weighted) variances and covariances\n    uxx = filter_func(im1 * im1, **filter_args)\n    uyy = filter_func(im2 * im2, **filter_args)\n    uxy = filter_func(im1 * im2, **filter_args)\n    vx = cov_norm * (uxx - ux * ux)\n    vy = cov_norm * (uyy - uy * uy)\n    vxy = cov_norm * (uxy - ux * uy)\n\n    R = data_range\n    C1 = (K1 * R) ** 2\n    C2 = (K2 * R) ** 2\n\n    A1, A2, B1, B2 = ((2 * ux * uy + C1,\n                       2 * vxy + C2,\n                       ux ** 2 + uy ** 2 + C1,\n                       vx + vy + C2))\n    D = B1 * B2\n    S = (A1 * A2) / D\n\n    # to avoid edge effects will ignore filter radius strip around edges\n    pad = (win_size - 1) // 2\n\n    # compute (weighted) mean of ssim. Use float64 for accuracy.\n    mssim = crop(S, pad).mean(dtype=np.float64)\n\n    if gradient:\n        # The following is Eqs. 7-8 of Avanaki 2009.\n        grad = filter_func(A1 / D, **filter_args) * im1\n        grad += filter_func(-S / B2, **filter_args) * im2\n        grad += filter_func((ux * (A2 - A1) - uy * (B2 - B1) * S) / D,\n                            **filter_args)\n        grad *= (2 / im1.size)\n\n        if full:\n            return mssim, grad, S\n        else:\n            return mssim, grad\n    else:\n        if full:\n            return mssim, S\n        else:\n            return mssim\n", "path": "skimage/metrics/_structural_similarity.py"}], "after_files": [{"content": "import functools\n\nimport numpy as np\nfrom scipy.ndimage import uniform_filter, gaussian_filter\n\nfrom .._shared import utils\nfrom .._shared.utils import _supported_float_type, check_shape_equality, warn\nfrom ..util.dtype import dtype_range\nfrom ..util.arraycrop import crop\n\n\n__all__ = ['structural_similarity']\n\n\n@utils.deprecate_multichannel_kwarg()\ndef structural_similarity(im1, im2,\n                          *,\n                          win_size=None, gradient=False, data_range=None,\n                          channel_axis=None, multichannel=False,\n                          gaussian_weights=False, full=False, **kwargs):\n    \"\"\"\n    Compute the mean structural similarity index between two images.\n\n    Parameters\n    ----------\n    im1, im2 : ndarray\n        Images. Any dimensionality with same shape.\n    win_size : int or None, optional\n        The side-length of the sliding window used in comparison. Must be an\n        odd value. If `gaussian_weights` is True, this is ignored and the\n        window size will depend on `sigma`.\n    gradient : bool, optional\n        If True, also return the gradient with respect to im2.\n    data_range : float, optional\n        The data range of the input image (distance between minimum and\n        maximum possible values). By default, this is estimated from the image\n        data-type.\n    channel_axis : int or None, optional\n        If None, the image is assumed to be a grayscale (single channel) image.\n        Otherwise, this parameter indicates which axis of the array corresponds\n        to channels.\n\n        .. versionadded:: 0.19\n           ``channel_axis`` was added in 0.19.\n    multichannel : bool, optional\n        If True, treat the last dimension of the array as channels. Similarity\n        calculations are done independently for each channel then averaged.\n        This argument is deprecated: specify `channel_axis` instead.\n    gaussian_weights : bool, optional\n        If True, each patch has its mean and variance spatially weighted by a\n        normalized Gaussian kernel of width sigma=1.5.\n    full : bool, optional\n        If True, also return the full structural similarity image.\n\n    Other Parameters\n    ----------------\n    use_sample_covariance : bool\n        If True, normalize covariances by N-1 rather than, N where N is the\n        number of pixels within the sliding window.\n    K1 : float\n        Algorithm parameter, K1 (small constant, see [1]_).\n    K2 : float\n        Algorithm parameter, K2 (small constant, see [1]_).\n    sigma : float\n        Standard deviation for the Gaussian when `gaussian_weights` is True.\n\n    Returns\n    -------\n    mssim : float\n        The mean structural similarity index over the image.\n    grad : ndarray\n        The gradient of the structural similarity between im1 and im2 [2]_.\n        This is only returned if `gradient` is set to True.\n    S : ndarray\n        The full SSIM image.  This is only returned if `full` is set to True.\n\n    Notes\n    -----\n    To match the implementation of Wang et. al. [1]_, set `gaussian_weights`\n    to True, `sigma` to 1.5, and `use_sample_covariance` to False.\n\n    .. versionchanged:: 0.16\n        This function was renamed from ``skimage.measure.compare_ssim`` to\n        ``skimage.metrics.structural_similarity``.\n\n    References\n    ----------\n    .. [1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P.\n       (2004). Image quality assessment: From error visibility to\n       structural similarity. IEEE Transactions on Image Processing,\n       13, 600-612.\n       https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf,\n       :DOI:`10.1109/TIP.2003.819861`\n\n    .. [2] Avanaki, A. N. (2009). Exact global histogram specification\n       optimized for structural similarity. Optical Review, 16, 613-621.\n       :arxiv:`0901.0065`\n       :DOI:`10.1007/s10043-009-0119-z`\n\n    \"\"\"\n    check_shape_equality(im1, im2)\n    float_type = _supported_float_type(im1.dtype)\n\n    if channel_axis is not None:\n        # loop over channels\n        args = dict(win_size=win_size,\n                    gradient=gradient,\n                    data_range=data_range,\n                    channel_axis=None,\n                    gaussian_weights=gaussian_weights,\n                    full=full)\n        args.update(kwargs)\n        nch = im1.shape[channel_axis]\n        mssim = np.empty(nch, dtype=float_type)\n\n        if gradient:\n            G = np.empty(im1.shape, dtype=float_type)\n        if full:\n            S = np.empty(im1.shape, dtype=float_type)\n        channel_axis = channel_axis % im1.ndim\n        _at = functools.partial(utils.slice_at_axis, axis=channel_axis)\n        for ch in range(nch):\n            ch_result = structural_similarity(im1[_at(ch)],\n                                              im2[_at(ch)], **args)\n            if gradient and full:\n                mssim[ch], G[_at(ch)], S[_at(ch)] = ch_result\n            elif gradient:\n                mssim[ch], G[_at(ch)] = ch_result\n            elif full:\n                mssim[ch], S[_at(ch)] = ch_result\n            else:\n                mssim[ch] = ch_result\n        mssim = mssim.mean()\n        if gradient and full:\n            return mssim, G, S\n        elif gradient:\n            return mssim, G\n        elif full:\n            return mssim, S\n        else:\n            return mssim\n\n    K1 = kwargs.pop('K1', 0.01)\n    K2 = kwargs.pop('K2', 0.03)\n    sigma = kwargs.pop('sigma', 1.5)\n    if K1 < 0:\n        raise ValueError(\"K1 must be positive\")\n    if K2 < 0:\n        raise ValueError(\"K2 must be positive\")\n    if sigma < 0:\n        raise ValueError(\"sigma must be positive\")\n    use_sample_covariance = kwargs.pop('use_sample_covariance', True)\n\n    if gaussian_weights:\n        # Set to give an 11-tap filter with the default sigma of 1.5 to match\n        # Wang et. al. 2004.\n        truncate = 3.5\n\n    if win_size is None:\n        if gaussian_weights:\n            # set win_size used by crop to match the filter size\n            r = int(truncate * sigma + 0.5)  # radius as in ndimage\n            win_size = 2 * r + 1\n        else:\n            win_size = 7   # backwards compatibility\n\n    if np.any((np.asarray(im1.shape) - win_size) < 0):\n        raise ValueError(\n            \"win_size exceeds image extent. \",\n            \"Either ensure that your images are \",\n            \"at least 7x7; or pass win_size explicitly \",\n            \"in the function call, with an odd value \",\n            \"less than or equal to the smaller side of your \",\n            \"images. If your images are multichannel \",\n            \"(with color channels), set channel_axis to \",\n            \"the axis number corresponding to the channels.\")\n\n    if not (win_size % 2 == 1):\n        raise ValueError('Window size must be odd.')\n\n    if data_range is None:\n        if im1.dtype != im2.dtype:\n            warn(\"Inputs have mismatched dtype.  Setting data_range based on \"\n                 \"im1.dtype.\", stacklevel=2)\n        dmin, dmax = dtype_range[im1.dtype.type]\n        data_range = dmax - dmin\n\n    ndim = im1.ndim\n\n    if gaussian_weights:\n        filter_func = gaussian_filter\n        filter_args = {'sigma': sigma, 'truncate': truncate}\n    else:\n        filter_func = uniform_filter\n        filter_args = {'size': win_size}\n\n    # ndimage filters need floating point data\n    im1 = im1.astype(float_type, copy=False)\n    im2 = im2.astype(float_type, copy=False)\n\n    NP = win_size ** ndim\n\n    # filter has already normalized by NP\n    if use_sample_covariance:\n        cov_norm = NP / (NP - 1)  # sample covariance\n    else:\n        cov_norm = 1.0  # population covariance to match Wang et. al. 2004\n\n    # compute (weighted) means\n    ux = filter_func(im1, **filter_args)\n    uy = filter_func(im2, **filter_args)\n\n    # compute (weighted) variances and covariances\n    uxx = filter_func(im1 * im1, **filter_args)\n    uyy = filter_func(im2 * im2, **filter_args)\n    uxy = filter_func(im1 * im2, **filter_args)\n    vx = cov_norm * (uxx - ux * ux)\n    vy = cov_norm * (uyy - uy * uy)\n    vxy = cov_norm * (uxy - ux * uy)\n\n    R = data_range\n    C1 = (K1 * R) ** 2\n    C2 = (K2 * R) ** 2\n\n    A1, A2, B1, B2 = ((2 * ux * uy + C1,\n                       2 * vxy + C2,\n                       ux ** 2 + uy ** 2 + C1,\n                       vx + vy + C2))\n    D = B1 * B2\n    S = (A1 * A2) / D\n\n    # to avoid edge effects will ignore filter radius strip around edges\n    pad = (win_size - 1) // 2\n\n    # compute (weighted) mean of ssim. Use float64 for accuracy.\n    mssim = crop(S, pad).mean(dtype=np.float64)\n\n    if gradient:\n        # The following is Eqs. 7-8 of Avanaki 2009.\n        grad = filter_func(A1 / D, **filter_args) * im1\n        grad += filter_func(-S / B2, **filter_args) * im2\n        grad += filter_func((ux * (A2 - A1) - uy * (B2 - B1) * S) / D,\n                            **filter_args)\n        grad *= (2 / im1.size)\n\n        if full:\n            return mssim, grad, S\n        else:\n            return mssim, grad\n    else:\n        if full:\n            return mssim, S\n        else:\n            return mssim\n", "path": "skimage/metrics/_structural_similarity.py"}]}