Repository: scikit-image/scikit-image

Issue:
Allow use of complex numbers with denoise_nl_means
The code of non_local_means.py  converts the dataset to 'float', but I have a dataset with complex numbers. Is there a possibility to support more data types? 

Thanks
```



Discussion:

Resolving patch:
diff --git a/skimage/restoration/non_local_means.py b/skimage/restoration/non_local_means.py
--- a/skimage/restoration/non_local_means.py
+++ b/skimage/restoration/non_local_means.py
@@ -1,12 +1,14 @@
-import numpy as np
 from warnings import warn
+
+import numpy as np
+
 from .._shared import utils
 from .._shared.utils import convert_to_float
-from ._nl_means_denoising import (
-    _nl_means_denoising_2d,
-    _nl_means_denoising_3d,
-    _fast_nl_means_denoising_2d,
-    _fast_nl_means_denoising_3d)
+from ._nl_means_denoising import (_nl_means_denoising_2d,
+                                  _nl_means_denoising_3d,
+                                  _fast_nl_means_denoising_2d,
+                                  _fast_nl_means_denoising_3d,
+                                  _fast_nl_means_denoising_4d)
 
 
 @utils.channel_as_last_axis()
@@ -143,14 +145,18 @@ def denoise_nl_means(image, patch_size=7, patch_distance=11, h=0.1,
     >>> rng = np.random.default_rng()
     >>> a += 0.3 * rng.standard_normal(a.shape)
     >>> denoised_a = denoise_nl_means(a, 7, 5, 0.1)
-
     """
-    if image.ndim == 2:
+    if channel_axis is None:
+        multichannel = False
         image = image[..., np.newaxis]
-        channel_axis = -1
-    if image.ndim != 3:
-        raise NotImplementedError("Non-local means denoising is only \
-        implemented for 2D grayscale and RGB images or 3-D grayscale images.")
+    else:
+        multichannel = True
+
+    ndim_no_channel = image.ndim - 1
+    if (ndim_no_channel < 2) or (ndim_no_channel > 4):
+        raise NotImplementedError(
+            "Non-local means denoising is only implemented for 2D, "
+            "3D or 4D grayscale or multichannel images.")
 
     if preserve_range is None and np.issubdtype(image.dtype, np.integer):
         warn('Image dtype is not float. By default denoise_nl_means will '
@@ -166,13 +172,23 @@ def denoise_nl_means(image, patch_size=7, patch_distance=11, h=0.1,
         image = np.ascontiguousarray(image)
 
     kwargs = dict(s=patch_size, d=patch_distance, h=h, var=sigma * sigma)
-    if channel_axis is not None:  # 2-D images
+    if ndim_no_channel == 2:
+        nlm_func = (_fast_nl_means_denoising_2d if fast_mode else
+                    _nl_means_denoising_2d)
+    elif ndim_no_channel == 3:
+        if multichannel and not fast_mode:
+            raise NotImplementedError(
+                "Multichannel 3D requires fast_mode to be True.")
         if fast_mode:
-            return _fast_nl_means_denoising_2d(image, **kwargs)
+            nlm_func = _fast_nl_means_denoising_3d
         else:
-            return _nl_means_denoising_2d(image, **kwargs)
-    else:  # 3-D grayscale
+            # have to drop the size 1 channel axis for slow mode
+            image = image[..., 0]
+            nlm_func = _nl_means_denoising_3d
+    elif ndim_no_channel == 4:
         if fast_mode:
-            return _fast_nl_means_denoising_3d(image, **kwargs)
+            nlm_func = _fast_nl_means_denoising_4d
         else:
-            return _nl_means_denoising_3d(image, **kwargs)
+            raise NotImplementedError("4D requires fast_mode to be True.")
+    dn = np.asarray(nlm_func(image, **kwargs))
+    return dn


Test patch:
diff --git a/skimage/restoration/tests/test_denoise.py b/skimage/restoration/tests/test_denoise.py
--- a/skimage/restoration/tests/test_denoise.py
+++ b/skimage/restoration/tests/test_denoise.py
@@ -4,7 +4,7 @@
 import numpy as np
 import pytest
 import pywt
-from numpy.testing import (assert_, assert_almost_equal, assert_equal,
+from numpy.testing import (assert_array_almost_equal, assert_array_equal,
                            assert_warns)
 
 from skimage import color, data, img_as_float, restoration
@@ -13,9 +13,8 @@
 from skimage.metrics import peak_signal_noise_ratio, structural_similarity
 from skimage.restoration._denoise import _wavelet_threshold
 
-
 try:
-    import dask
+    import dask  # noqa
 except ImportError:
     DASK_NOT_INSTALLED_WARNING = 'The optional dask dependency is not installed'
 else:
@@ -74,7 +73,7 @@ def test_denoise_tv_chambolle_multichannel(channel_axis):
     denoised = restoration.denoise_tv_chambolle(img, weight=0.1,
                                                 channel_axis=channel_axis)
     _at = functools.partial(slice_at_axis, axis=channel_axis % img.ndim)
-    assert_equal(denoised[_at(0)], denoised0)
+    assert_array_equal(denoised[_at(0)], denoised0)
 
     # tile astronaut subset to generate 3D+channels data
     astro3 = np.tile(astro[:64, :64, np.newaxis, :], [1, 1, 2, 1])
@@ -87,7 +86,7 @@ def test_denoise_tv_chambolle_multichannel(channel_axis):
                                                 channel_axis=channel_axis)
     _at = functools.partial(slice_at_axis,
                             axis=channel_axis % astro3.ndim)
-    assert_equal(denoised[_at(0)], denoised0)
+    assert_array_equal(denoised[_at(0)], denoised0)
 
 
 def test_denoise_tv_chambolle_multichannel_deprecation():
@@ -102,20 +101,20 @@ def test_denoise_tv_chambolle_multichannel_deprecation():
         denoised = restoration.denoise_tv_chambolle(astro, 0.1, 2e-4, 200,
                                                     True)
 
-    assert_equal(denoised[..., 0], denoised0)
+    assert_array_equal(denoised[..., 0], denoised0)
 
 
 def test_denoise_tv_chambolle_float_result_range():
     # astronaut image
     img = astro_gray
     int_astro = np.multiply(img, 255).astype(np.uint8)
-    assert_(np.max(int_astro) > 1)
+    assert np.max(int_astro) > 1
     denoised_int_astro = restoration.denoise_tv_chambolle(int_astro,
                                                           weight=0.1)
     # test if the value range of output float data is within [0.0:1.0]
-    assert_(denoised_int_astro.dtype == float)
-    assert_(np.max(denoised_int_astro) <= 1.0)
-    assert_(np.min(denoised_int_astro) >= 0.0)
+    assert denoised_int_astro.dtype == float
+    assert np.max(denoised_int_astro) <= 1.0
+    assert np.min(denoised_int_astro) >= 0.0
 
 
 def test_denoise_tv_chambolle_3d():
@@ -128,8 +127,8 @@ def test_denoise_tv_chambolle_3d():
     mask[mask < 0] = 0
     mask[mask > 255] = 255
     res = restoration.denoise_tv_chambolle(mask.astype(np.uint8), weight=0.1)
-    assert_(res.dtype == float)
-    assert_(res.std() * 255 < mask.std())
+    assert res.dtype == float
+    assert res.std() * 255 < mask.std()
 
 
 def test_denoise_tv_chambolle_1d():
@@ -138,16 +137,16 @@ def test_denoise_tv_chambolle_1d():
     x += 20 * np.random.rand(x.size)
     x = np.clip(x, 0, 255)
     res = restoration.denoise_tv_chambolle(x.astype(np.uint8), weight=0.1)
-    assert_(res.dtype == float)
-    assert_(res.std() * 255 < x.std())
+    assert res.dtype == float
+    assert res.std() * 255 < x.std()
 
 
 def test_denoise_tv_chambolle_4d():
     """ TV denoising for a 4D input."""
     im = 255 * np.random.rand(8, 8, 8, 8)
     res = restoration.denoise_tv_chambolle(im.astype(np.uint8), weight=0.1)
-    assert_(res.dtype == float)
-    assert_(res.std() * 255 < im.std())
+    assert res.dtype == float
+    assert res.std() * 255 < im.std()
 
 
 def test_denoise_tv_chambolle_weighting():
@@ -164,8 +163,8 @@ def test_denoise_tv_chambolle_weighting():
     w = 0.2
     denoised_2d = restoration.denoise_tv_chambolle(img2d, weight=w)
     denoised_4d = restoration.denoise_tv_chambolle(img4d, weight=w)
-    assert_(structural_similarity(denoised_2d,
-                                  denoised_4d[:, :, 0, 0]) > 0.99)
+    assert structural_similarity(denoised_2d,
+                                 denoised_4d[:, :, 0, 0]) > 0.99
 
 
 def test_denoise_tv_bregman_2d():
@@ -178,20 +177,20 @@ def test_denoise_tv_bregman_2d():
     out2 = restoration.denoise_tv_bregman(img, weight=5)
 
     # make sure noise is reduced in the checkerboard cells
-    assert_(img[30:45, 5:15].std() > out1[30:45, 5:15].std())
-    assert_(out1[30:45, 5:15].std() > out2[30:45, 5:15].std())
+    assert img[30:45, 5:15].std() > out1[30:45, 5:15].std()
+    assert out1[30:45, 5:15].std() > out2[30:45, 5:15].std()
 
 
 def test_denoise_tv_bregman_float_result_range():
     # astronaut image
     img = astro_gray.copy()
     int_astro = np.multiply(img, 255).astype(np.uint8)
-    assert_(np.max(int_astro) > 1)
+    assert np.max(int_astro) > 1
     denoised_int_astro = restoration.denoise_tv_bregman(int_astro, weight=60.0)
     # test if the value range of output float data is within [0.0:1.0]
-    assert_(denoised_int_astro.dtype == float)
-    assert_(np.max(denoised_int_astro) <= 1.0)
-    assert_(np.min(denoised_int_astro) >= 0.0)
+    assert denoised_int_astro.dtype == float
+    assert np.max(denoised_int_astro) <= 1.0
+    assert np.min(denoised_int_astro) >= 0.0
 
 
 def test_denoise_tv_bregman_3d():
@@ -204,8 +203,8 @@ def test_denoise_tv_bregman_3d():
     out2 = restoration.denoise_tv_bregman(img, weight=5)
 
     # make sure noise is reduced in the checkerboard cells
-    assert_(img[30:45, 5:15].std() > out1[30:45, 5:15].std())
-    assert_(out1[30:45, 5:15].std() > out2[30:45, 5:15].std())
+    assert img[30:45, 5:15].std() > out1[30:45, 5:15].std()
+    assert out1[30:45, 5:15].std() > out2[30:45, 5:15].std()
 
 
 @pytest.mark.parametrize('channel_axis', [0, 1, 2, -1])
@@ -217,7 +216,7 @@ def test_denoise_tv_bregman_3d_multichannel(channel_axis):
                                               channel_axis=channel_axis)
     _at = functools.partial(slice_at_axis,
                             axis=channel_axis % img_astro.ndim)
-    assert_equal(denoised0, denoised[_at(0)])
+    assert_array_equal(denoised0, denoised[_at(0)])
 
 
 def test_denoise_tv_bregman_3d_multichannel_deprecation():
@@ -227,7 +226,7 @@ def test_denoise_tv_bregman_3d_multichannel_deprecation():
         denoised = restoration.denoise_tv_bregman(img_astro, weight=60.0,
                                                   multichannel=True)
 
-    assert_equal(denoised0, denoised[..., 0])
+    assert_array_equal(denoised0, denoised[..., 0])
 
 
 def test_denoise_tv_bregman_max_iter_deprecation():
@@ -244,7 +243,7 @@ def test_denoise_tv_bregman_multichannel():
     out1 = restoration.denoise_tv_bregman(img, weight=60.0)
     out2 = restoration.denoise_tv_bregman(img, weight=60.0, channel_axis=-1)
 
-    assert_equal(out1, out2)
+    assert_array_equal(out1, out2)
 
 
 def test_denoise_bilateral_2d():
@@ -259,8 +258,8 @@ def test_denoise_bilateral_2d():
                                          sigma_spatial=20, channel_axis=None)
 
     # make sure noise is reduced in the checkerboard cells
-    assert_(img[30:45, 5:15].std() > out1[30:45, 5:15].std())
-    assert_(out1[30:45, 5:15].std() > out2[30:45, 5:15].std())
+    assert img[30:45, 5:15].std() > out1[30:45, 5:15].std()
+    assert out1[30:45, 5:15].std() > out2[30:45, 5:15].std()
 
 
 def test_denoise_bilateral_pad():
@@ -273,7 +272,7 @@ def test_denoise_bilateral_pad():
     condition_padding = np.count_nonzero(np.isclose(img_bil,
                                                     0,
                                                     atol=0.001))
-    assert_equal(condition_padding, 0)
+    assert_array_equal(condition_padding, 0)
 
 
 @pytest.mark.parametrize('dtype', [np.float32, np.double])
@@ -301,12 +300,14 @@ def test_denoise_bregman_types(dtype):
 
 def test_denoise_bilateral_zeros():
     img = np.zeros((10, 10))
-    assert_equal(img, restoration.denoise_bilateral(img, channel_axis=None))
+    assert_array_equal(img,
+                       restoration.denoise_bilateral(img, channel_axis=None))
 
 
 def test_denoise_bilateral_constant():
     img = np.ones((10, 10)) * 5
-    assert_equal(img, restoration.denoise_bilateral(img, channel_axis=None))
+    assert_array_equal(img,
+                       restoration.denoise_bilateral(img, channel_axis=None))
 
 
 @pytest.mark.parametrize('channel_axis', [0, 1, -1])
@@ -328,8 +329,8 @@ def test_denoise_bilateral_color(channel_axis):
     out2 = np.moveaxis(out2, channel_axis, -1)
 
     # make sure noise is reduced in the checkerboard cells
-    assert_(img[30:45, 5:15].std() > out1[30:45, 5:15].std())
-    assert_(out1[30:45, 5:15].std() > out2[30:45, 5:15].std())
+    assert img[30:45, 5:15].std() > out1[30:45, 5:15].std()
+    assert out1[30:45, 5:15].std() > out2[30:45, 5:15].std()
 
 
 def test_denoise_bilateral_multichannel_deprecation():
@@ -348,8 +349,8 @@ def test_denoise_bilateral_multichannel_deprecation():
                                              multichannel=True)
 
     # make sure noise is reduced in the checkerboard cells
-    assert_(img[30:45, 5:15].std() > out1[30:45, 5:15].std())
-    assert_(out1[30:45, 5:15].std() > out2[30:45, 5:15].std())
+    assert img[30:45, 5:15].std() > out1[30:45, 5:15].std()
+    assert out1[30:45, 5:15].std() > out2[30:45, 5:15].std()
 
 
 def test_denoise_bilateral_3d_grayscale():
@@ -363,7 +364,7 @@ def test_denoise_bilateral_3d_multichannel():
     with expected_warnings(["grayscale"]):
         result = restoration.denoise_bilateral(img, channel_axis=-1)
 
-    assert_equal(result, img)
+    assert_array_equal(result, img)
 
 
 def test_denoise_bilateral_multidimensional():
@@ -380,7 +381,7 @@ def test_denoise_bilateral_nan():
     # Python 3.5 will not trigger a warning.
     with expected_warnings([r'invalid|\A\Z']):
         out = restoration.denoise_bilateral(img, channel_axis=None)
-    assert_equal(img, out)
+    assert_array_equal(img, out)
 
 
 @pytest.mark.parametrize('fast_mode', [False, True])
@@ -396,14 +397,14 @@ def test_denoise_nl_means_2d(fast_mode):
                                                 channel_axis=None,
                                                 sigma=s)
         # make sure noise is reduced
-        assert_(img.std() > denoised.std())
+        assert img.std() > denoised.std()
 
         denoised_f32 = restoration.denoise_nl_means(img_f32, 7, 5, 0.2,
                                                     fast_mode=fast_mode,
                                                     channel_axis=None,
                                                     sigma=s)
         # make sure noise is reduced
-        assert_(img.std() > denoised_f32.std())
+        assert img.std() > denoised_f32.std()
 
         # Sheck single precision result
         assert np.allclose(denoised_f32, denoised, atol=1e-2)
@@ -436,7 +437,7 @@ def test_denoise_nl_means_2d_multichannel(fast_mode, n_channels, dtype):
             denoised[..., :n_channels], img[..., :n_channels])
 
         # make sure noise is reduced
-        assert_(psnr_denoised > psnr_noisy)
+        assert psnr_denoised > psnr_noisy
 
 
 def test_denoise_nl_means_2d_multichannel_deprecated():
@@ -457,7 +458,7 @@ def test_denoise_nl_means_2d_multichannel_deprecated():
     psnr_denoised = peak_signal_noise_ratio(denoised, img)
 
     # make sure noise is reduced
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
     # providing multichannel argument positionally also warns
     with expected_warnings(["Providing the `multichannel` argument"]):
@@ -479,7 +480,7 @@ def test_denoise_nl_means_3d(fast_mode, dtype):
                                                 fast_mode=fast_mode,
                                                 channel_axis=None, sigma=s)
         # make sure noise is reduced
-        assert_(peak_signal_noise_ratio(img, denoised) > psnr_noisy)
+        assert peak_signal_noise_ratio(img, denoised) > psnr_noisy
 
 
 @pytest.mark.parametrize('fast_mode', [False, True])
@@ -488,25 +489,25 @@ def test_denoise_nl_means_3d(fast_mode, dtype):
 def test_denoise_nl_means_multichannel(fast_mode, dtype, channel_axis):
     # for true 3D data, 3D denoising is better than denoising as 2D+channels
     dtype = np.float64
-    rstate = np.random.RandomState(5)
 
     # synthetic 3d volume
     img = data.binary_blobs(length=32, n_dim=3, seed=5)
     img = img[:, :24, :16].astype(dtype, copy=False)
 
     sigma = 0.2
-    imgn = img + sigma * rstate.randn(*img.shape)
+    rng = np.random.default_rng(5)
+    imgn = img + sigma * rng.standard_normal(img.shape)
     imgn = imgn.astype(dtype)
 
     # test 3D denoising (channel_axis = None)
     denoised_ok_multichannel = restoration.denoise_nl_means(
-        imgn, 3, 2, h=0.6 * sigma, sigma=sigma, fast_mode=fast_mode,
+        imgn.copy(), 3, 2, h=0.6 * sigma, sigma=sigma, fast_mode=fast_mode,
         channel_axis=None)
 
     # set a channel axis: one dimension is (incorrectly) considered "channels"
     imgn = np.moveaxis(imgn, -1, channel_axis)
     denoised_wrong_multichannel = restoration.denoise_nl_means(
-        imgn, 3, 2, h=0.6 * sigma, sigma=sigma, fast_mode=fast_mode,
+        imgn.copy(), 3, 2, h=0.6 * sigma, sigma=sigma, fast_mode=fast_mode,
         channel_axis=channel_axis
     )
     denoised_wrong_multichannel = np.moveaxis(
@@ -515,14 +516,93 @@ def test_denoise_nl_means_multichannel(fast_mode, dtype, channel_axis):
 
     psnr_wrong = peak_signal_noise_ratio(img, denoised_wrong_multichannel)
     psnr_ok = peak_signal_noise_ratio(img, denoised_ok_multichannel)
-    assert_(psnr_ok > psnr_wrong)
+    assert psnr_ok > psnr_wrong
+
+
+def test_denoise_nl_means_4d():
+    rng = np.random.default_rng(5)
+    img = np.zeros((10, 10, 8, 5))
+    img[2:-2, 2:-2, 2:-2, :2] = 0.5
+    img[2:-2, 2:-2, 2:-2, 2:] = 1.
+    sigma = 0.3
+    imgn = img + sigma * rng.standard_normal(img.shape)
+
+    nlmeans_kwargs = dict(patch_size=3, patch_distance=2, h=0.3 * sigma,
+                          sigma=sigma, fast_mode=True)
+
+    psnr_noisy = peak_signal_noise_ratio(img, imgn, data_range=1.)
+
+    # denoise by looping over 3D slices
+    denoised_3d = np.zeros_like(imgn)
+    for ch in range(img.shape[-1]):
+        denoised_3d[..., ch] = restoration.denoise_nl_means(
+            imgn[..., ch],
+            channel_axis=None,
+            **nlmeans_kwargs)
+    psnr_3d = peak_signal_noise_ratio(img, denoised_3d, data_range=1.)
+    assert psnr_3d > psnr_noisy
+
+    # denoise as 4D
+    denoised_4d = restoration.denoise_nl_means(imgn,
+                                               channel_axis=None,
+                                               **nlmeans_kwargs)
+    psnr_4d = peak_signal_noise_ratio(img, denoised_4d, data_range=1.)
+    assert psnr_4d > psnr_3d
+
+    # denoise as 3D + channels instead
+    denoised_3dmc = restoration.denoise_nl_means(imgn,
+                                                 channel_axis=-1,
+                                                 **nlmeans_kwargs)
+    psnr_3dmc = peak_signal_noise_ratio(img, denoised_3dmc, data_range=1.)
+    assert psnr_3dmc > psnr_3d
+
+
+def test_denoise_nl_means_4d_multichannel():
+    img = np.zeros((8, 8, 8, 4, 4))
+    img[2:-2, 2:-2, 2:-2, 1:-1, :] = 1.
+    sigma = 0.3
+    imgn = img + sigma * np.random.randn(*img.shape)
+
+    psnr_noisy = peak_signal_noise_ratio(img, imgn, data_range=1.)
+
+    denoised_4dmc = restoration.denoise_nl_means(imgn, 3, 3, h=0.35 * sigma,
+                                                 fast_mode=True,
+                                                 channel_axis=-1,
+                                                 sigma=sigma)
+    psnr_4dmc = peak_signal_noise_ratio(img, denoised_4dmc, data_range=1.)
+    assert psnr_4dmc > psnr_noisy
 
 
 def test_denoise_nl_means_wrong_dimension():
-    img = np.zeros((5, 5, 5, 5))
+    # 1D not implemented
+    img = np.zeros((5, ))
+    with pytest.raises(NotImplementedError):
+        restoration.denoise_nl_means(img, channel_axis=None)
+
+    img = np.zeros((5, 3))
     with pytest.raises(NotImplementedError):
         restoration.denoise_nl_means(img, channel_axis=-1)
 
+    # 3D + channels only for fast mode
+    img = np.zeros((5, 5, 5, 5))
+    with pytest.raises(NotImplementedError):
+        restoration.denoise_nl_means(img, channel_axis=-1, fast_mode=False)
+
+    # 4D only for fast mode
+    img = np.zeros((5, 5, 5, 5))
+    with pytest.raises(NotImplementedError):
+        restoration.denoise_nl_means(img, channel_axis=None, fast_mode=False)
+
+    # 4D + channels only for fast mode
+    img = np.zeros((5, 5, 5, 5, 5))
+    with pytest.raises(NotImplementedError):
+        restoration.denoise_nl_means(img, channel_axis=-1, fast_mode=False)
+
+    # 5D not implemented
+    img = np.zeros((5, 5, 5, 5, 5))
+    with pytest.raises(NotImplementedError):
+        restoration.denoise_nl_means(img, channel_axis=None)
+
 
 @pytest.mark.parametrize('fast_mode', [False, True])
 @pytest.mark.parametrize('dtype', ['float64', 'float32'])
@@ -535,11 +615,11 @@ def test_no_denoising_for_small_h(fast_mode, dtype):
     denoised = restoration.denoise_nl_means(img, 7, 5, 0.01,
                                             fast_mode=fast_mode,
                                             channel_axis=None)
-    assert_(np.allclose(denoised, img))
+    assert np.allclose(denoised, img)
     denoised = restoration.denoise_nl_means(img, 7, 5, 0.01,
                                             fast_mode=fast_mode,
                                             channel_axis=None)
-    assert_(np.allclose(denoised, img))
+    assert np.allclose(denoised, img)
 
 
 @pytest.mark.parametrize('fast_mode', [False, True])
@@ -598,7 +678,7 @@ def test_wavelet_denoising(img, multichannel, convert2ycbcr):
                                            rescale_sigma=True)
     psnr_noisy = peak_signal_noise_ratio(img, noisy)
     psnr_denoised = peak_signal_noise_ratio(img, denoised)
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
     # Verify that SNR is improved with internally estimated sigma
     denoised = restoration.denoise_wavelet(noisy,
@@ -607,7 +687,7 @@ def test_wavelet_denoising(img, multichannel, convert2ycbcr):
                                            rescale_sigma=True)
     psnr_noisy = peak_signal_noise_ratio(img, noisy)
     psnr_denoised = peak_signal_noise_ratio(img, denoised)
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
     # SNR is improved less with 1 wavelet level than with the default.
     denoised_1 = restoration.denoise_wavelet(noisy,
@@ -616,8 +696,8 @@ def test_wavelet_denoising(img, multichannel, convert2ycbcr):
                                              convert2ycbcr=convert2ycbcr,
                                              rescale_sigma=True)
     psnr_denoised_1 = peak_signal_noise_ratio(img, denoised_1)
-    assert_(psnr_denoised > psnr_denoised_1)
-    assert_(psnr_denoised_1 > psnr_noisy)
+    assert psnr_denoised > psnr_denoised_1
+    assert psnr_denoised_1 > psnr_noisy
 
     # Test changing noise_std (higher threshold, so less energy in signal)
     res1 = restoration.denoise_wavelet(noisy, sigma=2 * sigma,
@@ -626,7 +706,7 @@ def test_wavelet_denoising(img, multichannel, convert2ycbcr):
     res2 = restoration.denoise_wavelet(noisy, sigma=sigma,
                                        channel_axis=channel_axis,
                                        rescale_sigma=True)
-    assert_(np.sum(res1**2) <= np.sum(res2**2))
+    assert np.sum(res1**2) <= np.sum(res2**2)
 
 
 @pytest.mark.parametrize('channel_axis', [0, 1, 2, -1])
@@ -648,7 +728,7 @@ def test_wavelet_denoising_channel_axis(channel_axis, convert2ycbcr):
                                            rescale_sigma=True)
     psnr_noisy = peak_signal_noise_ratio(img, noisy)
     psnr_denoised = peak_signal_noise_ratio(img, denoised)
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
 
 def test_wavelet_denoising_deprecated():
@@ -665,7 +745,7 @@ def test_wavelet_denoising_deprecated():
                                                rescale_sigma=True)
     psnr_noisy = peak_signal_noise_ratio(img, noisy)
     psnr_denoised = peak_signal_noise_ratio(img, denoised)
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
     # providing multichannel argument positionally also warns
     with expected_warnings(["Providing the `multichannel` argument"]):
@@ -734,7 +814,7 @@ def test_wavelet_denoising_scaling(case, dtype, convert2ycbcr,
                                                 data_range=data_range)
 
         # output's max value is not substantially smaller than x's
-        assert_(denoised.max() > 0.9 * x.max())
+        assert denoised.max() > 0.9 * x.max()
     else:
         # have to compare to x_as_float in integer input cases
         x_as_float = img_as_float(x)
@@ -743,13 +823,13 @@ def test_wavelet_denoising_scaling(case, dtype, convert2ycbcr,
                                                 data_range=f_data_range)
 
         # output has been clipped to expected range
-        assert_(denoised.max() <= 1.0)
+        assert denoised.max() <= 1.0
         if np.dtype(dtype).kind == 'u':
-            assert_(denoised.min() >= 0)
+            assert denoised.min() >= 0
         else:
-            assert_(denoised.min() >= -1)
+            assert denoised.min() >= -1
 
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
 
 def test_wavelet_threshold():
@@ -765,7 +845,7 @@ def test_wavelet_threshold():
                                   threshold=sigma)
     psnr_noisy = peak_signal_noise_ratio(img, noisy)
     psnr_denoised = peak_signal_noise_ratio(img, denoised)
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
     # either method or threshold must be defined
     with pytest.raises(ValueError):
@@ -810,7 +890,7 @@ def test_wavelet_denoising_nd(rescale_sigma, method, ndim):
         rescale_sigma=rescale_sigma)
     psnr_noisy = peak_signal_noise_ratio(img, noisy)
     psnr_denoised = peak_signal_noise_ratio(img, denoised)
-    assert_(psnr_denoised > psnr_noisy)
+    assert psnr_denoised > psnr_noisy
 
 
 def test_wavelet_invalid_method():
@@ -843,7 +923,7 @@ def test_wavelet_denoising_levels(rescale_sigma):
     psnr_denoised_1 = peak_signal_noise_ratio(img, denoised_1)
 
     # multi-level case should outperform single level case
-    assert_(psnr_denoised > psnr_denoised_1 > psnr_noisy)
+    assert psnr_denoised > psnr_denoised_1 > psnr_noisy
 
     # invalid number of wavelet levels results in a ValueError or UserWarning
     max_level = pywt.dwt_max_level(np.min(img.shape),
@@ -871,7 +951,7 @@ def test_estimate_sigma_gray():
     img += sigma * rstate.standard_normal(img.shape)
 
     sigma_est = restoration.estimate_sigma(img, channel_axis=None)
-    assert_almost_equal(sigma, sigma_est, decimal=2)
+    assert_array_almost_equal(sigma, sigma_est, decimal=2)
 
 
 def test_estimate_sigma_masked_image():
@@ -888,7 +968,7 @@ def test_estimate_sigma_masked_image():
     img[center_roi] = sigma * rstate.standard_normal(img[center_roi].shape)
 
     sigma_est = restoration.estimate_sigma(img, channel_axis=None)
-    assert_almost_equal(sigma, sigma_est, decimal=1)
+    assert_array_almost_equal(sigma, sigma_est, decimal=1)
 
 
 @pytest.mark.parametrize('channel_axis', [0, 1, 2, -1])
@@ -903,12 +983,12 @@ def test_estimate_sigma_color(channel_axis):
 
     sigma_est = restoration.estimate_sigma(img, channel_axis=channel_axis,
                                            average_sigmas=True)
-    assert_almost_equal(sigma, sigma_est, decimal=2)
+    assert_array_almost_equal(sigma, sigma_est, decimal=2)
 
     sigma_list = restoration.estimate_sigma(img, channel_axis=channel_axis,
                                             average_sigmas=False)
-    assert_equal(len(sigma_list), img.shape[channel_axis])
-    assert_almost_equal(sigma_list[0], sigma_est, decimal=2)
+    assert_array_equal(len(sigma_list), img.shape[channel_axis])
+    assert_array_almost_equal(sigma_list[0], sigma_est, decimal=2)
 
     if channel_axis % img.ndim == 2:
         # default channel_axis=None should raise a warning about last axis size
@@ -926,12 +1006,12 @@ def test_estimate_sigma_color_deprecated_multichannel():
     with expected_warnings(["`multichannel` is a deprecated argument"]):
         sigma_est = restoration.estimate_sigma(img, multichannel=True,
                                                average_sigmas=True)
-    assert_almost_equal(sigma, sigma_est, decimal=2)
+    assert_array_almost_equal(sigma, sigma_est, decimal=2)
 
     # providing multichannel argument positionally also warns
     with expected_warnings(["Providing the `multichannel` argument"]):
         sigma_est = restoration.estimate_sigma(img, True, True)
-    assert_almost_equal(sigma, sigma_est, decimal=2)
+    assert_array_almost_equal(sigma, sigma_est, decimal=2)
 
 
 @pytest.mark.parametrize('rescale_sigma', [True, False])
@@ -1008,7 +1088,7 @@ def test_cycle_spinning_multichannel(rescale_sigma):
                                            func_kw=func_kw,
                                            channel_axis=channel_axis)
             dn = denoise_func(noisy, **func_kw)
-        assert_equal(dn, dn_cc)
+        assert_array_equal(dn, dn_cc)
 
         # denoising with cycle spinning will give better PSNR than without
         for max_shifts in valid_shifts:
@@ -1019,7 +1099,7 @@ def test_cycle_spinning_multichannel(rescale_sigma):
                                                channel_axis=channel_axis)
             psnr = peak_signal_noise_ratio(img, dn)
             psnr_cc = peak_signal_noise_ratio(img, dn_cc)
-            assert_(psnr_cc > psnr)
+            assert psnr_cc > psnr
 
         for shift_steps in valid_steps:
             with expected_warnings([DASK_NOT_INSTALLED_WARNING]):
@@ -1030,7 +1110,7 @@ def test_cycle_spinning_multichannel(rescale_sigma):
                                                channel_axis=channel_axis)
             psnr = peak_signal_noise_ratio(img, dn)
             psnr_cc = peak_signal_noise_ratio(img, dn_cc)
-            assert_(psnr_cc > psnr)
+            assert psnr_cc > psnr
 
         for max_shifts in invalid_shifts:
             with pytest.raises(ValueError):
@@ -1067,8 +1147,8 @@ def test_cycle_spinning_num_workers():
         dn_cc3 = restoration.cycle_spin(noisy, denoise_func, max_shifts=1,
                                         func_kw=func_kw, channel_axis=None,
                                         num_workers=None)
-    assert_almost_equal(dn_cc1, dn_cc2)
-    assert_almost_equal(dn_cc1, dn_cc3)
+    assert_array_almost_equal(dn_cc1, dn_cc2)
+    assert_array_almost_equal(dn_cc1, dn_cc3)
 
 
 def test_cycle_spinning_num_workers_deprecated_multichannel():
@@ -1097,7 +1177,7 @@ def test_cycle_spinning_num_workers_deprecated_multichannel():
         dn_cc2 = restoration.cycle_spin(noisy, denoise_func, max_shifts=1,
                                         func_kw=func_kw, multichannel=False,
                                         num_workers=2)
-    assert_almost_equal(dn_cc1, dn_cc2)
+    assert_array_almost_equal(dn_cc1, dn_cc2)
 
     # providing multichannel argument positionally also warns
     mc_warn_str = "Providing the `multichannel` argument"