diff --git a/doc/examples/plot_equalize_hist.py b/doc/examples/plot_equalize_hist.py new file mode 100644 index 00000000..59b8defc --- /dev/null +++ b/doc/examples/plot_equalize_hist.py @@ -0,0 +1,55 @@ +""" +====================== +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, ax=None): + """Plot histogram and cumulative histogram for image""" + ax = ax if ax is not None else plt.gca() + img_cdf, bins = exposure.cumulative_distribution(img, bins) + ax.hist(img.ravel(), bins=bins) + ax_right = ax.twinx() + ax_right.plot(bins, img_cdf, 'r') + xmin, xmax = dtype_range[img.dtype.type] + ax.set_xlim(xmin, xmax) + + ax.set_ylabel('# pixels') + ax.set_xlabel('pixel intensiy') + ax_right.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_hist(img) + +plt.subplot(2, 2, 1) +plt.imshow(img, cmap=plt.cm.gray, vmin=0, vmax=255) +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.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 index 51dcc5cf..1c64aad4 100644 --- a/skimage/exposure/__init__.py +++ b/skimage/exposure/__init__.py @@ -1 +1 @@ -from exposure import equalize_hist, cumulative_distribution +from exposure import histogram, equalize_hist, cumulative_distribution diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 1d328e3e..06172597 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -1,15 +1,63 @@ import numpy as np - -__all__ = ['cumulative_distribution', 'equalize_hist'] +import skimage -def cumulative_distribution(img, nbins=256): +__all__ = ['histogram', 'cumulative_distribution', 'equalize_hist'] + + +def histogram(image, nbins, density=True): + """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. + density : {True | False} + If True, values represent the probability density function. + If False, values represent the number of pixels in bins. + See numpy.histogram for details. + + 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 start with a non-zero bin + idx = np.nonzero(hist)[0][0] + return hist[idx:], bin_centers[idx:] + else: + + if np.version.version >= '1.6': + hist, bin_edges = np.histogram(image.flat, nbins, density=density) + else: + hist, bin_edges = np.histogram(image.flat, nbins, normed=density) + + 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 ---------- - img : array + image : array Image array. nbins : int Number of bins for image histogram. @@ -18,34 +66,33 @@ def cumulative_distribution(img, nbins=256): ------- img_cdf : array Values of cumulative distribution function. - bin_edges : array - Bin edges for cdf. Length is ``len(img_cdf) + 1``. + bin_centers : array + Centers of bins. References ---------- .. [1] http://en.wikipedia.org/wiki/Cumulative_distribution_function """ - hist, bin_edges = np.histogram(img.flat, nbins, density=True) + hist, bin_centers = histogram(image, nbins) img_cdf = hist.cumsum() - return img_cdf, bin_edges + img_cdf = img_cdf / float(img_cdf[-1]) + return img_cdf, bin_centers -def equalize_hist(img, nbins=256, max_intensity=255): +def equalize_hist(image, nbins=256): """Return image after histogram equalization. Parameters ---------- - img : array + image : array Image array. nbins : int Number of bins for image histogram. - max_intensity : int - Maximum intensity of the returned image. Returns ------- - out : array + out : float array Image array after histogram equalization. Notes @@ -58,8 +105,8 @@ def equalize_hist(img, nbins=256, max_intensity=255): .. [2] http://en.wikipedia.org/wiki/Histogram_equalization """ - cdf, bin_edges = cumulative_distribution(img, nbins) - cdf = max_intensity * cdf / cdf[-1] - out = np.interp(img.flat, bin_edges[:-1], cdf) - return out.reshape(img.shape) + 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..b2d19b8a --- /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_hist_ubyte(): + img_eq = exposure.equalize_hist(test_img) + + cdf, bin_edges = exposure.cumulative_distribution(img_eq) + check_cdf_slope(cdf) + + +def test_equalize_hist_float(): + img = skimage.img_as_float(test_img) + img_eq = exposure.equalize_hist(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 -