diff --git a/skimage/restoration/_denoise.py b/skimage/restoration/_denoise.py index 07819fa0..7d47225a 100644 --- a/skimage/restoration/_denoise.py +++ b/skimage/restoration/_denoise.py @@ -3,10 +3,11 @@ import numpy as np from .. import img_as_float from ..restoration._denoise_cy import _denoise_bilateral, _denoise_tv_bregman from .._shared.utils import _mode_deprecations +import warnings def denoise_bilateral(image, win_size=5, sigma_range=None, sigma_spatial=1, - bins=10000, mode='constant', cval=0): + bins=10000, mode='constant', cval=0, multichannel=True): """Denoise image using bilateral filter. This is an edge-preserving and noise reducing denoising filter. It averages @@ -45,6 +46,9 @@ def denoise_bilateral(image, win_size=5, sigma_range=None, sigma_spatial=1, cval : string Used in conjunction with mode 'constant', the value outside the image boundaries. + multichannel : bool + Whether the last axis of the image is to be interpreted as multiple + channels or another spatial dimension. Returns ------- @@ -64,6 +68,38 @@ def denoise_bilateral(image, win_size=5, sigma_range=None, sigma_spatial=1, >>> noisy = np.clip(noisy, 0, 1) >>> denoised = denoise_bilateral(noisy, sigma_range=0.05, sigma_spatial=15) """ + if multichannel: + if image.ndim != 3: + if image.ndim == 2: + raise ValueError("Use ``multichannel=False`` for 2D grayscale " + "images. The last axis of the input image " + "must be multiple color channels not another " + "spatial dimension.") + else: + raise ValueError("Bilateral filter is only implemented for " + "2D grayscale images (image.ndim == 2) and " + "2D multichannel (image.ndim == 3) images, " + "but the input image has {0} dimensions. " + "".format(image.ndim)) + elif image.shape[2] not in (3, 4): + if image.shape[2] > 4: + warnings.warn("The last axis of the input image is interpreted " + "as channels. Input image with shape {0} has {1} " + "channels in last axis. ``denoise_bilateral`` is " + "implemented for 2D grayscale and color images " + "only.".format(image.shape, image.shape[2])) + else: + msg = "Input image must be grayscale, RGB, or RGBA; but has shape {0}." + warnings.warn(msg.format(image.shape)) + else: + if image.ndim > 2: + raise ValueError("Bilateral filter is not implemented for " + "grayscale images of 3 or more dimensions, " + "but input image has {0} dimension. Use " + "``multichannel=True`` for 2-D RGB " + "images.".format(image.shape)) + + mode = _mode_deprecations(mode) return _denoise_bilateral(image, win_size, sigma_range, sigma_spatial, bins, mode, cval) diff --git a/skimage/restoration/tests/test_denoise.py b/skimage/restoration/tests/test_denoise.py index eacc5138..e565d741 100644 --- a/skimage/restoration/tests/test_denoise.py +++ b/skimage/restoration/tests/test_denoise.py @@ -2,6 +2,7 @@ import numpy as np from numpy.testing import run_module_suite, assert_raises, assert_equal from skimage import restoration, data, color, img_as_float, measure +from skimage._shared._warnings import expected_warnings np.random.seed(1234) @@ -159,34 +160,56 @@ def test_denoise_bilateral_2d(): img = np.clip(img, 0, 1) out1 = restoration.denoise_bilateral(img, sigma_range=0.1, - sigma_spatial=20) + sigma_spatial=20, multichannel=False) out2 = restoration.denoise_bilateral(img, sigma_range=0.2, - sigma_spatial=30) + sigma_spatial=30, multichannel=False) # 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() -def test_denoise_bilateral_3d(): +def test_denoise_bilateral_color(): img = checkerboard.copy() # add some random noise img += 0.5 * img.std() * np.random.rand(*img.shape) img = np.clip(img, 0, 1) - out1 = restoration.denoise_bilateral(img, sigma_range=0.1, - sigma_spatial=20) - out2 = restoration.denoise_bilateral(img, sigma_range=0.2, - sigma_spatial=30) + out1 = restoration.denoise_bilateral(img, sigma_range=0.1, sigma_spatial=20) + out2 = restoration.denoise_bilateral(img, sigma_range=0.2, sigma_spatial=30) # 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() +def test_denoise_bilateral_3d_grayscale(): + img = np.ones((50, 50, 3)) + assert_raises(ValueError, restoration.denoise_bilateral, img, + multichannel=False) + + +def test_denoise_bilateral_3d_multichannel(): + img = np.ones((50, 50, 50)) + with expected_warnings(["grayscale"]): + result = restoration.denoise_bilateral(img) + + expected = np.empty_like(img) + expected.fill(np.nan) + + assert_equal(result, expected) + + +def test_denoise_bilateral_multidimensional(): + img = np.ones((10, 10, 10, 10)) + assert_raises(ValueError, restoration.denoise_bilateral, img) + assert_raises(ValueError, restoration.denoise_bilateral, img, + multichannel=True) + + def test_denoise_bilateral_nan(): img = np.NaN + np.empty((50, 50)) - out = restoration.denoise_bilateral(img) + out = restoration.denoise_bilateral(img, multichannel=False) assert_equal(img, out)