mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-19 11:27:45 +08:00
Merge pull request #1073 from tonysyu/feature/adapt_rgb
Add `adapt_rgb` decorator and helpers
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user