Repository: scikit-image/scikit-image

Issue:
ValueError raised when bool array saving with imageio plugin
## Description

A `ValueError` is raised when attempting to save a bool array using the 'imageio' plugin

## Way to reproduce

```python
>>> from skimage import io
>>> a = np.ones((5, 5), bool)
>>> io.imsave('/tmp/test.png', a, 'imageio')
__main__:1: UserWarning: /tmp/test.png is a low contrast image
__main__:1: UserWarning: /tmp/test.png is a boolean image: setting True to 1 and False to 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".conda/envs/skimage-debug/lib/python3.6/site-packages/skimage/io/_io.py", line 131, in imsave
    return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args)
  File "conda/envs/skimage-debug/lib/python3.6/site-packages/skimage/io/manage_plugins.py", line 210, in call_plugin
    return func(*args, **kwargs)
  File "conda/envs/skimage-debug/lib/python3.6/site-packages/imageio/core/functions.py", line 293, in imwrite
    raise ValueError("Image is not numeric, but {}.".format(imt.__name__))
ValueError: Image is not numeric, but ndarray.
```
## Version information

```python
>>> from __future__ import print_function
>>> import sys; print(sys.version)
3.6.10 |Anaconda, Inc.| (default, Jan  7 2020, 21:14:29) 
[GCC 7.3.0]
>>> import platform; print(platform.platform())
Linux-4.4.0-174-generic-x86_64-with-debian-stretch-sid
>>> import skimage; print("scikit-image version: {}".format(skimage.__version__))
scikit-image version: 0.16.2
>>> import numpy; print("numpy version: {}".format(numpy.__version__))
numpy version: 1.18.1
```


Discussion:

Resolving patch:
diff --git a/skimage/io/_io.py b/skimage/io/_io.py
--- a/skimage/io/_io.py
+++ b/skimage/io/_io.py
@@ -126,10 +126,13 @@ def imsave(fname, arr, plugin=None, check_contrast=True, **plugin_args):
     if plugin is None and hasattr(fname, 'lower'):
         if fname.lower().endswith(('.tiff', '.tif')):
             plugin = 'tifffile'
+    if arr.dtype == bool:
+        warn('%s is a boolean image: setting True to 255 and False to 0. '
+             'To silence this warning, please convert the image using '
+             'img_as_ubyte.' % fname, stacklevel=2)
+        arr = arr.astype('uint8') * 255
     if check_contrast and is_low_contrast(arr):
         warn('%s is a low contrast image' % fname)
-    if arr.dtype == bool:
-        warn('%s is a boolean image: setting True to 1 and False to 0' % fname)
     return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args)
 
 


Test patch:
diff --git a/skimage/_shared/testing.py b/skimage/_shared/testing.py
--- a/skimage/_shared/testing.py
+++ b/skimage/_shared/testing.py
@@ -128,7 +128,7 @@ def color_check(plugin, fmt='png'):
 
     img2 = img > 128
     r2 = roundtrip(img2, plugin, fmt)
-    testing.assert_allclose(img2.astype(np.uint8), r2)
+    testing.assert_allclose(img2, r2.astype(bool))
 
     img3 = img_as_float(img)
     r3 = roundtrip(img3, plugin, fmt)
@@ -160,7 +160,7 @@ def mono_check(plugin, fmt='png'):
 
     img2 = img > 128
     r2 = roundtrip(img2, plugin, fmt)
-    testing.assert_allclose(img2.astype(np.uint8), r2)
+    testing.assert_allclose(img2, r2.astype(bool))
 
     img3 = img_as_float(img)
     r3 = roundtrip(img3, plugin, fmt)
diff --git a/skimage/io/tests/test_imageio.py b/skimage/io/tests/test_imageio.py
--- a/skimage/io/tests/test_imageio.py
+++ b/skimage/io/tests/test_imageio.py
@@ -1,10 +1,12 @@
 from tempfile import NamedTemporaryFile
 
+import pytest
 import numpy as np
 from skimage.io import imread, imsave, use_plugin, reset_plugins
 
 from skimage._shared import testing
 from skimage._shared.testing import assert_array_almost_equal, TestCase, fetch
+from skimage._shared._warnings import expected_warnings
 
 
 def setup():
@@ -60,6 +62,15 @@ def test_imsave_roundtrip(self):
                 x = (x * 255).astype(dtype)
                 yield self.roundtrip, x
 
+    def test_bool_array_save(self):
+        f = NamedTemporaryFile(suffix='.png')
+        fname = f.name
+        f.close()
+        with expected_warnings(['.* is a boolean image']):
+            a = np.zeros((5, 5), bool)
+            a[2, 2] = True
+            imsave(fname, a)
+
 
 def test_return_class():
     testing.assert_equal(
diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py
--- a/skimage/io/tests/test_pil.py
+++ b/skimage/io/tests/test_pil.py
@@ -225,14 +225,14 @@ def test_imsave_boolean_input():
 
     # save to file-like object
     with expected_warnings(
-            ['is a boolean image: setting True to 1 and False to 0']):
+            ['is a boolean image: setting True to 255 and False to 0']):
         imsave(s, image)
 
     # read from file-like object
     s.seek(0)
     out = imread(s)
     assert_equal(out.shape, shape)
-    assert_allclose(out, image)
+    assert_allclose(out.astype(bool), image)
 
 
 def test_imexport_imimport():