from __future__ import division __all__ = ['structural_similarity'] import numpy as np from ..util.dtype import dtype_range from ..util.shape import view_as_windows def structural_similarity(X, Y, win_size=7, gradient=False, dynamic_range=None): """Compute the mean structural similarity index between two images. Parameters ---------- X, Y : (N,N) ndarray Images. win_size : int The side-length of the sliding window used in comparison. Must be an odd value. gradient : bool If True, also return the gradient. dynamic_range : int Dynamic range of the input image (distance between minimum and maximum possible values). By default, this is estimated from the image data-type. Returns ------- s : float Strucutural similarity. grad : (N * N,) ndarray Gradient of the structural similarity index between X and Y. This is only returned if `gradient` is set to True. 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. """ if not X.dtype == Y.dtype: raise ValueError('Input images must have the same dtype.') if not X.shape == Y.shape: raise ValueError('Input images must have the same dimensions.') if not (win_size % 2 == 1): raise ValueError('Window size must be odd.') if dynamic_range is None: dmin, dmax = dtype_range[X.dtype.type] dynamic_range = dmax - dmin XW = view_as_windows(X, (win_size, win_size)) YW = view_as_windows(Y, (win_size, win_size)) NS = len(XW) NP = win_size * win_size ux = np.mean(np.mean(XW, axis=2), axis=2) uy = np.mean(np.mean(YW, axis=2), axis=2) # Compute variances var(X), var(Y) and var(X, Y) cov_norm = 1 / (win_size**2 - 1) XWM = XW - ux[..., None, None] YWM = YW - uy[..., None, None] vx = cov_norm * np.sum(np.sum(XWM**2, axis=2), axis=2) vy = cov_norm * np.sum(np.sum(YWM**2, axis=2), axis=2) vxy = cov_norm * np.sum(np.sum(XWM * YWM, axis=2), axis=2) R = dynamic_range K1 = 0.01 K2 = 0.03 C1 = (K1 * R)**2 C2 = (K2 * R)**2 A1, A2, B1, B2 = (v[..., None, None] for v in (2 * ux * uy + C1, 2 * vxy + C2, ux**2 + uy**2 + C1, vx + vy + C2)) S = np.mean((A1 * A2) / (B1 * B2)) if gradient: local_grad = 2 / (NP * B1**2 * B2**2) * \ ( A1 * B1 * (B2 * XW - A2 * YW) - \ B1 * B2 * (A2 - A1) * ux[..., None, None] + \ A1 * A2 * (B1 - B2) * uy[..., None, None] ) grad = np.zeros_like(X, dtype=float) OW = view_as_windows(grad, (win_size, win_size)) OW += local_grad grad /= NS return S, grad else: return S