    def structural_similarity(im1, im2,
                              *,
                              win_size=None, gradient=False, data_range=None,
                              channel_axis=None,
                              gaussian_weights=False, full=False, **kwargs):
        """
        Compute the mean structural similarity index between two images.
        Please pay attention to the `data_range` parameter with floating-point images.
    
        Parameters
        ----------
        im1, im2 : ndarray
            Images. Any dimensionality with same shape.
        win_size : int or None, optional
            The side-length of the sliding window used in comparison. Must be an
            odd value. If `gaussian_weights` is True, this is ignored and the
            window size will depend on `sigma`.
        gradient : bool, optional
            If True, also return the gradient with respect to im2.
        data_range : float, optional
            The data range of the input image (distance between minimum and
            maximum possible values). By default, this is estimated from the image
            data type. This estimate may be wrong for floating-point image data.
            Therefore it is recommended to always pass this value explicitly
            (see note below).
        channel_axis : int or None, optional
            If None, the image is assumed to be a grayscale (single channel) image.
            Otherwise, this parameter indicates which axis of the array corresponds
            to channels.
    
            .. versionadded:: 0.19
               ``channel_axis`` was added in 0.19.
        gaussian_weights : bool, optional
            If True, each patch has its mean and variance spatially weighted by a
            normalized Gaussian kernel of width sigma=1.5.
        full : bool, optional
            If True, also return the full structural similarity image.
    
        Other Parameters
        ----------------
        use_sample_covariance : bool
            If True, normalize covariances by N-1 rather than, N where N is the
            number of pixels within the sliding window.
        K1 : float
            Algorithm parameter, K1 (small constant, see [1]_).
        K2 : float
            Algorithm parameter, K2 (small constant, see [1]_).
        sigma : float
            Standard deviation for the Gaussian when `gaussian_weights` is True.
    
        Returns
        -------
        mssim : float
            The mean structural similarity index over the image.
        grad : ndarray
            The gradient of the structural similarity between im1 and im2 [2]_.
            This is only returned if `gradient` is set to True.
        S : ndarray
            The full SSIM image.  This is only returned if `full` is set to True.
    
        Notes
        -----
        If `data_range` is not specified, the range is automatically guessed
        based on the image data type. However for floating-point image data, this
        estimate yields a result double the value of the desired range, as the
        `dtype_range` in `skimage.util.dtype.py` has defined intervals from -1 to
        +1. This yields an estimate of 2, instead of 1, which is most often
        required when working with image data (as negative light intentsities are
        nonsensical). In case of working with YCbCr-like color data, note that
        these ranges are different per channel (Cb and Cr have double the range
        of Y), so one cannot calculate a channel-averaged SSIM with a single call
        to this function, as identical ranges are assumed for each channel.
    
        To match the implementation of Wang et al. [1]_, set `gaussian_weights`
        to True, `sigma` to 1.5, `use_sample_covariance` to False, and
        specify the `data_range` argument.
    
        .. versionchanged:: 0.16
            This function was renamed from ``skimage.measure.compare_ssim`` to
            ``skimage.metrics.structural_similarity``.
    
        References
        ----------
        .. [1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P.
           (2004). Image quality assessment: From error visibility to
           structural similarity. IEEE Transactions on Image Processing,
           13, 600-612.
           https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf,
           :DOI:`10.1109/TIP.2003.819861`
    
        .. [2] Avanaki, A. N. (2009). Exact global histogram specification
           optimized for structural similarity. Optical Review, 16, 613-621.
           :arxiv:`0901.0065`
           :DOI:`10.1007/s10043-009-0119-z`
    
        """
        check_shape_equality(im1, im2)
        float_type = _supported_float_type(im1.dtype)
    
        if channel_axis is not None:
            # loop over channels
            args = dict(win_size=win_size,
                        gradient=gradient,
                        data_range=data_range,
                        channel_axis=None,
                        gaussian_weights=gaussian_weights,
                        full=full)
            args.update(kwargs)
            nch = im1.shape[channel_axis]
            mssim = np.empty(nch, dtype=float_type)
    
            if gradient:
                G = np.empty(im1.shape, dtype=float_type)
            if full:
                S = np.empty(im1.shape, dtype=float_type)
            channel_axis = channel_axis % im1.ndim
            _at = functools.partial(utils.slice_at_axis, axis=channel_axis)
            for ch in range(nch):
                ch_result = structural_similarity(im1[_at(ch)],
                                                  im2[_at(ch)], **args)
                if gradient and full:
                    mssim[ch], G[_at(ch)], S[_at(ch)] = ch_result
                elif gradient:
                    mssim[ch], G[_at(ch)] = ch_result
                elif full:
                    mssim[ch], S[_at(ch)] = ch_result
                else:
                    mssim[ch] = ch_result
            mssim = mssim.mean()
            if gradient and full:
                return mssim, G, S
            elif gradient:
                return mssim, G
            elif full:
                return mssim, S
            else:
                return mssim
    
        K1 = kwargs.pop('K1', 0.01)
        K2 = kwargs.pop('K2', 0.03)
        sigma = kwargs.pop('sigma', 1.5)
        if K1 < 0:
            raise ValueError("K1 must be positive")
        if K2 < 0:
            raise ValueError("K2 must be positive")
        if sigma < 0:
            raise ValueError("sigma must be positive")
        use_sample_covariance = kwargs.pop('use_sample_covariance', True)
    
        if gaussian_weights:
            # Set to give an 11-tap filter with the default sigma of 1.5 to match
            # Wang et. al. 2004.
            truncate = 3.5
    
        if win_size is None:
            if gaussian_weights:
                # set win_size used by crop to match the filter size
                r = int(truncate * sigma + 0.5)  # radius as in ndimage
                win_size = 2 * r + 1
            else:
                win_size = 7   # backwards compatibility
    
        if np.any((np.asarray(im1.shape) - win_size) < 0):
>           raise ValueError(
                '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.')
