from __future__ import division import numpy as np from ..util.dtype import dtype_range __all__ = ['compare_mse', 'compare_nrmse', 'compare_psnr', ] def _assert_compatible(im1, im2): """Raise an error if the shape and dtype do not match.""" if not im1.dtype == im2.dtype: raise ValueError('Input images must have the same dtype.') if not im1.shape == im2.shape: raise ValueError('Input images must have the same dimensions.') return def _as_floats(im1, im2): """Promote im1, im2 to nearest appropriate floating point precision.""" float_type = np.result_type(im1.dtype, im2.dtype, np.float32) if im1.dtype != float_type: im1 = im1.astype(float_type) if im2.dtype != float_type: im2 = im2.astype(float_type) return im1, im2 def compare_mse(im1, im2): """Compute the mean-squared error between two images. Parameters ---------- im1, im2 : ndarray Image. Any dimensionality. Returns ------- mse : float The mean-squared error (MSE) metric. """ _assert_compatible(im1, im2) im1, im2 = _as_floats(im1, im2) return np.mean(np.square(im1 - im2), dtype=np.float64) def compare_nrmse(im_true, im_test, norm_type='Euclidean'): """Compute the normalized root mean-squared error (NRMSE) between two images. Parameters ---------- im_true : ndarray Ground-truth image. im_test : ndarray Test image. norm_type : {'Euclidean', 'min-max', 'mean'} Controls the normalization method to use in the denominator of the NRMSE. There is no standard method of normalization across the literature [1]_. The methods available here are as follows: - 'Euclidean' : normalize by the Euclidean norm of ``im_true``. - 'min-max' : normalize by the intensity range of ``im_true``. - 'mean' : normalize by the mean of ``im_true``. Returns ------- nrmse : float The NRMSE metric. References ---------- .. [1] https://en.wikipedia.org/wiki/Root-mean-square_deviation """ _assert_compatible(im_true, im_test) im_true, im_test = _as_floats(im_true, im_test) norm_type = norm_type.lower() if norm_type == 'euclidean': denom = np.sqrt(np.mean((im_true*im_true), dtype=np.float64)) elif norm_type == 'min-max': denom = im_true.max() - im_true.min() elif norm_type == 'mean': denom = im_true.mean() else: raise ValueError("Unsupported norm_type") return np.sqrt(compare_mse(im_true, im_test)) / denom def compare_psnr(im_true, im_test, dynamic_range=None): """ Compute the peak signal to noise ratio (PSNR) for an image. Parameters ---------- im_true : ndarray Ground-truth image. im_test : ndarray Test image. dynamic_range : int The dynamic range of the input image (distance between minimum and maximum possible values). By default, this is estimated from the image data-type. Returns ------- psnr : float The PSNR metric. References ---------- .. [1] https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio """ _assert_compatible(im_true, im_test) if dynamic_range is None: dmin, dmax = dtype_range[im_true.dtype.type] true_min, true_max = np.min(im_true), np.max(im_true) if true_max > dmax or true_min < dmin: raise ValueError( "im_true has intensity values outside the range expected for " "its data type. Please manually specify the dynamic_range") if true_min >= 0: # most common case (255 for uint8, 1 for float) dynamic_range = dmax else: dynamic_range = dmax - dmin im_true, im_test = _as_floats(im_true, im_test) err = compare_mse(im_true, im_test) return 10 * np.log10((dynamic_range ** 2) / err)