Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Channel roll #558

Merged
merged 4 commits into from
Feb 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions menpo/image/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from warnings import warn

import numpy as np
import scipy.linalg
import PIL.Image as PILImage

from menpo.compatibility import basestring
Expand All @@ -15,6 +14,10 @@
from .extract_patches import extract_patches


# Cache the greyscale luminosity coefficients as they are invariant.
_greyscale_luminosity_coef = None


class ImageBoundaryError(ValueError):
r"""
Exception that is thrown when an attempt is made to crop an image beyond
Expand Down Expand Up @@ -84,7 +87,7 @@ def channels_to_back(image):
else:
pixels = image.pixels

return np.rollaxis(pixels, 0, pixels.ndim)
return np.ascontiguousarray(np.rollaxis(pixels, 0, pixels.ndim))


class Image(Vectorizable, Landmarkable, Viewable, LandmarkableViewable):
Expand Down Expand Up @@ -1682,21 +1685,22 @@ def as_greyscale(self, mode='luminosity', channel=None):
raise ValueError("The 'luminosity' mode only works on RGB"
"images. {} channels found, "
"3 expected.".format(self.n_channels))

# Invert the transformation matrix to get more precise values
T = scipy.linalg.inv(np.array([[1.0, 0.956, 0.621],
[1.0, -0.272, -0.647],
[1.0, -1.106, 1.703]]))
coef = T[0, :]
pixels = np.rollaxis(greyscale.pixels, 0, self.n_dims+1)
pixels = np.dot(pixels, coef.T)
# Only compute the coefficients once.
global _greyscale_luminosity_coef
if _greyscale_luminosity_coef is None:
_greyscale_luminosity_coef = np.linalg.inv(
np.array([[1.0, 0.956, 0.621],
[1.0, -0.272, -0.647],
[1.0, -1.106, 1.703]]))[0, :]
pixels = np.einsum('i,ikl->kl', _greyscale_luminosity_coef,
greyscale.pixels)
elif mode == 'average':
pixels = np.mean(greyscale.pixels, axis=0)
elif mode == 'channel':
if channel is None:
raise ValueError("For the 'channel' mode you have to provide"
" a channel index")
pixels = greyscale.pixels[channel, ...].copy()
pixels = greyscale.pixels[channel, ...]
else:
raise ValueError("Unknown mode {} - expected 'luminosity', "
"'average' or 'channel'.".format(mode))
Expand Down Expand Up @@ -1759,6 +1763,20 @@ def as_PILImage(self):
raise ValueError('Unexpected data type - {}.'.format(pixels.dtype))
return PILImage.fromarray(pixels)

def rolled_channels(self):
r"""
Returns the pixels matrix, with the channels rolled to the back axis.
This may be required for interacting with external code bases that
require images to have channels as the last axis, rather than the
menpo convention of channels as the first axis.

Returns
-------
rolled_channels : `ndarray`
Pixels with channels as the back (last) axis.
"""
return channels_to_back(self)

def __str__(self):
return ('{} {}D Image with {} channel{}'.format(
self._str_shape, self.n_dims, self.n_channels,
Expand Down
15 changes: 13 additions & 2 deletions menpo/image/test/image_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,17 +577,28 @@ def test_resize():


def test_as_greyscale_luminosity():
image = MaskedImage(np.ones([3, 120, 120]))
ones = np.ones([3, 120, 120])
image = MaskedImage(ones)
image.pixels[0] *= 0.5
new_image = image.as_greyscale(mode='luminosity')
assert (new_image.shape == image.shape)
assert (new_image.n_channels == 1)
assert_allclose(new_image.pixels[0], ones[0] * 0.850532)

def test_rolled_channels():
ones = np.ones([3, 120, 120])
image = MaskedImage(ones)
rolled_channels = image.rolled_channels()
assert rolled_channels.shape == (120, 120, 3)

def test_as_greyscale_average():
image = MaskedImage(np.ones([3, 120, 120]))
ones = np.ones([3, 120, 120])
image = MaskedImage(ones)
image.pixels[0] *= 0.5
new_image = image.as_greyscale(mode='average')
assert (new_image.shape == image.shape)
assert (new_image.n_channels == 1)
assert_allclose(new_image.pixels[0], ones[0] * 0.83333333)


@raises(ValueError)
Expand Down