Repository: scikit-image/scikit-image

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
```



Discussion:
Additionally it might be worth updating the documentation for the structural_similarity function to explicitly mention this, as well as explain which function(s) people should be using instead if they need to compare (parts of an) image data that is very thin or very shallow.
Thanks for reporting this @Pomax. Indeed this error message should be updated to be more useful in this case.

`normalized_root_mse` and `peak_signal_noise_ratio` may be better for very small images
Can I take this issue? 

Resolving patch:
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 @@ def structural_similarity(im1, im2,
 
     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.')


Test patch:
diff --git a/skimage/metrics/tests/test_structural_similarity.py b/skimage/metrics/tests/test_structural_similarity.py
--- a/skimage/metrics/tests/test_structural_similarity.py
+++ b/skimage/metrics/tests/test_structural_similarity.py
@@ -233,6 +233,19 @@ def test_mssim_mixed_dtype():
     assert_almost_equal(mssim, mssim_mixed)
 
 
+@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
+def test_structural_similarity_small_image(dtype):
+    X = np.zeros((5, 5), dtype=dtype)
+    # structural_similarity can be computed for small images if win_size is
+    # a) odd and b) less than or equal to the images' smaller side
+    assert_equal(structural_similarity(X, X, win_size=3), 1.0)
+    assert_equal(structural_similarity(X, X, win_size=5), 1.0)
+    # structural_similarity errors for small images if user doesn't specify
+    # win_size
+    with pytest.raises(ValueError):
+        structural_similarity(X, X)
+
+
 def test_invalid_input():
     # size mismatch
     X = np.zeros((9, 9), dtype=np.double)