From d2b40c9049368b0ca3c35b2a5af32950889c0080 Mon Sep 17 00:00:00 2001 From: Martijn Courteaux Date: Wed, 26 Oct 2022 12:18:32 +0200 Subject: [PATCH 1/3] Improve SSIM documentation and warn about data range. Many researchers rely on this function, but gives in case of floating-point data wrong results. I added some explanation on what might go wrong and how to accommodate for this. Personally, I'm in my fourth year of my PhD research and only find this out now. Probably, hundreds of papers have published incorrect SSIMs due to this subtlety. --- skimage/metrics/_structural_similarity.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/skimage/metrics/_structural_similarity.py b/skimage/metrics/_structural_similarity.py index ac0ac1315c6..b090f9ea88c 100644 --- a/skimage/metrics/_structural_similarity.py +++ b/skimage/metrics/_structural_similarity.py @@ -20,6 +20,7 @@ def structural_similarity(im1, im2, gaussian_weights=False, full=False, **kwargs): """ Compute the mean structural similarity index between two images. + Please pay attention to the `data_range` parameter. Parameters ---------- @@ -34,7 +35,9 @@ def structural_similarity(im1, 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. + data-type. This estimate is most likely wrong for floating-point image + data. Therefore it is recommended to always explicitly specify this + value. (See the 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 @@ -76,8 +79,20 @@ def structural_similarity(im1, im2, Notes ----- + If the `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, and `use_sample_covariance` to False. + to True, `sigma` to 1.5, and `use_sample_covariance` to False, and + specify the `data_range` argument. .. versionchanged:: 0.16 This function was renamed from ``skimage.measure.compare_ssim`` to From e97b19db8e40d931c2e92abf7232412dec1cf109 Mon Sep 17 00:00:00 2001 From: Martijn Courteaux Date: Wed, 26 Oct 2022 13:16:44 +0200 Subject: [PATCH 2/3] Fix whitespace requirements. --- skimage/metrics/_structural_similarity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/metrics/_structural_similarity.py b/skimage/metrics/_structural_similarity.py index b090f9ea88c..0ae8734dc75 100644 --- a/skimage/metrics/_structural_similarity.py +++ b/skimage/metrics/_structural_similarity.py @@ -81,7 +81,7 @@ def structural_similarity(im1, im2, ----- If the `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 + 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 @@ -89,7 +89,7 @@ def structural_similarity(im1, im2, 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, and `use_sample_covariance` to False, and specify the `data_range` argument. From fe5f1ae31b5469cf4fbebd8ea0f74969f0bda538 Mon Sep 17 00:00:00 2001 From: Martijn Courteaux Date: Sat, 29 Oct 2022 18:16:10 +0200 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Marianne Corvellec --- skimage/metrics/_structural_similarity.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/skimage/metrics/_structural_similarity.py b/skimage/metrics/_structural_similarity.py index 0ae8734dc75..f90a0896a2b 100644 --- a/skimage/metrics/_structural_similarity.py +++ b/skimage/metrics/_structural_similarity.py @@ -20,7 +20,7 @@ def structural_similarity(im1, im2, gaussian_weights=False, full=False, **kwargs): """ Compute the mean structural similarity index between two images. - Please pay attention to the `data_range` parameter. + Please pay attention to the `data_range` parameter with floating-point images. Parameters ---------- @@ -35,9 +35,9 @@ def structural_similarity(im1, 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 is most likely wrong for floating-point image - data. Therefore it is recommended to always explicitly specify this - value. (See the note below.) + 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 @@ -79,7 +79,7 @@ def structural_similarity(im1, im2, Notes ----- - If the `data_range` is not specified, the range is automatically guessed + 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 @@ -90,8 +90,8 @@ def structural_similarity(im1, im2, 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, and `use_sample_covariance` to False, and + 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