Repository: scikit-image/scikit-image

Issue:
tifffile deprecation warning on test_tifffile.py
## Description

<!--
(Note: for guidance on how to use `scikit-image`, please post instead on https://forum.image.sc/tag/scikit-image)
-->

## Way to reproduce
```python
run pytest on skimage/io/tests/test_tifffile.py
```
Will show deprecation warning issues
```bash
skimage/io/tests/test_tifffile.py ...................................    [100%]

=============================== warnings summary ===============================
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[False-float32-shape1]
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[True-float32-shape1]
  /scikit-image/.venv/lib/python3.10/site-packages/tifffile/tifffile.py:1155: DeprecationWarning: <tifffile.TiffWriter.write> data with shape (10, 10, 3) and dtype 'float32' are stored as RGB with contiguous samples. Future versions will store such data as MINISBLACK in separate pages by default, unless the 'photometric' parameter is specified.
    result = tif.write(

skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[False-float32-shape2]
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[True-float32-shape2]
  /scikit-image/.venv/lib/python3.10/site-packages/tifffile/tifffile.py:1155: DeprecationWarning: <tifffile.TiffWriter.write> data with shape (10, 10, 4) and dtype 'float32' are stored as RGB with contiguous samples. Future versions will store such data as MINISBLACK in separate pages by default, unless the 'photometric' parameter is specified.
    result = tif.write(

skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[False-int16-shape1]
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[True-int16-shape1]
  /scikit-image/.venv/lib/python3.10/site-packages/tifffile/tifffile.py:1155: DeprecationWarning: <tifffile.TiffWriter.write> data with shape (10, 10, 3) and dtype 'int16' are stored as RGB with contiguous samples. Future versions will store such data as MINISBLACK in separate pages by default, unless the 'photometric' parameter is specified.
    result = tif.write(

skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[False-int16-shape2]
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[True-int16-shape2]
  /scikit-image/.venv/lib/python3.10/site-packages/tifffile/tifffile.py:1155: DeprecationWarning: <tifffile.TiffWriter.write> data with shape (10, 10, 4) and dtype 'int16' are stored as RGB with contiguous samples. Future versions will store such data as MINISBLACK in separate pages by default, unless the 'photometric' parameter is specified.
    result = tif.write(

skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[False-float64-shape1]
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[True-float64-shape1]
  /scikit-image/.venv/lib/python3.10/site-packages/tifffile/tifffile.py:1155: DeprecationWarning: <tifffile.TiffWriter.write> data with shape (10, 10, 3) and dtype 'float64' are stored as RGB with contiguous samples. Future versions will store such data as MINISBLACK in separate pages by default, unless the 'photometric' parameter is specified.
    result = tif.write(

skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[False-float64-shape2]
skimage/io/tests/test_tifffile.py::TestSave::test_imsave_roundtrip[True-float64-shape2]
  /scikit-image/.venv/lib/python3.10/site-packages/tifffile/tifffile.py:1155: DeprecationWarning: <tifffile.TiffWriter.write> data with shape (10, 10, 4) and dtype 'float64' are stored as RGB with contiguous samples. Future versions will store such data as MINISBLACK in separate pages by default, unless the 'photometric' parameter is specified.
    result = tif.write(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

```




Discussion:

Resolving patch:
diff --git a/skimage/io/_plugins/tifffile_plugin.py b/skimage/io/_plugins/tifffile_plugin.py
--- a/skimage/io/_plugins/tifffile_plugin.py
+++ b/skimage/io/_plugins/tifffile_plugin.py
@@ -1,6 +1,50 @@
+from tifffile import imread as tifffile_imread
+from tifffile import imwrite as tifffile_imwrite
+
 __all__ = ['imread', 'imsave']
 
-from tifffile import imwrite as imsave, imread as tifffile_imread
+
+def imsave(fname, arr, **kwargs):
+    """Load a tiff image to file.
+
+    Parameters
+    ----------
+    fname : str or file
+        File name or file-like object.
+    arr : ndarray
+        The array to write.
+    kwargs : keyword pairs, optional
+        Additional keyword arguments to pass through (see ``tifffile``'s
+        ``imwrite`` function).
+
+    Notes
+    -----
+    Provided by the tifffile library [1]_, and supports many
+    advanced image types including multi-page and floating-point.
+
+    This implementation will set ``photometric='RGB'`` when writing if the first
+    or last axis of `arr` has length 3 or 4. To override this, explicitly
+    pass the ``photometric`` kwarg.
+
+    This implementation will set ``planarconfig='SEPARATE'`` when writing if the
+    first axis of arr has length 3 or 4. To override this, explicitly
+    specify the ``planarconfig`` kwarg.
+
+    References
+    ----------
+    .. [1] https://pypi.org/project/tifffile/
+
+    """
+    if arr.shape[0] in [3, 4]:
+        if 'planarconfig' not in kwargs:
+            kwargs['planarconfig'] = 'SEPARATE'
+        rgb = True
+    else:
+        rgb = arr.shape[-1] in [3, 4]
+    if rgb and 'photometric' not in kwargs:
+        kwargs['photometric'] = 'RGB'
+
+    return tifffile_imwrite(fname, arr, **kwargs)
 
 
 def imread(fname, **kwargs):


Test patch:
diff --git a/skimage/_shared/testing.py b/skimage/_shared/testing.py
--- a/skimage/_shared/testing.py
+++ b/skimage/_shared/testing.py
@@ -212,23 +212,6 @@ def setup_test():
 
         warnings.simplefilter('error')
 
-        # do not error on specific warnings from the skimage.io module
-        # https://github.com/scikit-image/scikit-image/issues/5337
-        warnings.filterwarnings(
-            'default', message='TiffFile:', category=DeprecationWarning
-        )
-
-        warnings.filterwarnings(
-            'default', message='TiffWriter:', category=DeprecationWarning
-        )
-        # newer tifffile change the start of the warning string
-        # e.g. <tifffile.TiffWriter.write> data with shape ...
-        warnings.filterwarnings(
-            'default',
-            message='<tifffile.',
-            category=DeprecationWarning
-        )
-
         warnings.filterwarnings(
             'default', message='unclosed file', category=ResourceWarning
         )
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
@@ -1,26 +1,25 @@
 import os
-import numpy as np
 from io import BytesIO
 from tempfile import NamedTemporaryFile
 
-from ... import img_as_float
-from .. import imread, imsave, use_plugin, reset_plugins
-
+import numpy as np
+import pytest
 from PIL import Image
-from .._plugins.pil_plugin import (
-    pil_to_ndarray, ndarray_to_pil, _palette_is_grayscale)
-from ...color import rgb2lab
-
 from skimage._shared import testing
-from skimage._shared.testing import (mono_check, color_check,
-                                     assert_equal, assert_array_equal,
-                                     assert_array_almost_equal,
-                                     assert_allclose, fetch)
-from skimage._shared._warnings import expected_warnings
 from skimage._shared._tempfile import temporary_file
-
+from skimage._shared._warnings import expected_warnings
+from skimage._shared.testing import (assert_allclose,
+                                     assert_array_almost_equal,
+                                     assert_array_equal, assert_equal,
+                                     color_check, fetch, mono_check)
 from skimage.metrics import structural_similarity
 
+from ... import img_as_float
+from ...color import rgb2lab
+from .. import imread, imsave, reset_plugins, use_plugin
+from .._plugins.pil_plugin import (_palette_is_grayscale, ndarray_to_pil,
+                                   pil_to_ndarray)
+
 
 def setup():
     use_plugin('pil')
@@ -60,13 +59,22 @@ def test_imread_as_gray():
     assert np.sctype2char(img.dtype) in np.typecodes['AllInteger']
 
 
-def test_imread_separate_channels():
-    # Test that imread returns RGBA values contiguously even when they are
+@pytest.mark.parametrize('explicit_kwargs', [False, True])
+def test_imread_separate_channels(explicit_kwargs):
+    # Test that imread returns RGB(A) values contiguously even when they are
     # stored in separate planes.
     x = np.random.rand(3, 16, 8)
     with NamedTemporaryFile(suffix='.tif') as f:
         fname = f.name
 
+    # Tifffile is used as backend whenever suffix is .tif or .tiff
+    # To avoid pending changes to tifffile defaults, we must specify this is an
+    # RGB image with separate planes (i.e., channel_axis=0).
+    if explicit_kwargs:
+        kwargs = {'photometric': 'RGB', 'planarconfig': 'SEPARATE'}
+    else:
+        kwargs = {}
+
     imsave(fname, x)
     img = imread(fname)
     os.remove(fname)
diff --git a/skimage/io/tests/test_tifffile.py b/skimage/io/tests/test_tifffile.py
--- a/skimage/io/tests/test_tifffile.py
+++ b/skimage/io/tests/test_tifffile.py
@@ -4,7 +4,6 @@
 import numpy as np
 import pytest
 from numpy.testing import assert_array_almost_equal, assert_array_equal
-
 from skimage._shared.testing import fetch
 from skimage.io import imread, imsave, reset_plugins, use_plugin
 
@@ -51,13 +50,14 @@ def test_imread_handle():
 
 
 class TestSave:
-    def roundtrip(self, dtype, x, use_pathlib=False):
+
+    def roundtrip(self, dtype, x, use_pathlib=False, **kwargs):
         with NamedTemporaryFile(suffix='.tif') as f:
             fname = f.name
 
         if use_pathlib:
             fname = pathlib.Path(fname)
-        imsave(fname, x, check_contrast=False)
+        imsave(fname, x, check_contrast=False, **kwargs)
         y = imread(fname)
         assert_array_equal(x, y)
 
@@ -67,11 +67,17 @@ def roundtrip(self, dtype, x, use_pathlib=False):
     @pytest.mark.parametrize("shape", shapes)
     @pytest.mark.parametrize("dtype", dtypes)
     @pytest.mark.parametrize("use_pathlib", [False, True])
-    def test_imsave_roundtrip(self, shape, dtype, use_pathlib):
+    @pytest.mark.parametrize('explicit_photometric_kwarg', [False, True])
+    def test_imsave_roundtrip(self, shape, dtype, use_pathlib,
+                              explicit_photometric_kwarg):
         x = np.random.rand(*shape)
 
         if not np.issubdtype(dtype, np.floating):
             x = (x * np.iinfo(dtype).max).astype(dtype)
         else:
             x = x.astype(dtype)
-        self.roundtrip(dtype, x, use_pathlib)
+        if explicit_photometric_kwarg and x.shape[-1] in [3, 4]:
+            kwargs = {'photometric': 'rgb'}
+        else:
+            kwargs = {}
+        self.roundtrip(dtype, x, use_pathlib, **kwargs)