From e2f874e75fd2926fc2f0492ad797b1f5cf187dc0 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Dec 2015 00:39:01 -0800 Subject: [PATCH] draw: Add alpha ability to set_color --- skimage/draw/_draw.pyx | 18 ++++++++----- skimage/draw/draw.py | 46 ++++++++++++++++++++++++++++----- skimage/draw/tests/test_draw.py | 24 +++++++++++++++-- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 3a0d3612..c25183a5 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -21,20 +21,24 @@ def _coords_inside_image(rr, cc, shape, val=None): shape : tuple Image shape which is used to determine the maximum extent of output pixel coordinates. - val : ndarray of float, optional - Values of pixels at coordinates [rr, cc]. + val : (N, D) ndarray of float, optional + Values of pixels at coordinates ``[rr, cc]``. Returns ------- - rr, cc : (N,) array of int + rr, cc : (M,) array of int Row and column indices of valid pixels (i.e. those inside `shape`). - val : (N,) array of float, optional + val : (M, D) array of float, optional Values at `rr, cc`. Returned only if `val` is given as input. """ mask = (rr >= 0) & (rr < shape[0]) & (cc >= 0) & (cc < shape[1]) - if val is not None: - return rr[mask], cc[mask], val[mask] - return rr[mask], cc[mask] + if val is None: + return rr[mask], cc[mask] + else: + if np.isscalar(val): + return rr[mask], cc[mask], val + else: + return rr[mask], cc[mask], val[mask] def line(Py_ssize_t y0, Py_ssize_t x0, Py_ssize_t y1, Py_ssize_t x1): diff --git a/skimage/draw/draw.py b/skimage/draw/draw.py index c91aca2c..498183ae 100644 --- a/skimage/draw/draw.py +++ b/skimage/draw/draw.py @@ -125,7 +125,7 @@ def circle(r, c, radius, shape=None): return ellipse(r, c, radius, radius, shape) -def set_color(img, coords, color): +def set_color(img, coords, color, alpha=None): """Set pixel color in the image at the given coordinates. Coordinates that exceed the shape of the image will be ignored. @@ -134,10 +134,13 @@ def set_color(img, coords, color): ---------- img : (M, N, D) ndarray Image - coords : ((P,) ndarray, (P,) ndarray) - Coordinates of pixels to be colored. + coords : tuple of ((P,) ndarray, (P,) ndarray) + Row and column coordinates of pixels to be colored. color : (D,) ndarray Color to be assigned to coordinates in the image. + alpha : scalar or (N,) ndarray + Alpha values used to blend color with image. 0 is transparent, + 1 is opaque. Returns ------- @@ -163,7 +166,38 @@ def set_color(img, coords, color): [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], dtype=uint8) """ - rr, cc = coords - rr, cc = _coords_inside_image(rr, cc, img.shape) - img[rr, cc] = color + + if alpha is None: + rr, cc = _coords_inside_image(rr, cc, img.shape) + else: + rr, cc, alpha = _coords_inside_image(rr, cc, img.shape, val=alpha) + + if not np.isscalar(color): + color = np.array(color) + if color.shape[0] != img.shape[-1]: + raise ValueError('Color shape ({}) must match last ' + 'image dimension ({}).'.format(color.shape[0], + img.shape[-1])) + + if alpha is None: + img[rr, cc] = color + else: + if np.isscalar(alpha) or np.isscalar(color): + color = color * alpha + else: + color = color * alpha[:, None] + + + # Strategy: try operation directly. If scalar / correctly shaped + # vector, this will work. If not (e.g. vector alpha but color image), + # try broadcasting. + try: + vals = img[rr, cc] * (1 - alpha) + except ValueError: + vals = img[rr, cc] * (1 - alpha[:, None]) + + try: + img[rr, cc] = vals + color + except ValueError: + img[rr, cc] = vals + color[:, None] diff --git a/skimage/draw/tests/test_draw.py b/skimage/draw/tests/test_draw.py index a07ca631..bd295f17 100644 --- a/skimage/draw/tests/test_draw.py +++ b/skimage/draw/tests/test_draw.py @@ -1,4 +1,4 @@ -from numpy.testing import assert_array_equal, assert_equal +from numpy.testing import assert_array_equal, assert_equal, assert_raises import numpy as np from skimage._shared.testing import test_parallel @@ -20,6 +20,24 @@ def test_set_color(): assert_array_equal(img, img_) +def test_set_color_with_alpha(): + img = np.zeros((10, 10)) + + rr, cc, alpha = line_aa(0, 0, 0, 30) + set_color(img, (rr, cc), 1, alpha=alpha) + + # Wrong dimensionality color + assert_raises(ValueError, set_color, img, (rr, cc), (255, 0, 0), alpha=alpha) + + img = np.zeros((10, 10, 3)) + + rr, cc, alpha = line_aa(0, 0, 0, 30) + set_color(img, (rr, cc), 1, alpha=alpha) + + rr, cc, alpha = line_aa(0, 0, 0, 30) + set_color(img, (rr, cc), (1, 0, 0), alpha=alpha) + + @test_parallel() def test_line_horizontal(): img = np.zeros((10, 10)) @@ -72,7 +90,7 @@ def test_line_aa_horizontal(): img = np.zeros((10, 10)) rr, cc, val = line_aa(0, 0, 0, 9) - img[rr, cc] = val + set_color(img, (rr, cc), 1, alpha=val) img_ = np.zeros((10, 10)) img_[0, :] = 1 @@ -329,12 +347,14 @@ def test_circle_perimeter_aa_shape(): img = np.zeros((15, 20), 'uint8') rr, cc, val = circle_perimeter_aa(7, 10, 9, shape=(15, 20)) img[rr, cc] = val * 255 + shift = 5 img_ = np.zeros((15 + 2 * shift, 20), 'uint8') rr, cc, val = circle_perimeter_aa(7 + shift, 10, 9, shape=None) img_[rr, cc] = val * 255 assert_array_equal(img, img_[shift:-shift, :]) + def test_ellipse_trivial(): img = np.zeros((2, 2), 'uint8') rr, cc = ellipse(0.5, 0.5, 0.5, 0.5)