From 082586c10cec63d2e8e8c0a4b1423c0f7b2850ae Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sun, 25 Aug 2013 14:50:55 +0200 Subject: [PATCH 1/4] Added a wrapper around ndimage's Gaussian filter This version of the Gaussian filter * uses 'nearest' as the default boundary mode. This can be discussed, but I had the impression that for images this is the most relevant mode ('extending' boundaries) * has a `multichannel` keyword, so that each color channel can be filtered separately. For now no attempt is made at guessing whether the image has color channels or not. --- skimage/filter/__init__.py | 2 + skimage/filter/_gaussian.py | 91 +++++++++++++++++++++++++++ skimage/filter/tests/test_gaussian.py | 29 +++++++++ 3 files changed, 122 insertions(+) create mode 100644 skimage/filter/_gaussian.py create mode 100644 skimage/filter/tests/test_gaussian.py diff --git a/skimage/filter/__init__.py b/skimage/filter/__init__.py index 1e13a87e..67088b20 100644 --- a/skimage/filter/__init__.py +++ b/skimage/filter/__init__.py @@ -1,5 +1,6 @@ from .lpi_filter import inverse, wiener, LPIFilter2D from .ctmf import median_filter +from ._gaussian import gaussian_filter from ._canny import canny from .edges import (sobel, hsobel, vsobel, scharr, hscharr, vscharr, prewitt, hprewitt, vprewitt, roberts , roberts_positive_diagonal, @@ -16,6 +17,7 @@ __all__ = ['inverse', 'wiener', 'LPIFilter2D', 'median_filter', + 'gaussian_filter', 'canny', 'sobel', 'hsobel', diff --git a/skimage/filter/_gaussian.py b/skimage/filter/_gaussian.py new file mode 100644 index 00000000..6f0f9a64 --- /dev/null +++ b/skimage/filter/_gaussian.py @@ -0,0 +1,91 @@ +from scipy import ndimage +from skimage.util import img_as_float + +__all__ = ['gaussian_filter'] + + +def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, + multichannel=False): + """ + Multi-dimensional Gaussian filter + + Parameters + ---------- + + image : array-like + input image to filter + sigma : scalar or sequence of scalars + standard deviation for Gaussian kernel. The standard + deviations of the Gaussian filter are given for each axis as a + sequence, or as a single number, in which case it is equal for + all axes. + output : array, optional + The ``output`` parameter passes an array in which to store the + filter output. + mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional + The ``mode`` parameter determines how the array borders are + handled, where ``cval`` is the value when mode is equal to + 'constant'. Default is 'nearest'. + cval : scalar, optional + Value to fill past edges of input if ``mode`` is 'constant'. Default + is 0.0 + multichannel : bool, optional (default: False) + Whether the last axis of the image is to be interpreted as multiple + channels. Only 3 channels are supported. If `None`, the function will + attempt to guess this, and raise a warning if ambiguous, when the + array has shape (M, N, 3). + + + Returns + ------- + + filtered_image : ndarray + the filtered array + + Notes + ----- + + This function is a wrapper around :func:`scipy.ndimage.gaussian_filter`. + + Integer arrays are converted to float. + + The multi-dimensional filter is implemented as a sequence of + one-dimensional convolution filters. The intermediate arrays are + stored in the same data type as the output. Therefore, for output + types with a limited precision, the results may be imprecise + because intermediate results may be stored with insufficient + precision. + + Examples + -------- + + >>> a = np.zeros((3, 3)) + >>> a[1, 1] = 1 + >>> a + array([[ 0., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 0.]]) + >>> gaussian_filter(a, sigma=0.4) # mild smoothing + array([[ 0.00163116, 0.03712502, 0.00163116], + [ 0.03712502, 0.84496158, 0.03712502], + [ 0.00163116, 0.03712502, 0.00163116]]) + >>> gaussian_filter(a, sigma=1) # more smooting + array([[ 0.05855018, 0.09653293, 0.05855018], + [ 0.09653293, 0.15915589, 0.09653293], + [ 0.05855018, 0.09653293, 0.05855018]]) + >>> # Several modes are possible for handling boundaries + >>> gaussian_filter(a, sigma=1, mode='reflect') + array([[ 0.08767308, 0.12075024, 0.08767308], + [ 0.12075024, 0.16630671, 0.12075024], + [ 0.08767308, 0.12075024, 0.08767308]]) + >>> # For RGB images, each is filtered separately + >>> from skimage.data import lena + >>> image = lena() + >>> filtered_lena = gaussian_filter(image, sigma=1, multichannel=True) + """ + if multichannel: + # do not filter across channels + ndim = image.ndim + sigma = [sigma] * (ndim - 1) + [0] + image = img_as_float(image) + return ndimage.gaussian_filter(image, sigma, mode=mode, cval=cval) diff --git a/skimage/filter/tests/test_gaussian.py b/skimage/filter/tests/test_gaussian.py new file mode 100644 index 00000000..afae5079 --- /dev/null +++ b/skimage/filter/tests/test_gaussian.py @@ -0,0 +1,29 @@ +import numpy as np +from skimage.filter._gaussian import gaussian_filter + + +def test_null_sigma(): + a = np.zeros((3, 3)) + a[1, 1] = 1. + assert np.all(gaussian_filter(a, 0) == a) + + +def test_energy_decrease(): + a = np.zeros((3, 3)) + a[1, 1] = 1. + gaussian_a = gaussian_filter(a, sigma=1, mode='reflect') + assert gaussian_a.std() < a.std() + + +def test_multichannel(): + a = np.zeros((3, 3, 3)) + a[1, 1] = np.arange(1, 4) + gaussian_rgb_a = gaussian_filter(a, sigma=1, mode='reflect', + multichannel=True) + assert np.allclose([a[..., i].mean() for i in range(3)], + [gaussian_rgb_a[..., i].mean() for i in range(3)]) + + +if __name__ == "__main__": + from numpy import testing + testing.run_module_suite() From 15b4a4d979f73a7b1d24220a767d3f9e02697c37 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sun, 25 Aug 2013 15:04:22 +0200 Subject: [PATCH 2/4] DOC: corrected description of multichannel parameter --- skimage/filter/_gaussian.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/filter/_gaussian.py b/skimage/filter/_gaussian.py index 6f0f9a64..0d6cdfbe 100644 --- a/skimage/filter/_gaussian.py +++ b/skimage/filter/_gaussian.py @@ -31,9 +31,8 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, is 0.0 multichannel : bool, optional (default: False) Whether the last axis of the image is to be interpreted as multiple - channels. Only 3 channels are supported. If `None`, the function will - attempt to guess this, and raise a warning if ambiguous, when the - array has shape (M, N, 3). + channels. If True, each channel is filtered separately (channels are + not mixed together). Returns From b4242ca3fb1005ac1839b63ac30cf87367f02015 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sun, 25 Aug 2013 16:41:37 +0200 Subject: [PATCH 3/4] [BUG] iterable sigma and multichannel + docstring improvement --- skimage/filter/_gaussian.py | 20 ++++++++++++-------- skimage/filter/tests/test_gaussian.py | 7 +++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/skimage/filter/_gaussian.py b/skimage/filter/_gaussian.py index 0d6cdfbe..8cb2be1f 100644 --- a/skimage/filter/_gaussian.py +++ b/skimage/filter/_gaussian.py @@ -1,3 +1,5 @@ +import collections as coll +import numpy as np from scipy import ndimage from skimage.util import img_as_float @@ -13,7 +15,8 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, ---------- image : array-like - input image to filter + input image (grayscale or color) to filter. If color channels are + to be filtered separately, use ``multichannel=True``. sigma : scalar or sequence of scalars standard deviation for Gaussian kernel. The standard deviations of the Gaussian filter are given for each axis as a @@ -22,19 +25,18 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, output : array, optional The ``output`` parameter passes an array in which to store the filter output. - mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional - The ``mode`` parameter determines how the array borders are - handled, where ``cval`` is the value when mode is equal to + mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional + The `mode` parameter determines how the array borders are + handled, where `cval` is the value when mode is equal to 'constant'. Default is 'nearest'. cval : scalar, optional - Value to fill past edges of input if ``mode`` is 'constant'. Default + Value to fill past edges of input if `mode` is 'constant'. Default is 0.0 multichannel : bool, optional (default: False) Whether the last axis of the image is to be interpreted as multiple channels. If True, each channel is filtered separately (channels are not mixed together). - Returns ------- @@ -84,7 +86,9 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, """ if multichannel: # do not filter across channels - ndim = image.ndim - sigma = [sigma] * (ndim - 1) + [0] + if not isinstance(sigma, coll.Iterable): + sigma = [sigma] * (image.ndim - 1) + if len(sigma) != image.ndim: + sigma = np.concatenate((np.asarray(sigma), [0])) image = img_as_float(image) return ndimage.gaussian_filter(image, sigma, mode=mode, cval=cval) diff --git a/skimage/filter/tests/test_gaussian.py b/skimage/filter/tests/test_gaussian.py index afae5079..77c01a63 100644 --- a/skimage/filter/tests/test_gaussian.py +++ b/skimage/filter/tests/test_gaussian.py @@ -20,6 +20,13 @@ def test_multichannel(): a[1, 1] = np.arange(1, 4) gaussian_rgb_a = gaussian_filter(a, sigma=1, mode='reflect', multichannel=True) + # Check that the mean value is conserved in each channel + # (color channels are not mixed together) + assert np.allclose([a[..., i].mean() for i in range(3)], + [gaussian_rgb_a[..., i].mean() for i in range(3)]) + # Iterable sigma + gaussian_rgb_a = gaussian_filter(a, sigma=[1, 2], mode='reflect', + multichannel=True) assert np.allclose([a[..., i].mean() for i in range(3)], [gaussian_rgb_a[..., i].mean() for i in range(3)]) From 9795d3fad4bb4c039d1515bece5e85cc88d76999 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Thu, 29 Aug 2013 23:14:30 +0200 Subject: [PATCH 4/4] Gaussian filter function: changed the default value of multichannel Now we try to guess automatically whether the image is grayscale or RGB --- skimage/filter/_gaussian.py | 23 +++++++++++++++++------ skimage/filter/tests/test_gaussian.py | 8 +++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/skimage/filter/_gaussian.py b/skimage/filter/_gaussian.py index 8cb2be1f..af63ba10 100644 --- a/skimage/filter/_gaussian.py +++ b/skimage/filter/_gaussian.py @@ -1,13 +1,16 @@ import collections as coll import numpy as np from scipy import ndimage -from skimage.util import img_as_float +import warnings + +from ..util import img_as_float +from ..color import guess_spatial_dimensions __all__ = ['gaussian_filter'] def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, - multichannel=False): + multichannel=None): """ Multi-dimensional Gaussian filter @@ -15,8 +18,7 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, ---------- image : array-like - input image (grayscale or color) to filter. If color channels are - to be filtered separately, use ``multichannel=True``. + input image (grayscale or color) to filter. sigma : scalar or sequence of scalars standard deviation for Gaussian kernel. The standard deviations of the Gaussian filter are given for each axis as a @@ -32,10 +34,12 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, cval : scalar, optional Value to fill past edges of input if `mode` is 'constant'. Default is 0.0 - multichannel : bool, optional (default: False) + multichannel : bool, optional (default: None) Whether the last axis of the image is to be interpreted as multiple channels. If True, each channel is filtered separately (channels are - not mixed together). + not mixed together). Only 3 channels are supported. If `None`, + the function will attempt to guess this, and raise a warning if + ambiguous, when the array has shape (M, N, 3). Returns ------- @@ -84,6 +88,13 @@ def gaussian_filter(image, sigma, output=None, mode='nearest', cval=0, >>> image = lena() >>> filtered_lena = gaussian_filter(image, sigma=1, multichannel=True) """ + spatial_dims = guess_spatial_dimensions(image) + if spatial_dims is None and multichannel is None: + msg = ("Images with dimensions (M, N, 3) are interpreted as 2D+RGB" + + " by default. Use `multichannel=False` to interpret as " + + " 3D image with last dimension of length 3.") + warnings.warn(RuntimeWarning(msg)) + multichannel = True if multichannel: # do not filter across channels if not isinstance(sigma, coll.Iterable): diff --git a/skimage/filter/tests/test_gaussian.py b/skimage/filter/tests/test_gaussian.py index 77c01a63..9118bfae 100644 --- a/skimage/filter/tests/test_gaussian.py +++ b/skimage/filter/tests/test_gaussian.py @@ -16,12 +16,18 @@ def test_energy_decrease(): def test_multichannel(): - a = np.zeros((3, 3, 3)) + a = np.zeros((5, 5, 3)) a[1, 1] = np.arange(1, 4) gaussian_rgb_a = gaussian_filter(a, sigma=1, mode='reflect', multichannel=True) # Check that the mean value is conserved in each channel # (color channels are not mixed together) + assert np.allclose([a[..., i].mean() for i in range(3)], + [gaussian_rgb_a[..., i].mean() for i in range(3)]) + # Test multichannel = None + gaussian_rgb_a = gaussian_filter(a, sigma=1, mode='reflect') + # Check that the mean value is conserved in each channel + # (color channels are not mixed together) assert np.allclose([a[..., i].mean() for i in range(3)], [gaussian_rgb_a[..., i].mean() for i in range(3)]) # Iterable sigma