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 1/6] 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) From 731356763eb3f62e44fe58b48316fdfe05c60a1a Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Dec 2015 00:39:11 -0800 Subject: [PATCH 2/6] Draw daisy features using anti-aliasing (closes #1665) --- skimage/feature/_daisy.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/skimage/feature/_daisy.py b/skimage/feature/_daisy.py index 6dca0c59..df5cf4f4 100644 --- a/skimage/feature/_daisy.py +++ b/skimage/feature/_daisy.py @@ -181,20 +181,20 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, for i in range(descs.shape[0]): for j in range(descs.shape[1]): # Draw center histogram sigma - color = (1, 0, 0) + color = [1, 0, 0] desc_y = i * step + radius desc_x = j * step + radius - coords = draw.circle_perimeter(desc_y, desc_x, int(sigmas[0])) - draw.set_color(descs_img, coords, color) + rows, cols, val = draw.circle_perimeter_aa(desc_y, desc_x, int(sigmas[0])) + draw.set_color(descs_img, (rows, cols), color, alpha=val) max_bin = np.max(descs[i, j, :]) for o_num, o in enumerate(orientation_angles): # Draw center histogram bins bin_size = descs[i, j, o_num] / max_bin dy = sigmas[0] * bin_size * sin(o) dx = sigmas[0] * bin_size * cos(o) - coords = draw.line(desc_y, desc_x, int(desc_y + dy), - int(desc_x + dx)) - draw.set_color(descs_img, coords, color) + rows, cols, val = draw.line_aa(desc_y, desc_x, int(desc_y + dy), + int(desc_x + dx)) + draw.set_color(descs_img, (rows, cols), color, alpha=val) for r_num, r in enumerate(ring_radii): color_offset = float(1 + r_num) / rings color = (1 - color_offset, 1, color_offset) @@ -202,9 +202,9 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, # Draw ring histogram sigmas hist_y = desc_y + int(round(r * sin(t))) hist_x = desc_x + int(round(r * cos(t))) - coords = draw.circle_perimeter(hist_y, hist_x, - int(sigmas[r_num + 1])) - draw.set_color(descs_img, coords, color) + rows, cols, val = draw.circle_perimeter_aa(hist_y, hist_x, + int(sigmas[r_num + 1])) + draw.set_color(descs_img, (rows, cols), color, alpha=val) for o_num, o in enumerate(orientation_angles): # Draw histogram bins bin_size = descs[i, j, orientations + r_num * @@ -213,10 +213,10 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, bin_size /= max_bin dy = sigmas[r_num + 1] * bin_size * sin(o) dx = sigmas[r_num + 1] * bin_size * cos(o) - coords = draw.line(hist_y, hist_x, - int(hist_y + dy), - int(hist_x + dx)) - draw.set_color(descs_img, coords, color) + rows, cols, val = draw.line_aa(hist_y, hist_x, + int(hist_y + dy), + int(hist_x + dx)) + draw.set_color(descs_img, (rows, cols), color, alpha=val) return descs, descs_img else: return descs From c8ba77d5f93467a3a0cf1fb2ab82ca515c4d23f4 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 23 Jan 2016 17:07:03 -0800 Subject: [PATCH 3/6] Clean up implementation --- skimage/draw/_draw.pyx | 5 +--- skimage/draw/draw.py | 45 +++++++++++++-------------------- skimage/draw/tests/test_draw.py | 3 --- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index c25183a5..0d384a38 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -35,10 +35,7 @@ def _coords_inside_image(rr, cc, shape, val=None): 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] + 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 498183ae..325137ed 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, alpha=None): +def set_color(img, coords, color, alpha=1): """Set pixel color in the image at the given coordinates. Coordinates that exceed the shape of the image will be ignored. @@ -168,36 +168,25 @@ def set_color(img, coords, color, alpha=None): """ rr, cc = coords - if alpha is None: + if img.ndim == 2: + img = img[..., np.newaxis] + + color = np.array(color, ndmin=1, copy=False) + + if img.shape[-1] != color.shape[-1]: + raise ValueError('Color shape ({}) must match last ' + 'image dimension ({}).'.format(color.shape[0], + img.shape[-1])) + + if np.isscalar(alpha): rr, cc = _coords_inside_image(rr, cc, img.shape) + alpha = np.ones_like(rr) * alpha 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])) + alpha = alpha[..., np.newaxis] - 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] + color = color * alpha + vals = img[rr, cc] * (1 - alpha) - - # 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] + img[rr, cc] = vals + color diff --git a/skimage/draw/tests/test_draw.py b/skimage/draw/tests/test_draw.py index bd295f17..214f09b0 100644 --- a/skimage/draw/tests/test_draw.py +++ b/skimage/draw/tests/test_draw.py @@ -31,9 +31,6 @@ def test_set_color_with_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) From 772dc48337874a6b426e544d04785669de0120a1 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Wed, 27 Jan 2016 14:28:40 -0800 Subject: [PATCH 4/6] Simplify alpha vector generation --- skimage/draw/draw.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/skimage/draw/draw.py b/skimage/draw/draw.py index 325137ed..862e4c3c 100644 --- a/skimage/draw/draw.py +++ b/skimage/draw/draw.py @@ -179,10 +179,9 @@ def set_color(img, coords, color, alpha=1): img.shape[-1])) if np.isscalar(alpha): - rr, cc = _coords_inside_image(rr, cc, img.shape) - alpha = np.ones_like(rr) * alpha - else: - rr, cc, alpha = _coords_inside_image(rr, cc, img.shape, val=alpha) + alpha = np.full_like(rr, alpha, dtype=float) + + rr, cc, alpha = _coords_inside_image(rr, cc, img.shape, val=alpha) alpha = alpha[..., np.newaxis] From 31b6adb778ea7f5202279fd76e2cb14c84428b03 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 28 Jan 2016 11:24:29 -0800 Subject: [PATCH 5/6] Bump numpy requirement to 1.7.2 --- DEPENDS.txt | 4 ++-- requirements.txt | 2 +- skimage/util/dtype.py | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/DEPENDS.txt b/DEPENDS.txt index 2c2aeb7d..fc6d6679 100644 --- a/DEPENDS.txt +++ b/DEPENDS.txt @@ -1,7 +1,7 @@ Build Requirements ------------------ * `Python >= 2.6 `__ -* `Numpy >= 1.6.1 `__ +* `Numpy >= 1.7.2 `__ * `Cython >= 0.21 `__ * `Six >=1.4 `__ * `SciPy >=0.9 `__ @@ -9,7 +9,7 @@ Build Requirements Runtime requirements -------------------- * `Python >= 2.6 `__ -* `Numpy >= 1.6.1 `__ +* `Numpy >= 1.7.2 `__ * `SciPy >= 0.9 `__ * `Matplotlib >= 1.1.0 `__ * `NetworkX >= 1.8 `__ diff --git a/requirements.txt b/requirements.txt index 19c541f5..98c1b49a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ matplotlib>=1.1.0 -numpy>=1.6.1 +numpy>=1.7.2 scipy>=0.9.0 six>=1.4 networkx>=1.8 diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 077b14be..a0ff637e 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -25,9 +25,8 @@ _supported_types = (np.bool_, np.bool8, np.int8, np.int16, np.int32, np.int64, np.float32, np.float64) -if np.__version__ >= "1.6.0": - dtype_range[np.float16] = (-1, 1) - _supported_types += (np.float16, ) +dtype_range[np.float16] = (-1, 1) +_supported_types += (np.float16, ) def dtype_limits(image, clip_negative=True): From 6d103b5a7ff96f6f40598a5cd1382c1b57201531 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sun, 31 Jan 2016 09:43:28 -0800 Subject: [PATCH 6/6] Revert back usage of full_like to support older numpy --- skimage/draw/draw.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skimage/draw/draw.py b/skimage/draw/draw.py index 862e4c3c..1624b115 100644 --- a/skimage/draw/draw.py +++ b/skimage/draw/draw.py @@ -179,8 +179,10 @@ def set_color(img, coords, color, alpha=1): img.shape[-1])) if np.isscalar(alpha): - alpha = np.full_like(rr, alpha, dtype=float) - + # Can be replaced by ``full_like`` when numpy 1.8 becomes + # minimum dependency + alpha = np.ones_like(rr) * alpha + rr, cc, alpha = _coords_inside_image(rr, cc, img.shape, val=alpha) alpha = alpha[..., np.newaxis]