Files
scikit-image/skimage/exposure/_adapthist.py
T
blink1073 b6dcf3c336 Update Travis build to use Anaconda.
Update Travis build to use Anaconda

Travis updates and fixes

More travis fixes

Another travis attempt

Revert changes

Use PIL and Pillow

Refactor travis into 4 different builds

Fix activation error

Remove explicit mpl in build_versions.py

Make matplotlib an explicit requirement

Rearrange travis

Make pillow a hard requirement

Try again to make Pillow optional

Fix bash syntax error

Fix bash syntax error

Bump required cython version

More rearrangments

Remove mpl from build_versions, rearrange travis

Fix version check

Make matplotlib explicit again

Conda install into test env

Check for proper install

Allow tests to skip if networkx is not available

Allow tests to skip if networkx is not available

Try swapping pillow for matplotlib

Allow tests to pass when matplotlib is not present

Remove matplotlib from build_versions

Print PIL version

Get pillow from PIP

Allow tests to skip if matplotlib is not present.

Allow tests to skip if networkx is not present.

travis fix

Remove unused mpl import that caused test error

Use nose-cov and do not run doctests without optional libs

Bump required numpy version and fix nose calls

Make overlay test repeatable

bump numpy version again

Move low-end numpy to python 2.7

Play with minimum versions

Add version requirements and use functions

Add version requirements and use functions

Allow require to skip a test

More implementation of require decorator

Update require decorator and clean up tests

Only use requires decorator when needed

Fix python3 error in version_requirements

Fix build errors

Fix handling of require with tests

More fixes for require handler

Use latest miniconda

Fix more build errors

Fix another dict comprehension and travis file.

Fix missing imports

Fix dictionary again

Fix import warning

Fix last failing test on 2.6

Skip doc examples on python2.6

Do not run doctests on python2.6

Fix typo in travis.yml

Make numpy-1.6 compatibility changes

Use numpy-1.6 in travis python2.6

Add tests for version requirements

Fix line noise in PR

Add additional io plugins

Fix simpleitk test.

Fix python 3 error in freeimage_plugin.

Install imread in Travis.

Put matplotlib settings in XDG recommended directory

Fix formatting in travis yml

Fix formatting in travis yml

Make sure to close PIL file atexit

Fix name of apt package xcftools

Fix pil fp closing

Fix matplotlibrc creation

Only download SimpleITK on py2x, run coverage on py27

Fix travis yml syntax error

Run coveralls on py2.7

Install SimpleITK on py3.3 and run coverage on py3.3

Make simpleitk install quiet

Use standard nose and clean up incantation

Fix travis yml syntax error

Put in miniconda workout for libc error.

Fix imread plugin.

Fix travis syntax

Remove unused import

Remove miniconda libpng in favor of system png

Fix imread install and move libm removal to after optional pkg install.

Fix png header copy in travis yml

Another attempt to use png headers

Debug freeimage

Add jpeg library for freeimage and debug imread.

More debug for imread and freeimage

More freeimage and imread debugging

More debugging

Use correct paths for test env

Make sure imread is tied to libpng15

Add a TODO note for simpleitk test causing error.

Fix typo in yml

Cleanup and add more comments to travis yml

Update comment

Try and add 3.2 support.

Docstring formatting

Add more travis comments.

Try numpy 1.6 on python 2.7

Fix travis syntax error

Rename CONDA to ENV for clarity

Alias python on python 3.2

Use python 3.2 as the system python

Clean up libfreeimage install

Fix order on py3.2 pre_install

Move old numpy back to py26

Use the appropriate python calls.

Debug 3.2 build.

Update comment

Fix syntax error

Another fix for syntax error.

Install scipy after downloading import tools

More debugging for py32

Do not install conda on py3.2 (duh)

Fix typo in travis yml

Fix py32 qt install, separate pyfits and imread to find error

Fix syntax error and front-load option lib check for debug

pyfits is not supported in py3.2, try imread now

imread is also not supported on py3.2

install imread before pyfits to show relationship with libs

Make pip builds quiet

Minor formatting to retrigger build

Allow simpleitk to fail to download without breaking the build

Use travis_retry for SimpleITK

See what breaks when we keep libm in

Now remove libm again
2014-08-02 06:47:09 -05:00

332 lines
11 KiB
Python

