diff --git a/TODO.txt b/TODO.txt index 9bc05571..d62c1c09 100644 --- a/TODO.txt +++ b/TODO.txt @@ -8,7 +8,8 @@ Version 0.10 depending on which optional dependencies are available. * Remove deprecated `out` parameter of `skimage.morphology.binary_*` * Remove deprecated parameter `depth` in `skimage.segmentation.random_walker` -* Remove deprecated logger function in ``skimage/__init__.py`` +* Remove deprecated logger function in `skimage/__init__.py` +* Remove deprecated function `filter.median_filter` Version 0.9 ----------- diff --git a/skimage/filter/ctmf.py b/skimage/filter/ctmf.py index 267c5b6c..e39aa8bc 100644 --- a/skimage/filter/ctmf.py +++ b/skimage/filter/ctmf.py @@ -1,4 +1,4 @@ -'''ctmf.py - constant time per pixel median filtering with an octagonal shape +"""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. @@ -9,39 +9,46 @@ 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. + """Masked median filter with octagon shape. Parameters ---------- - image : (M,N) ndarray, dtype uint8 + image : (M, N) ndarray Input image. - radius : {int, 2}, optional - The radius of a circle inscribed into the filtering - octagon. Must be at least 2. Default radius is 2. - mask : (M,N) ndarray, dtype uint8, optional - A value of 1 indicates a significant pixel, 0 - that a pixel is masked. By default, all pixels - are considered. - percent : {int, 50}, optional + 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 the `percent`-th index chosen. For example, the - default value of 50 chooses the median pixel. + value at `percent` percent of the index range is chosen. + Default value of 50 gives the median pixel. Returns ------- - out : (M,N) ndarray, dtype uint8 - Filtered array. In areas where the median filter does - not overlap the mask, the filtered result is underfined, but + 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)) @@ -49,53 +56,54 @@ def median_filter(image, radius=2, mask=None, percent=50): >>> b = median_filter(a) >>> b[2, 2] # the median filter is good at removing outliers 1.0 - ''' + """ if image.ndim != 2: - raise TypeError("The input 'image' must be a two dimensional array.") + raise TypeError("Input 'image' must be a two-dimensional array.") if radius < 2: - raise ValueError("The input 'radius' must be >= 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() - # - # Normalize the ranked image to 0-255 - # + if (not np.issubdtype(image.dtype, np.int) or np.min(image) < 0 or np.max(image) > 255): - ranked_image, translation = rank_order(image[mask]) - max_ranked_image = np.max(ranked_image) - if max_ranked_image == 0: - return image - if max_ranked_image > 255: - ranked_image = ranked_image * 255 // max_ranked_image + 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_image = image[mask] + ranked_values = image[mask] was_ranked = False - input = np.zeros(image.shape, np.uint8) - input[mask] = ranked_image + 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(input, mask, output, radius, percent) + _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_image > 255: + if max_ranked_values > 255: result = translation[output.astype(np.uint32) * - max_ranked_image // 255] + max_ranked_values // 255] else: result = translation[output] else: result = output return result +