Add adapt_rgb decorator and helpers.

This applies `adapt_rgb` to `equalize_adapthist` and removes the
special-casing of RGB images in that function. Note that tests of
`adapt_rgb` fail because some type conversion and intensity scaling
were added to pass tests for `equalize_adapthist`.
This commit is contained in:
Tony S Yu
2014-07-16 23:13:04 -05:00
parent 938955c971
commit c24adec714
4 changed files with 169 additions and 15 deletions
+83
View File
@@ -0,0 +1,83 @@
import functools
import numpy as np
from skimage import img_as_float
from skimage import color
from skimage.exposure import rescale_intensity
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
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.
"""
# XXX: Are these 2 lines really necessary?
image = img_as_float(image[:, :, :3])
image = rescale_intensity(image)
# Slice the first three channels so that we remove any alpha channels.
hsv = color.rgb2hsv(image)
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 -14
View File
@@ -16,6 +16,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 +26,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 +67,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):
-1
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
# ===========================