"""
Adapted code from "Contrast Limited Adaptive Histogram Equalization" by Karel
Zuiderveld <karel@cv.ruu.nl>, Graphics Gems IV, Academic Press, 1994.
http://tog.acm.org/resources/GraphicsGems/gems.html#gemsvi
The Graphics Gems code is copyright-protected. In other words, you cannot
claim the text of the code as your own and resell it. Using the code is
permitted in any program, product, or library, non-commercial or commercial.
Giving credit is not required, though is a nice gesture. The code comes as-is,
and if there are any flaws or problems with any Gems code, nobody involved with
Gems - authors, editors, publishers, or webmasters - are to be held
responsible. Basically, don't be a jerk, and remember that anything free
comes with no guarantee.
"""
import numpy as np
import skimage
from skimage.color.adapt_rgb import adapt_rgb, hsv_value
from skimage.exposure import rescale_intensity
from skimage.util import view_as_blocks, pad
MAX_REG_X = 16 # max. # contextual regions in x-direction */
MAX_REG_Y = 16 # max. # contextual regions in y-direction */
NR_OF_GREY = 2**14 # number of grayscale levels to use in CLAHE algorithm
@adapt_rgb(hsv_value)
def equalize_adapthist(image, ntiles_x=8, ntiles_y=8, clip_limit=0.01,
nbins=256):
"""Contrast Limited Adaptive Histogram Equalization.
Parameters
----------
image : array-like
Input image.
ntiles_x : int, optional
Number of tile regions in the X direction. Ranges between 1 and 16.
ntiles_y : int, optional
Number of tile regions in the Y direction. Ranges between 1 and 16.
clip_limit : float: optional
Clipping limit, normalized between 0 and 1 (higher values give more
contrast).
nbins : int, optional
Number of gray bins for histogram ("dynamic range").
Returns
-------
out : ndarray
Equalized image.
Notes
-----
* For color images, the following steps are performed:
- The image is converted to HSV color space
- The CLAHE algorithm is run on the V (Value) channel
- The image is converted back to RGB space and returned
* For RGBA images, the original alpha channel is removed.
* The CLAHE algorithm relies on image blocks of equal size. This may
result in extra border pixels that would not be handled. In that case,
we pad the image with a repeat of the border pixels, apply the
algorithm, and then trim the image to original size.
References
----------
.. [1] http://tog.acm.org/resources/GraphicsGems/gems.html#gemsvi
.. [2] https://en.wikipedia.org/wiki/CLAHE#CLAHE
"""
image = skimage.img_as_uint(image)
image = rescale_intensity(image, out_range=(0, NR_OF_GREY - 1))
out = _clahe(image, ntiles_x, ntiles_y, clip_limit * nbins, nbins)
image[:out.shape[0], :out.shape[1]] = out
image = skimage.img_as_float(image)
return rescale_intensity(image)
def _clahe(image, ntiles_x, ntiles_y, clip_limit, nbins=128):
"""Contrast Limited Adaptive Histogram Equalization.
Parameters
----------
image : array-like
Input image.
ntiles_x : int, optional
Number of tile regions in the X direction. Ranges between 2 and 16.
ntiles_y : int, optional
Number of tile regions in the Y direction. Ranges between 2 and 16.
clip_limit : float, optional
Normalized clipping limit (higher values give more contrast).
nbins : int, optional
Number of gray bins for histogram ("dynamic range").
Returns
-------
out : ndarray
Equalized image.
The number of "effective" greylevels in the output image is set by `nbins`;
selecting a small value (eg. 128) speeds up processing and still produce
an output image of good quality. The output image will have the same
minimum and maximum value as the input image. A clip limit smaller than 1
results in standard (non-contrast limited) AHE.
"""
ntiles_x = min(ntiles_x, MAX_REG_X)
ntiles_y = min(ntiles_y, MAX_REG_Y)
if clip_limit == 1.0:
return image # is OK, immediately returns original image.
h_inner = image.shape[0] - image.shape[0] % ntiles_y
w_inner = image.shape[1] - image.shape[1] % ntiles_x
# make the tile size divisible by 2
h_inner -= h_inner % (2 * ntiles_y)
w_inner -= w_inner % (2 * ntiles_x)
orig_shape = image.shape
width = w_inner // ntiles_x # Actual size of contextual regions
height = h_inner // ntiles_y
if h_inner != image.shape[0]:
ntiles_y += 1
if w_inner != image.shape[1]:
ntiles_x += 1
if h_inner != image.shape[1] or w_inner != image.shape[0]:
h_pad = height * ntiles_y - image.shape[0]
w_pad = width * ntiles_x - image.shape[1]
image = pad(image, ((0, h_pad), (0, w_pad)), mode='reflect')
h_inner, w_inner = image.shape
bin_size = 1 + NR_OF_GREY / nbins
lut = np.arange(NR_OF_GREY)
lut //= bin_size
img_blocks = view_as_blocks(image, (height, width))
map_array = np.zeros((ntiles_y, ntiles_x, nbins), dtype=int)
n_pixels = width * height
if clip_limit > 0.0: # Calculate actual cliplimit
clip_limit = int(clip_limit * (width * height) / nbins)
if clip_limit < 1:
clip_limit = 1
else:
clip_limit = NR_OF_GREY # Large value, do not clip (AHE)
# Calculate greylevel mappings for each contextual region
for y in range(ntiles_y):
for x in range(ntiles_x):
sub_img = img_blocks[y, x]
hist = lut[sub_img.ravel()]
hist = np.bincount(hist)
hist = np.append(hist, np.zeros(nbins - hist.size, dtype=int))
hist = clip_histogram(hist, clip_limit)
hist = map_histogram(hist, 0, NR_OF_GREY - 1, n_pixels)
map_array[y, x] = hist
# Interpolate greylevel mappings to get CLAHE image
ystart = 0
for y in range(ntiles_y + 1):
xstart = 0
if y == 0: # special case: top row
ystep = height / 2.0
yU = 0
yB = 0
elif y == ntiles_y: # special case: bottom row
ystep = height / 2.0
yU = ntiles_y - 1
yB = yU
else: # default values
ystep = height
yU = y - 1
yB = yB + 1
for x in range(ntiles_x + 1):
if x == 0: # special case: left column
xstep = width / 2.0
xL = 0
xR = 0
elif x == ntiles_x: # special case: right column
xstep = width / 2.0
xL = ntiles_x - 1
xR = xL
else: # default values
xstep = width
xL = x - 1
xR = xL + 1
mapLU = map_array[yU, xL]
mapRU = map_array[yU, xR]
mapLB = map_array[yB, xL]
mapRB = map_array[yB, xR]
xslice = np.arange(xstart, xstart + xstep)
yslice = np.arange(ystart, ystart + ystep)
interpolate(image, xslice, yslice,
mapLU, mapRU, mapLB, mapRB, lut)
xstart += xstep # set pointer on next matrix */
ystart += ystep
if image.shape != orig_shape:
image = image[:orig_shape[0], :orig_shape[1]]
return image
def clip_histogram(hist, clip_limit):
"""Perform clipping of the histogram and redistribution of bins.
The histogram is clipped and the number of excess pixels is counted.
Afterwards the excess pixels are equally redistributed across the
whole histogram (providing the bin count is smaller than the cliplimit).
Parameters
----------
hist : ndarray
Histogram array.
clip_limit : int
Maximum allowed bin count.
Returns
-------
hist : ndarray
Clipped histogram.
"""
# calculate total number of excess pixels
excess_mask = hist > clip_limit
excess = hist[excess_mask]
n_excess = excess.sum() - excess.size * clip_limit
# Second part: clip histogram and redistribute excess pixels in each bin
bin_incr = int(n_excess / hist.size) # average binincrement
upper = clip_limit - bin_incr # Bins larger than upper set to cliplimit
hist[excess_mask] = clip_limit
low_mask = hist < upper
n_excess -= hist[low_mask].size * bin_incr
hist[low_mask] += bin_incr
mid_mask = (hist >= upper) & (hist < clip_limit)
mid = hist[mid_mask]
n_excess -= mid.size * clip_limit - mid.sum()
hist[mid_mask] = clip_limit
while n_excess > 0: # Redistribute remaining excess
index = 0
while n_excess > 0 and index < hist.size:
step_size = int(hist[hist < clip_limit].size / n_excess)
step_size = max(step_size, 1)
indices = np.arange(index, hist.size, step_size)
under = hist[indices] < clip_limit
hist[under] += 1
n_excess -= hist[under].size
index += 1
return hist
def map_histogram(hist, min_val, max_val, n_pixels):
"""Calculate the equalized lookup table (mapping).
It does so by cumulating the input histogram.
Parameters
----------
hist : ndarray
Clipped histogram.
min_val : int
Minimum value for mapping.
max_val : int
Maximum value for mapping.
n_pixels : int
Number of pixels in the region.
Returns
-------
out : ndarray
Mapped intensity LUT.
"""
out = np.cumsum(hist).astype(float)
scale = ((float)(max_val - min_val)) / n_pixels
out *= scale
out += min_val
out[out > max_val] = max_val
return out.astype(int)
def interpolate(image, xslice, yslice,
mapLU, mapRU, mapLB, mapRB, lut):
"""Find the new grayscale level for a region using bilinear interpolation.
Parameters
----------
image : ndarray
Full image.
xslice, yslice : array-like
Indices of the region.
map* : ndarray
Mappings of greylevels from histograms.
lut : ndarray
Maps grayscale levels in image to histogram levels.
Returns
-------
out : ndarray
Original image with the subregion replaced.
Notes
-----
This function calculates the new greylevel assignments of pixels within
a submatrix of the image. This is done by a bilinear interpolation between
four different mappings in order to eliminate boundary artifacts.
"""
norm = xslice.size * yslice.size # Normalization factor
# interpolation weight matrices
x_coef, y_coef = np.meshgrid(np.arange(xslice.size),
np.arange(yslice.size))
x_inv_coef, y_inv_coef = x_coef[:, ::-1] + 1, y_coef[::-1] + 1
view = image[int(yslice[0]):int(yslice[-1] + 1),
int(xslice[0]):int(xslice[-1] + 1)]
im_slice = lut[view]
new = ((y_inv_coef * (x_inv_coef * mapLU[im_slice]
+ x_coef * mapRU[im_slice])
+ y_coef * (x_inv_coef * mapLB[im_slice]
+ x_coef * mapRB[im_slice]))
/ norm)
view[:, :] = new
return image