diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 007f267a..bb17033a 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -26,9 +26,11 @@ - Tony Yu Reading of paletted images; build, bug and doc fixes. Code to generate skimage logo. + Otsu thresholding, histogram equalisation, and more. - Zachary Pincus - Tracing of low cost paths, FreeImage I/O plugin, iso-contours + Tracing of low cost paths, FreeImage I/O plugin, iso-contours, + and more. - Almar Klein Binary heap class for graph algorithms @@ -45,7 +47,7 @@ - Emmanuelle Guillart Total variation noise filtering, integration of CellProfiler's - mathematical morphology tools. + mathematical morphology tools, tutorials, and more. - Maƫl Primet Total variation noise filtering diff --git a/doc/examples/plot_equalize.py b/doc/examples/plot_equalize.py new file mode 100644 index 00000000..9cb30dd7 --- /dev/null +++ b/doc/examples/plot_equalize.py @@ -0,0 +1,56 @@ +""" +====================== +Histogram Equalization +====================== + +This examples takes an image with low contrast and enhances its contrast using +histogram equalization. Histogram equalization enhances contrast by "spreading +out the most frequent intensity values" in an image [1]_. The equalized image +has a roughly linear cumulative distribution function, as shown in this example. + +.. [1] http://en.wikipedia.org/wiki/Histogram_equalization + +""" +import matplotlib.pyplot as plt + +from skimage import data +from skimage.util.dtype import dtype_range +from skimage import exposure + + +def plot_hist(img, bins=256): + """Plot histogram and cumulative histogram for image""" + img_cdf, bins = exposure.cumulative_distribution(img, bins) + plt.hist(img.ravel(), bins=bins) + plt.ylabel('Number of pixels') + plt.xlabel('Pixel intensiy') + + ax_cdf = plt.twinx() + ax_cdf.plot(bins, img_cdf, 'r') + xmin, xmax = dtype_range[img.dtype.type] + plt.xlim(xmin, xmax) + + ax_cdf.set_ylabel('Fraction of total intensity') + +img_orig = data.camera() +# squeeze image intensities to lower image contrast +img = img_orig / 5 + 100 +img_eq = exposure.equalize(img) + +plt.subplot(2, 2, 1) +plt.imshow(img, cmap=plt.cm.gray, vmin=0, vmax=255) +plt.title('Low contrast input image') +plt.axis('off') +plt.subplot(2, 2, 2) +plot_hist(img) + +plt.subplot(2, 2, 3) +plt.imshow(img_eq, cmap=plt.cm.gray, vmin=0, vmax=1) +plt.title('After\nhistogram equalization') +plt.axis('off') +plt.subplot(2, 2, 4) +plot_hist(img_eq) + +plt.subplots_adjust(left=0.05, hspace=0.25, wspace=0.3, top=0.95, bottom=0.1) +plt.show() + diff --git a/skimage/exposure/__init__.py b/skimage/exposure/__init__.py new file mode 100644 index 00000000..ee5969ab --- /dev/null +++ b/skimage/exposure/__init__.py @@ -0,0 +1 @@ +from exposure import histogram, equalize, cumulative_distribution diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py new file mode 100644 index 00000000..42a60dbf --- /dev/null +++ b/skimage/exposure/exposure.py @@ -0,0 +1,106 @@ +import numpy as np + +import skimage + + +__all__ = ['histogram', 'cumulative_distribution', 'equalize'] + + +def histogram(image, nbins=256): + """Return histogram of image. + + Unlike `numpy.histogram`, this function returns the centers of bins and + does not rebin integer arrays. For integer arrays, each integer value has + its own bin, which improves speed and intensity-resolution. + + Parameters + ---------- + image : array + Input image. + nbins : int + Number of bins used to calculate histogram. This value is ignored for + integer arrays. + + Returns + ------- + hist : array + The values of the histogram. + bin_centers : array + The values at the center of the bins. + """ + + # For integer types, histogramming with bincount is more efficient. + if np.issubdtype(image.dtype, np.integer): + offset = 0 + if np.min(image) < 0: + offset = np.min(image) + hist = np.bincount(image.ravel() - offset) + bin_centers = np.arange(len(hist)) + offset + + # clip histogram to start with a non-zero bin + idx = np.nonzero(hist)[0][0] + return hist[idx:], bin_centers[idx:] + else: + hist, bin_edges = np.histogram(image.flat, nbins) + bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2. + return hist, bin_centers + + +def cumulative_distribution(image, nbins=256): + """Return cumulative distribution function (cdf) for the given image. + + Parameters + ---------- + image : array + Image array. + nbins : int + Number of bins for image histogram. + + Returns + ------- + img_cdf : array + Values of cumulative distribution function. + bin_centers : array + Centers of bins. + + References + ---------- + .. [1] http://en.wikipedia.org/wiki/Cumulative_distribution_function + + """ + hist, bin_centers = histogram(image, nbins) + img_cdf = hist.cumsum() + img_cdf = img_cdf / float(img_cdf[-1]) + return img_cdf, bin_centers + + +def equalize(image, nbins=256): + """Return image after histogram equalization. + + Parameters + ---------- + image : array + Image array. + nbins : int + Number of bins for image histogram. + + Returns + ------- + out : float array + Image array after histogram equalization. + + Notes + ----- + This function is adapted from [1]_ with the author's permission. + + References + ---------- + .. [1] http://www.janeriksolem.net/2009/06/histogram-equalization-with-python-and.html + .. [2] http://en.wikipedia.org/wiki/Histogram_equalization + + """ + image = skimage.img_as_float(image) + cdf, bin_centers = cumulative_distribution(image, nbins) + out = np.interp(image.flat, bin_centers, cdf) + return out.reshape(image.shape) + diff --git a/skimage/exposure/tests/test_exposure.py b/skimage/exposure/tests/test_exposure.py new file mode 100644 index 00000000..22339de7 --- /dev/null +++ b/skimage/exposure/tests/test_exposure.py @@ -0,0 +1,37 @@ +import numpy as np + +import skimage +from skimage import data +from skimage import exposure + + +# squeeze image intensities to lower image contrast +test_img = data.camera() / 5 + 100 + + +def test_equalize_ubyte(): + img_eq = exposure.equalize(test_img) + + cdf, bin_edges = exposure.cumulative_distribution(img_eq) + check_cdf_slope(cdf) + + +def test_equalize_float(): + img = skimage.img_as_float(test_img) + img_eq = exposure.equalize(img) + + cdf, bin_edges = exposure.cumulative_distribution(img_eq) + check_cdf_slope(cdf) + + +def check_cdf_slope(cdf): + """Slope of cdf which should equal 1 for an equalized histogram.""" + norm_intensity = np.linspace(0, 1, len(cdf)) + slope, intercept = np.polyfit(norm_intensity, cdf, 1) + assert 0.9 < slope < 1.1 + + +if __name__ == '__main__': + from numpy import testing + testing.run_module_suite() + diff --git a/skimage/filter/thresholding.py b/skimage/filter/thresholding.py index 800e18b8..856bd78d 100644 --- a/skimage/filter/thresholding.py +++ b/skimage/filter/thresholding.py @@ -1,5 +1,7 @@ import numpy as np +from skimage.exposure import histogram + __all__ = ['threshold_otsu'] @@ -50,40 +52,3 @@ def threshold_otsu(image, nbins=256): threshold = bin_centers[:-1][idx] return threshold - -def histogram(image, nbins): - """Return histogram of image. - - Unlike `numpy.histogram`, this function returns the centers of bins and - does not rebin integer arrays. - - Parameters - ---------- - image : array - Input image. - nbins : int - Number of bins used to calculate histogram. This value is ignored for - integer arrays. - - Returns - ------- - hist : array - The values of the histogram. - bin_centers : array - The values at the center of the bins. - """ - if np.issubdtype(image.dtype, np.integer): - offset = 0 - if np.min(image) < 0: - offset = np.min(image) - hist = np.bincount(image.ravel() - offset) - bin_centers = np.arange(len(hist)) + offset - - # clip histogram to return only non-zero bins - idx = np.nonzero(hist)[0][0] - return hist[idx:], bin_centers[idx:] - else: - hist, bin_edges = np.histogram(image, bins=nbins) - bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2. - return hist, bin_centers -