Merge pull request #946 from synesthesiam/master

Added alpha support to novice
This commit is contained in:
Johannes Schönberger
2014-03-26 09:31:15 -04:00
2 changed files with 121 additions and 20 deletions
+76 -13
View File
@@ -1,6 +1,7 @@
import os import os
import imghdr import imghdr
from collections import namedtuple from collections import namedtuple
from io import BytesIO
import numpy as np import numpy as np
from skimage import io from skimage import io
@@ -10,6 +11,9 @@ from skimage.color import color_dict
from skimage.io.util import file_or_url_context, is_url from skimage.io.util import file_or_url_context, is_url
import six import six
from six.moves.urllib_parse import urlparse
from six.moves.urllib import request
urlopen = request.urlopen
# Convert colors from `skimage.color` to uint8 and allow access through # Convert colors from `skimage.color` to uint8 and allow access through
# dict or a named tuple. # dict or a named tuple.
@@ -72,15 +76,18 @@ class Pixel(object):
Vertical coordinate of this pixel (bottom = 0). Vertical coordinate of this pixel (bottom = 0).
rgb : tuple rgb : tuple
RGB tuple with red, green, and blue components (0-255) RGB tuple with red, green, and blue components (0-255)
alpha : int
Transparency component (0-255), 255 (opaque) by default
""" """
def __init__(self, pic, array, x, y, rgb): def __init__(self, pic, array, x, y, rgb, alpha=255):
self._picture = pic self._picture = pic
self._x = x self._x = x
self._y = y self._y = y
self._red = self._validate(rgb[0]) self._red = self._validate(rgb[0])
self._green = self._validate(rgb[1]) self._green = self._validate(rgb[1])
self._blue = self._validate(rgb[2]) self._blue = self._validate(rgb[2])
self._alpha = self._validate(alpha)
@property @property
def x(self): def x(self):
@@ -122,6 +129,16 @@ class Pixel(object):
self._blue = self._validate(value) self._blue = self._validate(value)
self._setpixel() self._setpixel()
@property
def alpha(self):
"""The transparency component of the pixel (0-255)."""
return self._alpha
@alpha.setter
def alpha(self, value):
self._alpha = self._validate(value)
self._setpixel()
@property @property
def rgb(self): def rgb(self):
"""The RGB color components of the pixel (3 values 0-255).""" """The RGB color components of the pixel (3 values 0-255)."""
@@ -129,7 +146,25 @@ class Pixel(object):
@rgb.setter @rgb.setter
def rgb(self, value): def rgb(self, value):
self._red, self._green, self._blue = (self._validate(v) for v in value) if len(value) == 4:
self.rgba = value
else:
self._red, self._green, self._blue \
= (self._validate(v) for v in value)
self._alpha = 255
self._setpixel()
@property
def rgba(self):
"""The RGB color and transparency components of the pixel
(4 values 0-255).
"""
return (self.red, self.green, self.blue, self.alpha)
@rgba.setter
def rgba(self, value):
self._red, self._green, self._blue, self._alpha \
= (self._validate(v) for v in value)
self._setpixel() self._setpixel()
def _validate(self, value): def _validate(self, value):
@@ -145,16 +180,17 @@ class Pixel(object):
return value return value
def _setpixel(self): def _setpixel(self):
self._picture.xy_array[self._x, self._y] = self.rgb # RGB + alpha
self._picture.xy_array[self._x, self._y] = self.rgba
self._picture._array_modified() self._picture._array_modified()
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, Pixel): if isinstance(other, Pixel):
return self.rgb == other.rgb return self.rgba == other.rgba
def __repr__(self): def __repr__(self):
args = self.red, self.green, self.blue args = self.red, self.green, self.blue, self.alpha
return "Pixel(red={0}, green={1}, blue={2})".format(*args) return "Pixel(red={0}, green={1}, blue={2}, alpha={3})".format(*args)
class Picture(object): class Picture(object):
@@ -165,9 +201,9 @@ class Picture(object):
path : str path : str
Path to an image file to load / URL of an image Path to an image file to load / URL of an image
array : array array : array
Raw RGB image data [0-255], with origin at top-left. Raw RGB or RGBA image data [0-255], with origin at top-left.
xy_array : array xy_array : array
Raw RGB image data [0-255], with origin at bottom-left. Raw RGB or RGBA image data [0-255], with origin at bottom-left.
Examples Examples
-------- --------
@@ -218,13 +254,17 @@ class Picture(object):
path = os.path.abspath(path) path = os.path.abspath(path)
self._path = path self._path = path
with file_or_url_context(path) as context: with file_or_url_context(path) as context:
self.array = io.imread(context) self.array = img_as_ubyte(io.imread(context))
self._format = imghdr.what(context) self._format = imghdr.what(context)
elif array is not None: elif array is not None:
self.array = array self.array = array
elif xy_array is not None: elif xy_array is not None:
self.xy_array = xy_array self.xy_array = xy_array
# Force RGBA internally (use max alpha)
if self.array.shape[-1] == 3:
self.array = np.insert(self.array, 3, values=255, axis=2)
@staticmethod @staticmethod
def from_size(size, color='black'): def from_size(size, color='black'):
"""Return a Picture of the specified size and a uniform color. """Return a Picture of the specified size and a uniform color.
@@ -234,13 +274,18 @@ class Picture(object):
size : tuple size : tuple
Width and height of the picture in pixels. Width and height of the picture in pixels.
color : tuple or str color : tuple or str
RGB tuple with the fill color for the picture [0-255] or a valid RGB or RGBA tuple with the fill color for the picture [0-255] or
key in `color_dict`. a valid key in `color_dict`.
""" """
if isinstance(color, six.string_types): if isinstance(color, six.string_types):
color = color_dict[color] color = color_dict[color]
rgb_size = tuple(size) + (3,) rgb_size = tuple(size) + (len(color),)
array = np.ones(rgb_size, dtype=np.uint8) * color array = np.ones(rgb_size, dtype=np.uint8) * color
# Force RGBA internally (use max alpha)
if array.shape[-1] == 3:
array = np.insert(array, 3, values=255, axis=2)
return Picture(array=array) return Picture(array=array)
@property @property
@@ -384,13 +429,31 @@ class Picture(object):
def blue(self, value): def blue(self, value):
self._set_channel(2, value) self._set_channel(2, value)
@property
def alpha(self):
"""The transparency component of the pixel (0-255)."""
return self._get_channel(3).ravel()
@alpha.setter
def alpha(self, value):
self._set_channel(3, value)
@property @property
def rgb(self): def rgb(self):
"""The RGB color components of the pixel (3 values 0-255).""" """The RGB color components of the pixel (3 values 0-255)."""
return self.xy_array return self.xy_array[:, :, :3]
@rgb.setter @rgb.setter
def rgb(self, value): def rgb(self, value):
self.xy_array[:, :, :3] = value
@property
def rgba(self):
"""The RGBA color components of the pixel (4 values 0-255)."""
return self.xy_array
@rgba.setter
def rgba(self, value):
self.xy_array[:] = value self.xy_array[:] = value
def __iter__(self): def __iter__(self):
+45 -7
View File
@@ -13,8 +13,12 @@ IMAGE_PATH = os.path.join(data_dir, "chelsea.png")
SMALL_IMAGE_PATH = os.path.join(data_dir, "block.png") SMALL_IMAGE_PATH = os.path.join(data_dir, "block.png")
def _array_2d_to_RGB(array): def _array_2d_to_RGBA(array):
return np.tile(array[:, :, np.newaxis], (1, 1, 3)) return np.tile(array[:, :, np.newaxis], (1, 1, 4))
def _array_2d_to_RGBA(array):
return np.tile(array[:, :, np.newaxis], (1, 1, 4))
def test_xy_to_array_origin(): def test_xy_to_array_origin():
@@ -82,6 +86,28 @@ def test_pixel_rgb():
for i, channel in enumerate((pixel.red, pixel.green, pixel.blue)): for i, channel in enumerate((pixel.red, pixel.green, pixel.blue)):
assert_equal(channel, i + 3) assert_equal(channel, i + 3)
pixel.rgb = np.arange(4)
assert_equal(pixel.rgb, np.arange(3))
def test_pixel_rgba():
pic = novice.Picture.from_size((3, 3), color=(10, 10, 10))
pixel = pic[0, 0]
pixel.rgba = np.arange(4)
assert_equal(pixel.rgba, np.arange(4))
for i, channel in enumerate((pixel.red, pixel.green, pixel.blue, pixel.alpha)):
assert_equal(channel, i)
pixel.red = 3
pixel.green = 4
pixel.blue = 5
pixel.alpha = 6
assert_equal(pixel.rgba, np.arange(4) + 3)
for i, channel in enumerate((pixel.red, pixel.green, pixel.blue, pixel.alpha)):
assert_equal(channel, i + 3)
def test_pixel_rgb_float(): def test_pixel_rgb_float():
pixel = novice.Picture.from_size((1, 1))[0, 0] pixel = novice.Picture.from_size((1, 1))[0, 0]
@@ -89,6 +115,12 @@ def test_pixel_rgb_float():
assert_equal(pixel.rgb, (1, 1, 1)) assert_equal(pixel.rgb, (1, 1, 1))
def test_pixel_rgba_float():
pixel = novice.Picture.from_size((1, 1))[0, 0]
pixel.rgba = (1.1, 1.1, 1.1, 1.1)
assert_equal(pixel.rgba, (1, 1, 1, 1))
def test_modified_on_set(): def test_modified_on_set():
pic = novice.Picture(SMALL_IMAGE_PATH) pic = novice.Picture(SMALL_IMAGE_PATH)
pic[0, 0] = (1, 1, 1) pic[0, 0] = (1, 1, 1)
@@ -161,7 +193,7 @@ def test_indexing():
def test_picture_slice(): def test_picture_slice():
array = _array_2d_to_RGB(np.arange(0, 10)[np.newaxis, :]) array = _array_2d_to_RGBA(np.arange(0, 10)[np.newaxis, :])
pic = novice.Picture(array=array) pic = novice.Picture(array=array)
x_slice = slice(3, 8) x_slice = slice(3, 8)
@@ -171,7 +203,7 @@ def test_picture_slice():
def test_move_slice(): def test_move_slice():
h, w = 3, 12 h, w = 3, 12
array = _array_2d_to_RGB(np.linspace(0, 255, h * w).reshape(h, w)) array = _array_2d_to_RGBA(np.linspace(0, 255, h * w).reshape(h, w))
array = array.astype(np.uint8) array = array.astype(np.uint8)
pic = novice.Picture(array=array) pic = novice.Picture(array=array)
@@ -191,7 +223,7 @@ def test_move_slice():
def test_negative_index(): def test_negative_index():
n = 10 n = 10
array = _array_2d_to_RGB(np.arange(0, n)[np.newaxis, :]) array = _array_2d_to_RGBA(np.arange(0, n)[np.newaxis, :])
# Test both x and y indices. # Test both x and y indices.
pic = novice.Picture(array=array) pic = novice.Picture(array=array)
assert pic[-1, 0] == pic[n - 1, 0] assert pic[-1, 0] == pic[n - 1, 0]
@@ -201,7 +233,7 @@ def test_negative_index():
def test_negative_slice(): def test_negative_slice():
n = 10 n = 10
array = _array_2d_to_RGB(np.arange(0, n)[np.newaxis, :]) array = _array_2d_to_RGBA(np.arange(0, n)[np.newaxis, :])
# Test both x and y slices. # Test both x and y slices.
pic = novice.Picture(array=array) pic = novice.Picture(array=array)
assert pic[-3:, 0] == pic[n - 3:, 0] assert pic[-3:, 0] == pic[n - 3:, 0]
@@ -211,7 +243,7 @@ def test_negative_slice():
def test_getitem_with_step(): def test_getitem_with_step():
h, w = 5, 5 h, w = 5, 5
array = _array_2d_to_RGB(np.linspace(0, 255, h * w).reshape(h, w)) array = _array_2d_to_RGBA(np.linspace(0, 255, h * w).reshape(h, w))
pic = novice.Picture(array=array) pic = novice.Picture(array=array)
sliced_pic = pic[::2, ::2] sliced_pic = pic[::2, ::2]
assert sliced_pic == novice.Picture(array=array[::2, ::2]) assert sliced_pic == novice.Picture(array=array[::2, ::2])
@@ -271,5 +303,11 @@ def test_pixel_blue_raises():
pixel.blue = 256 pixel.blue = 256
@raises(ValueError)
def test_pixel_alpha_raises():
pixel = novice.Picture.from_size((1, 1))[0, 0]
pixel.alpha = 256
if __name__ == '__main__': if __name__ == '__main__':
np.testing.run_module_suite() np.testing.run_module_suite()