Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1073 from tonysyu/feature/adapt_rgb
Add `adapt_rgb` decorator and helpers
- Loading branch information
Showing
4 changed files
with
181 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters