Merge pull request #1073 from tonysyu/feature/adapt_rgb

Add `adapt_rgb` decorator and helpers
This commit is contained in:
Juan Nunez-Iglesias
2014-07-18 08:27:57 -05:00
4 changed files with 181 additions and 34 deletions
+78
View File
@@ -0,0 +1,78 @@
import functools
import numpy as np
from skimage import color
from skimage.util.dtype import convert
__all__ = ['adapt_rgb', 'hsv_value', 'each_channel']
def is_rgb_like(image):
"""Return True if the image *looks* like it's RGB.
This function should not be public because it is only intended to be used
for functions that don't accept volumes as input, since checking an image's
shape is fragile.
"""
return (image.ndim == 3) and (image.shape[2] in (3, 4))
def adapt_rgb(apply_to_rgb):
"""Return decorator that adapts to RGB images to a gray-scale filter.
This function is only intended to be used for functions that don't accept
volumes as input, since checking an image's shape is fragile.
Parameters
----------
apply_to_rgb : function
Function that returns a filtered image from an image-filter and RGB
image. This will only be called if the image is RGB-like.
"""
def decorator(image_filter):
@functools.wraps(image_filter)
def image_filter_adapted(image, *args, **kwargs):
if is_rgb_like(image):
return apply_to_rgb(image_filter, image, *args, **kwargs)
else:
return image_filter(image, *args, **kwargs)
return image_filter_adapted
return decorator
def hsv_value(image_filter, image, *args, **kwargs):
"""Return color image by applying `image_filter` on HSV-value of `image`.
Note that this function is intended for use with `adapt_rgb`.
Parameters
----------
image_filter : function
Function that filters a gray-scale image.
image : array
Input image. Note that RGBA images are treated as RGB.
"""
# Slice the first three channels so that we remove any alpha channels.
hsv = color.rgb2hsv(image[:, :, :3])
value = hsv[:, :, 2].copy()
value = image_filter(value, *args, **kwargs)
hsv[:, :, 2] = convert(value, hsv.dtype)
return color.hsv2rgb(hsv)
def each_channel(image_filter, image, *args, **kwargs):
"""Return color image by applying `image_filter` on channels of `image`.
Note that this function is intended for use with `adapt_rgb`.
Parameters
----------
image_filter : function
Function that filters a gray-scale image.
image : array
Input image.
"""
c_new = [image_filter(c, *args, **kwargs) for c in image.T]
return np.array(c_new).T
+83
View File
@@ -0,0 +1,83 @@
from functools import partial
import numpy as np
from skimage import img_as_float, img_as_uint
from skimage import color, data, filter
from skimage.color.adapt_rgb import adapt_rgb, each_channel, hsv_value
# Down-sample image for quicker testing.
COLOR_IMAGE = data.lena()[::5, ::5]
GRAY_IMAGE = data.camera()[::5, ::5]
SIGMA = 3
smooth = partial(filter.gaussian_filter, sigma=SIGMA)
assert_allclose = partial(np.testing.assert_allclose, atol=1e-8)
@adapt_rgb(each_channel)
def edges_each(image):
return filter.sobel(image)
@adapt_rgb(each_channel)
def smooth_each(image, sigma):
return filter.gaussian_filter(image, sigma)
@adapt_rgb(hsv_value)
def edges_hsv(image):
return filter.sobel(image)
@adapt_rgb(hsv_value)
def smooth_hsv(image, sigma):
return filter.gaussian_filter(image, sigma)
@adapt_rgb(hsv_value)
def edges_hsv_uint(image):
return img_as_uint(filter.sobel(image))
def test_gray_scale_image():
# We don't need to test both `hsv_value` and `each_channel` since
# `adapt_rgb` is handling gray-scale inputs.
assert_allclose(edges_each(GRAY_IMAGE), filter.sobel(GRAY_IMAGE))
def test_each_channel():
filtered = edges_each(COLOR_IMAGE)
for i, channel in enumerate(np.rollaxis(filtered, axis=-1)):
expected = img_as_float(filter.sobel(COLOR_IMAGE[:, :, i]))
assert_allclose(channel, expected)
def test_each_channel_with_filter_argument():
filtered = smooth_each(COLOR_IMAGE, SIGMA)
for i, channel in enumerate(np.rollaxis(filtered, axis=-1)):
assert_allclose(channel, smooth(COLOR_IMAGE[:, :, i]))
def test_hsv_value():
filtered = edges_hsv(COLOR_IMAGE)
value = color.rgb2hsv(COLOR_IMAGE)[:, :, 2]
assert_allclose(color.rgb2hsv(filtered)[:, :, 2], filter.sobel(value))
def test_hsv_value_with_filter_argument():
filtered = smooth_hsv(COLOR_IMAGE, SIGMA)
value = color.rgb2hsv(COLOR_IMAGE)[:, :, 2]
assert_allclose(color.rgb2hsv(filtered)[:, :, 2], smooth(value))
def test_hsv_value_with_non_float_output():
# Since `rgb2hsv` returns a float image and the result of the filtered
# result is inserted into the HSV image, we want to make sure there isn't
# a dtype mismatch.
filtered = edges_hsv_uint(COLOR_IMAGE)
filtered_value = color.rgb2hsv(filtered)[:, :, 2]
value = color.rgb2hsv(COLOR_IMAGE)[:, :, 2]
# Reduce tolerance because dtype conversion.
assert_allclose(filtered_value, filter.sobel(value), rtol=1e-5, atol=1e-5)
+3 -15
View File
@@ -15,7 +15,7 @@ comes with no guarantee.
"""
import numpy as np
import skimage
from skimage import color
from skimage.color.adapt_rgb import adapt_rgb, hsv_value
from skimage.exposure import rescale_intensity
from skimage.util import view_as_blocks
@@ -25,6 +25,7 @@ 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.
@@ -65,25 +66,12 @@ def equalize_adapthist(image, ntiles_x=8, ntiles_y=8, clip_limit=0.01,
.. [1] http://tog.acm.org/resources/GraphicsGems/gems.html#gemsvi
.. [2] https://en.wikipedia.org/wiki/CLAHE#CLAHE
"""
ndim = image.ndim
if ndim == 3:
if image.shape[2] == 4:
image = image[:, :, :3]
image = skimage.img_as_float(image)
image = rescale_intensity(image)
hsv_img = color.rgb2hsv(image)
image = hsv_img[:, :, 2].copy()
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)
if ndim == 3:
hsv_img[:, :, 2] = rescale_intensity(image)
image = color.hsv2rgb(hsv_img)
else:
image = rescale_intensity(image)
return image
return rescale_intensity(image)
def _clahe(image, ntiles_x, ntiles_y, clip_limit, nbins=128):
+17 -19
View File
@@ -11,7 +11,6 @@ from skimage.exposure.exposure import intensity_range
from skimage.color import rgb2gray
from skimage.util.dtype import dtype_range
import matplotlib.pyplot as plt
# Test histogram equalization
# ===========================
@@ -146,8 +145,8 @@ def test_rescale_uint14_limits():
# ====================================
def test_adapthist_scalar():
'''Test a scalar uint8 image
'''
"""Test a scalar uint8 image
"""
img = skimage.img_as_ubyte(data.moon())
adapted = exposure.equalize_adapthist(img, clip_limit=0.02)
assert adapted.min() == 0.0
@@ -163,8 +162,8 @@ def test_adapthist_scalar():
def test_adapthist_grayscale():
'''Test a grayscale float image
'''
"""Test a grayscale float image
"""
img = skimage.img_as_float(data.lena())
img = rgb2gray(img)
img = np.dstack((img, img, img))
@@ -172,14 +171,14 @@ 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), 104.307, 3)
assert_almost_equal(peak_snr(img, adapted), 104.3277, 3)
assert_almost_equal(norm_brightness_err(img, adapted), 0.0265, 3)
return data, adapted
def test_adapthist_color():
'''Test an RGB color uint16 image
'''
"""Test an RGB color uint16 image
"""
img = skimage.img_as_uint(data.lena())
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
@@ -192,15 +191,14 @@ def test_adapthist_color():
assert adapted.max() == 1.0
assert img.shape == adapted.shape
full_scale = skimage.exposure.rescale_intensity(img)
assert_almost_equal(peak_snr(full_scale, adapted), 105.50517, 3)
assert_almost_equal(norm_brightness_err(full_scale, adapted),
0.0544, 3)
assert_almost_equal(peak_snr(full_scale, adapted), 106.9, 1)
assert_almost_equal(norm_brightness_err(full_scale, adapted), 0.05, 2)
return data, adapted
def test_adapthist_alpha():
'''Test an RGBA color image
'''
"""Test an RGBA color image
"""
img = skimage.img_as_float(data.lena())
alpha = np.ones((img.shape[0], img.shape[1]), dtype=float)
img = np.dstack((img, alpha))
@@ -210,12 +208,12 @@ def test_adapthist_alpha():
full_scale = skimage.exposure.rescale_intensity(img)
assert img.shape == adapted.shape
assert_almost_equal = np.testing.assert_almost_equal
assert_almost_equal(peak_snr(full_scale, adapted), 105.50198, 3)
assert_almost_equal(norm_brightness_err(full_scale, adapted), 0.0544, 3)
assert_almost_equal(peak_snr(full_scale, adapted), 106.86, 2)
assert_almost_equal(norm_brightness_err(full_scale, adapted), 0.0509, 3)
def peak_snr(img1, img2):
'''Peak signal to noise ratio of two images
"""Peak signal to noise ratio of two images
Parameters
----------
@@ -226,7 +224,7 @@ def peak_snr(img1, img2):
-------
peak_snr : float
Peak signal to noise ratio
'''
"""
if img1.ndim == 3:
img1, img2 = rgb2gray(img1.copy()), rgb2gray(img2.copy())
img1 = skimage.img_as_float(img1)
@@ -237,7 +235,7 @@ def peak_snr(img1, img2):
def norm_brightness_err(img1, img2):
'''Normalized Absolute Mean Brightness Error between two images
"""Normalized Absolute Mean Brightness Error between two images
Parameters
----------
@@ -248,7 +246,7 @@ def norm_brightness_err(img1, img2):
-------
norm_brightness_error : float
Normalized absolute mean brightness error
'''
"""
if img1.ndim == 3:
img1, img2 = rgb2gray(img1), rgb2gray(img2)
ambe = np.abs(img1.mean() - img2.mean())