__all__ = ['threshold_adaptive', 'threshold_otsu', 'threshold_yen', 'threshold_isodata'] import numpy as np import scipy.ndimage from skimage.exposure import histogram def threshold_adaptive(image, block_size, method='gaussian', offset=0, mode='reflect', param=None): """Applies an adaptive threshold to an array. Also known as local or dynamic thresholding where the threshold value is the weighted mean for the local neighborhood of a pixel subtracted by a constant. Alternatively the threshold can be determined dynamically by a a given function using the 'generic' method. Parameters ---------- image : (N, M) ndarray Input image. block_size : int Uneven size of pixel neighborhood which is used to calculate the threshold value (e.g. 3, 5, 7, ..., 21, ...). method : {'generic', 'gaussian', 'mean', 'median'}, optional Method used to determine adaptive threshold for local neighbourhood in weighted mean image. * 'generic': use custom function (see `param` parameter) * 'gaussian': apply gaussian filter (see `param` parameter for custom\ sigma value) * 'mean': apply arithmetic mean filter * 'median': apply median rank filter By default the 'gaussian' method is used. offset : float, optional Constant subtracted from weighted mean of neighborhood to calculate the local threshold value. Default offset is 0. 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 'reflect'. param : {int, function}, optional Either specify sigma for 'gaussian' method or function object for 'generic' method. This functions takes the flat array of local neighbourhood as a single argument and returns the calculated threshold for the centre pixel. Returns ------- threshold : (N, M) ndarray Thresholded binary image References ---------- .. [1] http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=threshold#adaptivethreshold Examples -------- >>> from skimage.data import camera >>> image = camera()[:50, :50] >>> binary_image1 = threshold_adaptive(image, 15, 'mean') >>> func = lambda arr: arr.mean() >>> binary_image2 = threshold_adaptive(image, 15, 'generic', param=func) """ thresh_image = np.zeros(image.shape, 'double') if method == 'generic': scipy.ndimage.generic_filter(image, param, block_size, output=thresh_image, mode=mode) elif method == 'gaussian': if param is None: # automatically determine sigma which covers > 99% of distribution sigma = (block_size - 1) / 6.0 else: sigma = param scipy.ndimage.gaussian_filter(image, sigma, output=thresh_image, mode=mode) elif method == 'mean': mask = 1. / block_size * np.ones((block_size,)) # separation of filters to speedup convolution scipy.ndimage.convolve1d(image, mask, axis=0, output=thresh_image, mode=mode) scipy.ndimage.convolve1d(thresh_image, mask, axis=1, output=thresh_image, mode=mode) elif method == 'median': scipy.ndimage.median_filter(image, block_size, output=thresh_image, mode=mode) return image > (thresh_image - offset) def threshold_otsu(image, nbins=256): """Return threshold value based on Otsu's method. Parameters ---------- image : array Input image. nbins : int, optional Number of bins used to calculate histogram. This value is ignored for integer arrays. Returns ------- threshold : float Upper threshold value. All pixels intensities that less or equal of this value assumed as foreground. References ---------- .. [1] Wikipedia, http://en.wikipedia.org/wiki/Otsu's_Method Examples -------- >>> from skimage.data import camera >>> image = camera() >>> thresh = threshold_otsu(image) >>> binary = image <= thresh """ hist, bin_centers = histogram(image, nbins) hist = hist.astype(float) # class probabilities for all possible thresholds weight1 = np.cumsum(hist) weight2 = np.cumsum(hist[::-1])[::-1] # class means for all possible thresholds mean1 = np.cumsum(hist * bin_centers) / weight1 mean2 = (np.cumsum((hist * bin_centers)[::-1]) / weight2[::-1])[::-1] # Clip ends to align class 1 and class 2 variables: # The last value of `weight1`/`mean1` should pair with zero values in # `weight2`/`mean2`, which do not exist. variance12 = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2 idx = np.argmax(variance12) threshold = bin_centers[:-1][idx] return threshold def threshold_yen(image, nbins=256): """Return threshold value based on Yen's method. Parameters ---------- image : array Input image. nbins : int, optional Number of bins used to calculate histogram. This value is ignored for integer arrays. Returns ------- threshold : float Upper threshold value. All pixels intensities that less or equal of this value assumed as foreground. References ---------- .. [1] Yen J.C., Chang F.J., and Chang S. (1995) "A New Criterion for Automatic Multilevel Thresholding" IEEE Trans. on Image Processing, 4(3): 370-378 .. [2] Sezgin M. and Sankur B. (2004) "Survey over Image Thresholding Techniques and Quantitative Performance Evaluation" Journal of Electronic Imaging, 13(1): 146-165, http://www.busim.ee.boun.edu.tr/~sankur/SankurFolder/Threshold_survey.pdf .. [3] ImageJ AutoThresholder code, http://fiji.sc/wiki/index.php/Auto_Threshold Examples -------- >>> from skimage.data import camera >>> image = camera() >>> thresh = threshold_yen(image) >>> binary = image <= thresh """ hist, bin_centers = histogram(image, nbins) # On blank images (e.g. filled with 0) with int dtype, `histogram()` # returns `bin_centers` containing only one value. Speed up with it. if bin_centers.size == 1: return bin_centers[0] # Calculate probability mass function pmf = hist.astype(np.float32) / hist.sum() P1 = np.cumsum(pmf) # Cumulative normalized histogram P1_sq = np.cumsum(pmf ** 2) # Get cumsum calculated from end of squared array: P2_sq = np.cumsum(pmf[::-1] ** 2)[::-1] # P2_sq indexes is shifted +1. I assume, with P1[:-1] it's help avoid '-inf' # in crit. ImageJ Yen implementation replaces those values by zero. crit = np.log(((P1_sq[:-1] * P2_sq[1:]) ** -1) * (P1[:-1] * (1.0 - P1[:-1])) ** 2) return bin_centers[crit.argmax()] def threshold_isodata(image, nbins=256): """Return threshold value based on ISODATA method. Histogram-based threshold, known as Ridler-Calvard method or intermeans. Parameters ---------- image : array Input image. nbins : int, optional Number of bins used to calculate histogram. This value is ignored for integer arrays. Returns ------- threshold : float or int, corresponding input array dtype. Upper threshold value. All pixels intensities that less or equal of this value assumed as background. References ---------- .. [1] Ridler, TW & Calvard, S (1978), "Picture thresholding using an iterative selection method" .. [2] IEEE Transactions on Systems, Man and Cybernetics 8: 630-632, http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=4310039 .. [3] Sezgin M. and Sankur B. (2004) "Survey over Image Thresholding Techniques and Quantitative Performance Evaluation" Journal of Electronic Imaging, 13(1): 146-165, http://www.busim.ee.boun.edu.tr/~sankur/SankurFolder/Threshold_survey.pdf .. [4] ImageJ AutoThresholder code, http://fiji.sc/wiki/index.php/Auto_Threshold Examples -------- >>> from skimage.data import coins >>> image = coins() >>> thresh = threshold_isodata(image) >>> binary = image > thresh """ hist, bin_centers = histogram(image, nbins) # On blank images (e.g. filled with 0) with int dtype, `histogram()` # returns `bin_centers` containing only one value. Speed up with it. if bin_centers.size == 1: return bin_centers[0] # It is not necessary to calculate the probability mass function here, # because the l and h fractions already include the normalization. pmf = hist.astype(np.float32) # / hist.sum() cpmfl = np.cumsum(pmf, dtype=np.float32) cpmfh = np.cumsum(pmf[::-1], dtype=np.float32)[::-1] binnums = np.arange(pmf.size, dtype=np.uint8) # l and h contain average value of pixels in sum of bins, calculated # from lower to higher and from higher to lower respectively. l = np.ma.divide(np.cumsum(pmf * binnums, dtype=np.float32), cpmfl) h = np.ma.divide( np.cumsum((pmf[::-1] * binnums[::-1]), dtype=np.float32)[::-1], cpmfh) allmean = (l + h) / 2.0 threshold = bin_centers[np.nonzero(allmean.round() == binnums)[0][0]] # This implementation returns threshold where # `background <= threshold < foreground`. return threshold