From a924e55b5276dfd37d30bb505471f8fa468dc725 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Sat, 12 Jul 2014 09:40:30 -0500 Subject: [PATCH] Fix handling of border pixels and update tests --- skimage/exposure/_adapthist.py | 42 ++++++++++++------ skimage/exposure/tests/test_exposure.py | 59 ++----------------------- 2 files changed, 33 insertions(+), 68 deletions(-) diff --git a/skimage/exposure/_adapthist.py b/skimage/exposure/_adapthist.py index d583424d..c89ba35b 100644 --- a/skimage/exposure/_adapthist.py +++ b/skimage/exposure/_adapthist.py @@ -55,9 +55,10 @@ def equalize_adapthist(image, ntiles_x=8, ntiles_y=8, clip_limit=0.01, - 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 results - in extra border pixels that are not handled. Extra blocks are created - around the border to handle these pixels. + * 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 ---------- @@ -120,19 +121,36 @@ def _clahe(image, ntiles_x, ntiles_y, clip_limit, nbins=128): if clip_limit == 1.0: return image # is OK, immediately returns original image. - map_array = np.zeros((ntiles_y, ntiles_x, nbins), dtype=int) - y_res = image.shape[0] - image.shape[0] % ntiles_y x_res = image.shape[1] - image.shape[1] % ntiles_x + # make the tile size divisible by 2 while y_res % (2 * ntiles_y): y_res -= 1 while x_res % (2 * ntiles_x): x_res -= 1 - image = image[: y_res, : x_res] - x_size = image.shape[1] // ntiles_x # Actual size of contextual regions - y_size = image.shape[0] // ntiles_y + orig_shape = image.shape + x_size = x_res // ntiles_x # Actual size of contextual regions + y_size = y_res // ntiles_y + + if y_res != image.shape[0]: + ntiles_y += 1 + if x_res != image.shape[1]: + ntiles_x += 1 + if y_res != image.shape[1] or x_res != image.shape[0]: + hgt = y_size * ntiles_y - image.shape[0] + wid = x_size * ntiles_x - image.shape[1] + image = np.vstack((image, image[-hgt:, :])) + image = np.hstack((image, image[:, -wid:])) + y_res, x_res = image.shape + + bin_size = 1 + NR_OF_GREY / nbins + aLUT = np.arange(NR_OF_GREY) + aLUT //= bin_size + img_blocks = view_as_blocks(image, (y_size, x_size)) + + map_array = np.zeros((ntiles_y, ntiles_x, nbins), dtype=int) n_pixels = x_size * y_size if clip_limit > 0.0: # Calculate actual cliplimit @@ -142,11 +160,6 @@ def _clahe(image, ntiles_x, ntiles_y, clip_limit, nbins=128): else: clip_limit = NR_OF_GREY # Large value, do not clip (AHE) - bin_size = 1 + NR_OF_GREY / nbins - aLUT = np.arange(NR_OF_GREY) - aLUT //= bin_size - img_blocks = view_as_blocks(image, (y_size, x_size)) - # Calculate greylevel mappings for each contextual region for y in range(ntiles_y): for x in range(ntiles_x): @@ -203,6 +216,9 @@ def _clahe(image, ntiles_x, ntiles_y, clip_limit, nbins=128): ystart += ystep + if image.shape != orig_shape: + image = image[:orig_shape[0], :orig_shape[1]] + return image diff --git a/skimage/exposure/tests/test_exposure.py b/skimage/exposure/tests/test_exposure.py index 3ee2a492..bb584f1a 100644 --- a/skimage/exposure/tests/test_exposure.py +++ b/skimage/exposure/tests/test_exposure.py @@ -16,6 +16,8 @@ import matplotlib.pyplot as plt # Test histogram equalization # =========================== +np.random.seed(0) + # squeeze image intensities to lower image contrast test_img = skimage.img_as_float(data.camera()) test_img = exposure.rescale_intensity(test_img / 5. + 100) @@ -170,8 +172,8 @@ def test_adapthist_grayscale(): nbins=128) assert_almost_equal = np.testing.assert_almost_equal assert img.shape == adapted.shape - assert_almost_equal(peak_snr(img, adapted), 105.669, 3) - assert_almost_equal(norm_brightness_err(img, adapted), 0.02470, 3) + assert_almost_equal(peak_snr(img, adapted), 104.3168, 3) + assert_almost_equal(norm_brightness_err(img, adapted), 0.0265, 3) return data, adapted @@ -212,59 +214,6 @@ def test_adapthist_alpha(): assert_almost_equal(norm_brightness_err(full_scale, adapted), 0.0544, 3) -def test_adapthist_modes_scalar(): - '''Test adaptist `mode` parameter values for ndim==2 image. - ''' - img = skimage.img_as_float(data.moon()) - full_scale = skimage.exposure.rescale_intensity(skimage.img_as_uint(img)) - ignore = exposure.equalize_adapthist(img.copy(), ntiles_x=9, ntiles_y=9) - zero = exposure.equalize_adapthist(img.copy(), ntiles_x=9, ntiles_y=9, - mode='zero') - crop = exposure.equalize_adapthist(img.copy(), ntiles_x=9, ntiles_y=9, - mode='crop') - assert ignore.shape == zero.shape - assert ignore.shape != crop.shape - assert_array_equal(zero[crop.shape[0]:, :], 0) - assert_array_equal(zero[:, crop.shape[1]:], 0) - - assert_almost_equal = np.testing.assert_almost_equal - full_cropped = full_scale[:crop.shape[0], :crop.shape[1]] - zero_cropped = zero[:crop.shape[0], :crop.shape[1]] - ignore_cropped = ignore[:crop.shape[0], :crop.shape[1]] - - for crop_img in [ignore_cropped, zero_cropped, crop]: - assert_almost_equal(peak_snr(full_cropped, crop_img), 120.456, 3) - assert_almost_equal(norm_brightness_err(full_cropped, crop_img), - 0.4398, 3) - - -def test_adapthist_modes_rgb(): - '''Test adaptist `mode` parameter values for rgb image. - ''' - img = skimage.img_as_float(data.lena()) - full_scale = skimage.exposure.rescale_intensity(skimage.img_as_uint(img)) - ignore = exposure.equalize_adapthist(img.copy(), ntiles_x=9, ntiles_y=10) - zero = exposure.equalize_adapthist(img.copy(), ntiles_x=9, ntiles_y=10, - mode='zero') - crop = exposure.equalize_adapthist(img.copy(), ntiles_x=9, ntiles_y=10, - mode='crop') - assert ignore.shape == zero.shape - assert ignore.shape != crop.shape - - assert_array_equal(zero[crop.shape[0]:, :, :], 0) - assert_array_equal(zero[:, crop.shape[1]:, :], 0) - - assert_almost_equal = np.testing.assert_almost_equal - full_cropped = full_scale[:crop.shape[0], :crop.shape[1], :] - zero_cropped = zero[:crop.shape[0], :crop.shape[1], :] - ignore_cropped = ignore[:crop.shape[0], :crop.shape[1], :] - - for crop_img in [ignore_cropped, zero_cropped, crop]: - assert np.floor(peak_snr(full_cropped, crop_img)) == 106 - assert_almost_equal(norm_brightness_err(full_cropped, crop_img), - 0.0517, 3) - - def peak_snr(img1, img2): '''Peak signal to noise ratio of two images