diff --git a/TODO.txt b/TODO.txt index f5830f09..1c3d1324 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,4 +14,3 @@ Version 0.10 ------------ * Remove backwards-compatability of `skimage.measure.regionprops` * Remove deprecated logger function in `skimage/__init__.py` -* Remove deprecated function `filter.median_filter` diff --git a/skimage/filter/__init__.py b/skimage/filter/__init__.py index c8fca735..649eba6b 100644 --- a/skimage/filter/__init__.py +++ b/skimage/filter/__init__.py @@ -1,5 +1,4 @@ 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, @@ -25,7 +24,6 @@ denoise_tv_chambolle = deprecated('skimage.restoration.denoise_tv_chambolle')\ __all__ = ['inverse', 'wiener', 'LPIFilter2D', - 'median_filter', 'gaussian_filter', 'canny', 'sobel', diff --git a/skimage/filter/ctmf.py b/skimage/filter/ctmf.py deleted file mode 100644 index e39aa8bc..00000000 --- a/skimage/filter/ctmf.py +++ /dev/null @@ -1,109 +0,0 @@ -"""ctmf.py - constant time per pixel median filtering with an octagonal shape - -Reference: S. Perreault and P. Hebert, "Median Filtering in Constant Time", -IEEE Transactions on Image Processing, September 2007. - -Originally part of CellProfiler, code licensed under both GPL and BSD licenses. -Website: http://www.cellprofiler.org -Copyright (c) 2003-2009 Massachusetts Institute of Technology -Copyright (c) 2009-2011 Broad Institute -All rights reserved. -Original author: Lee Kamentsky -""" - -import warnings -import numpy as np -from . import _ctmf -from ._rank_order import rank_order -from .._shared.utils import deprecated - - -@deprecated('filter.rank.median') -def median_filter(image, radius=2, mask=None, percent=50): - """Masked median filter with octagon shape. - - Parameters - ---------- - image : (M, N) ndarray - Input image. - radius : int - Radius (in pixels) of a circle inscribed into the filtering - octagon. Must be at least 2. Default radius is 2. - mask : (M, N) ndarray - Mask with 1's for significant pixels, 0's for masked pixels. - By default, all pixels are considered significant. - percent : int - The unmasked pixels within the octagon are sorted, and the - value at `percent` percent of the index range is chosen. - Default value of 50 gives the median pixel. - - Returns - ------- - out : (M, N) ndarray - Filtered array. In areas where the median filter does - not overlap the mask, the filtered result is undefined, but - in practice, it will be the lowest value in the valid area. - - Notes - ----- - Because of the histogram implementation, the number of unique values - for the output is limited to 256. - - Examples - -------- - >>> a = np.ones((5, 5)) - >>> a[2, 2] = 10 # introduce outlier - >>> b = median_filter(a) - >>> b[2, 2] # the median filter is good at removing outliers - 1.0 - """ - - if image.ndim != 2: - raise TypeError("Input 'image' must be a two-dimensional array.") - - if radius < 2: - raise ValueError("Input 'radius' must be >= 2.") - - if mask is None: - mask = np.ones(image.shape, dtype=np.bool) - mask = np.ascontiguousarray(mask, dtype=np.bool) - - if np.all(~ mask): - warnings.warn('Mask is all over image! Returning copy of input image.') - return image.copy() - - if (not np.issubdtype(image.dtype, np.int) or - np.min(image) < 0 or np.max(image) > 255): - ranked_values, translation = rank_order(image[mask]) - max_ranked_values = np.max(ranked_values) - if max_ranked_values == 0: - warnings.warn('Particular case? Returning copy of input image.') - return image.copy() - if max_ranked_values > 255: - ranked_values = ranked_values * 255 // max_ranked_values - was_ranked = True - else: - ranked_values = image[mask] - was_ranked = False - ranked_image = np.zeros(image.shape, np.uint8) - ranked_image[mask] = ranked_values - - mask.dtype = np.uint8 - output = np.zeros(image.shape, np.uint8) - - _ctmf.median_filter(ranked_image, mask, output, radius, percent) - if was_ranked: - # - # The translation gives the original value at each ranking. - # We rescale the output to the original ranking and then - # use the translation to look up the original value in the image. - # - if max_ranked_values > 255: - result = translation[output.astype(np.uint32) * - max_ranked_values // 255] - else: - result = translation[output] - else: - result = output - return result - diff --git a/skimage/filter/tests/test_ctmf.py b/skimage/filter/tests/test_ctmf.py deleted file mode 100644 index c4f6d10e..00000000 --- a/skimage/filter/tests/test_ctmf.py +++ /dev/null @@ -1,127 +0,0 @@ -import numpy as np -from nose.tools import raises - -from skimage.filter import median_filter - - -def test_00_00_zeros(): - '''The median filter on an array of all zeros should be zero''' - result = median_filter(np.zeros((10, 10)), 3, np.ones((10, 10), bool)) - assert np.all(result == 0) - - -def test_00_01_all_masked(): - '''Test a completely masked image - - Regression test of IMG-1029''' - result = median_filter(np.zeros((10, 10)), 3, np.zeros((10, 10), bool)) - assert (np.all(result == 0)) - - -def test_00_02_all_but_one_masked(): - mask = np.zeros((10, 10), bool) - mask[5, 5] = True - median_filter(np.zeros((10, 10)), 3, mask) - - -def test_01_01_mask(): - '''The median filter, masking a single value''' - img = np.zeros((10, 10)) - img[5, 5] = 1 - mask = np.ones((10, 10), bool) - mask[5, 5] = False - result = median_filter(img, 3, mask) - assert (np.all(result[mask] == 0)) - np.testing.assert_equal(result[5, 5], 1) - - -def test_02_01_median(): - '''A median filter larger than the image = median of image''' - np.random.seed(0) - img = np.random.uniform(size=(9, 9)) - result = median_filter(img, 20, np.ones((9, 9), bool)) - np.testing.assert_equal(result[0, 0], np.median(img)) - assert (np.all(result == np.median(img))) - - -def test_02_02_median_bigger(): - '''Use an image of more than 255 values to test approximation''' - np.random.seed(0) - img = np.random.uniform(size=(20, 20)) - result = median_filter(img, 40, np.ones((20, 20), bool)) - sorted = np.ravel(img) - sorted.sort() - min_acceptable = sorted[198] - max_acceptable = sorted[202] - assert (np.all(result >= min_acceptable)) - assert (np.all(result <= max_acceptable)) - - -def test_03_01_shape(): - '''Make sure the median filter is the expected octagonal shape''' - - radius = 5 - a_2 = int(radius / 2.414213) - i, j = np.mgrid[-10:11, -10:11] - octagon = np.ones((21, 21), bool) - # - # constrain the octagon mask to be the points that are on - # the correct side of the 8 edges - # - octagon[i < -radius] = False - octagon[i > radius] = False - octagon[j < -radius] = False - octagon[j > radius] = False - octagon[i + j < -radius - a_2] = False - octagon[j - i > radius + a_2] = False - octagon[i + j > radius + a_2] = False - octagon[i - j > radius + a_2] = False - np.random.seed(0) - img = np.random.uniform(size=(21, 21)) - result = median_filter(img, radius, np.ones((21, 21), bool)) - sorted = img[octagon] - sorted.sort() - min_acceptable = sorted[len(sorted) / 2 - 1] - max_acceptable = sorted[len(sorted) / 2 + 1] - assert (result[10, 10] >= min_acceptable) - assert (result[10, 10] <= max_acceptable) - - -def test_04_01_half_masked(): - '''Make sure that the median filter can handle large masked areas.''' - img = np.ones((20, 20)) - mask = np.ones((20, 20), bool) - mask[10:, :] = False - img[~ mask] = 2 - img[1, 1] = 0 # to prevent short circuit for uniform data. - result = median_filter(img, 5, mask) - # in partial coverage areas, the result should be only - # from the masked pixels - assert (np.all(result[:14, :] == 1)) - # in zero coverage areas, the result should be the lowest - # value in the valid area - assert (np.all(result[15:, :] == np.min(img[mask]))) - - -def test_default_values(): - img = (np.random.random((20, 20)) * 255).astype(np.uint8) - mask = np.ones((20, 20), dtype=np.uint8) - result1 = median_filter(img, radius=2, mask=mask, percent=50) - result2 = median_filter(img) - np.testing.assert_array_equal(result1, result2) - - -@raises(ValueError) -def test_insufficient_size(): - img = (np.random.random((20, 20)) * 255).astype(np.uint8) - median_filter(img, radius=1) - - -@raises(TypeError) -def test_wrong_shape(): - img = np.empty((10, 10, 3)) - median_filter(img) - - -if __name__ == "__main__": - np.testing.run_module_suite()