diff --git a/doc/examples/plot_shapes.py b/doc/examples/plot_shapes.py index ec385640..2d957977 100644 --- a/doc/examples/plot_shapes.py +++ b/doc/examples/plot_shapes.py @@ -14,7 +14,8 @@ This example shows how to fill several different shapes: import numpy as np import matplotlib.pyplot as plt -from skimage.draw import line, polygon, circle, circle_perimeter, \ +from skimage.draw import line, polygon, circle, \ + circle_perimeter, circle_perimeter_aa, \ ellipse, ellipse_perimeter import numpy as np import math @@ -49,7 +50,7 @@ rr, cc = circle_perimeter(120, 400, 15) img[rr, cc, :] = (255, 0, 0) # anti-aliased circle -rr, cc, val = circle_perimeter(120, 400, 70, 'wu') +rr, cc, val = circle_perimeter_aa(120, 400, 70) img[rr, cc, 1] = val * 255 # ellipses diff --git a/skimage/draw/__init__.py b/skimage/draw/__init__.py index 4fb222d1..285ba49a 100644 --- a/skimage/draw/__init__.py +++ b/skimage/draw/__init__.py @@ -1,6 +1,6 @@ from .draw import circle, ellipse, set_color -from ._draw import line, polygon, ellipse_perimeter, circle_perimeter, \ - bezier_segment +from ._draw import (line, polygon, ellipse_perimeter, circle_perimeter, + circle_perimeter_aa, bezier_segment) from .draw3d import ellipsoid, ellipsoid_stats __all__ = ['line', @@ -11,4 +11,5 @@ __all__ = ['line', 'ellipsoid_stats', 'circle', 'circle_perimeter', + 'circle_perimeter_aa', 'set_color'] diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 90ba001c..f68bb2f2 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -178,7 +178,6 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, method : {'bresenham', 'andres', 'wu'}, optional bresenham : Bresenham method (default) andres : Andres method - wu : Wu's method Returns ------- @@ -188,19 +187,13 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, May be used to directly index into an array, e.g. ``img[rr, cc] = 1``. - rr, cc, val : (N,) ndarray of int - Wu's method: - Indices of pixels and intensity values. - ``img[rr, cc] = val``. - Notes ----- Andres method presents the advantage that concentric circles create a disc whereas Bresenham can make holes. There is also less distortions when Andres circles are rotated. Bresenham method is also known as midpoint circle algorithm. - Wu's method draws anti-aliased circle. This implementation doesn't use - lookup table optimization. + Anti-aliased circle generator is available with `circle_perimeter_aa`. References ---------- @@ -208,8 +201,6 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, plotter", IBM Systems journal, 4 (1965) 25-30. .. [2] E. Andres, "Discrete circles, rings and spheres", Computers & Graphics, 18 (1994) 695-706. - .. [3] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH - Computer Graphics, 25 (1991) 143-152. Examples -------- @@ -233,7 +224,6 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, cdef list rr = list() cdef list cc = list() - cdef list val = list() cdef Py_ssize_t x = 0 cdef Py_ssize_t y = radius @@ -249,62 +239,97 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, elif method == 'andres': d = radius - 1 cmethod = 'a' - elif method == 'wu': - cmethod = 'w' else: raise ValueError('Wrong method') - if cmethod == 'a' or cmethod == 'b': - while y >= x: - rr.extend([y, -y, y, -y, x, -x, x, -x]) - cc.extend([x, x, -x, -x, y, y, -y, -y]) + while y >= x: + rr.extend([y, -y, y, -y, x, -x, x, -x]) + cc.extend([x, x, -x, -x, y, y, -y, -y]) - if cmethod == 'b': - if d < 0: - d += 4 * x + 6 - else: - d += 4 * (x - y) + 10 - y -= 1 - x += 1 - elif cmethod == 'a': - if d >= 2 * (x - 1): - d = d - 2 * x - x = x + 1 - elif d <= 2 * (radius - y): - d = d + 2 * y - 1 - y = y - 1 - else: - d = d + 2 * (y - x - 1) - y = y - 1 - x = x + 1 - return (np.array(rr, dtype=np.intp) + cy, - np.array(cc, dtype=np.intp) + cx) - - elif cmethod == 'w': - dceil_prev = 0 - - rr.extend([y, x, y, x, -y, -x, -y, -x]) - cc.extend([x, y, -x, -y, x, y, -x, -y]) - val.extend([1] * 8) - - while y > x + 1: - x += 1 - dceil = math.sqrt(radius**2 - x**2) - dceil = math.ceil(dceil) - dceil - if dceil < dceil_prev: + if cmethod == 'b': + if d < 0: + d += 4 * x + 6 + else: + d += 4 * (x - y) + 10 y -= 1 - rr.extend([y, y - 1, x, x, y, y - 1, x, x]) - cc.extend([x, x, y, y - 1, -x, -x, -y, 1 - y]) + x += 1 + elif cmethod == 'a': + if d >= 2 * (x - 1): + d = d - 2 * x + x = x + 1 + elif d <= 2 * (radius - y): + d = d + 2 * y - 1 + y = y - 1 + else: + d = d + 2 * (y - x - 1) + y = y - 1 + x = x + 1 + return (np.array(rr, dtype=np.intp) + cy, + np.array(cc, dtype=np.intp) + cx) - rr.extend([-y, 1 - y, -x, -x, -y, 1 - y, -x, -x]) - cc.extend([x, x, y, y - 1, -x, -x, -y, 1 - y]) - val.extend([1 - dceil, dceil] * 8) - dceil_prev = dceil +def circle_perimeter_aa(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius): + """Generate anti-aliased circle perimeter coordinates. - return (np.array(rr, dtype=np.intp) + cy, - np.array(cc, dtype=np.intp) + cx, - np.array(val, dtype=np.float)) + Parameters + ---------- + cy, cx : int + Centre coordinate of circle. + radius: int + Radius of circle. + + Returns + ------- + rr, cc, val : (N,) ndarray of int + Indices of pixels (`rr`, `cc`) and intensity values (`val`). + ``img[rr, cc] = val``. + + Notes + ----- + Wu's method draws anti-aliased circle. This implementation doesn't use + lookup table optimization. + + References + ---------- + .. [1] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH + Computer Graphics, 25 (1991) 143-152. + + """ + + cdef list rr = list() + cdef list cc = list() + cdef list val = list() + + cdef Py_ssize_t x = 0 + cdef Py_ssize_t y = radius + cdef Py_ssize_t d = 0 + + cdef double dceil = 0 + + dceil_prev = 0 + + rr.extend([y, x, y, x, -y, -x, -y, -x]) + cc.extend([x, y, -x, -y, x, y, -x, -y]) + val.extend([1] * 8) + + while y > x + 1: + x += 1 + dceil = math.sqrt(radius**2 - x**2) + dceil = math.ceil(dceil) - dceil + if dceil < dceil_prev: + y -= 1 + rr.extend([y, y - 1, x, x, y, y - 1, x, x]) + cc.extend([x, x, y, y - 1, -x, -x, -y, 1 - y]) + + rr.extend([-y, 1 - y, -x, -x, -y, 1 - y, -x, -x]) + cc.extend([x, x, y, y - 1, -x, -x, -y, 1 - y]) + + val.extend([1 - dceil, dceil] * 8) + dceil_prev = dceil + + return (np.array(rr, dtype=np.intp) + cy, + np.array(cc, dtype=np.intp) + cx, + np.array(val, dtype=np.float)) def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, diff --git a/skimage/draw/tests/test_draw.py b/skimage/draw/tests/test_draw.py index 8dac897a..152797a7 100644 --- a/skimage/draw/tests/test_draw.py +++ b/skimage/draw/tests/test_draw.py @@ -1,8 +1,10 @@ from numpy.testing import assert_array_equal import numpy as np -from skimage.draw import line, polygon, circle, circle_perimeter, \ - ellipse, ellipse_perimeter, bezier_segment +from skimage.draw import (line, polygon, circle, circle_perimeter, + circle_perimeter_aa, ellipse, + ellipse_perimeter, bezier_segment, + ) def test_line_horizontal(): @@ -215,15 +217,15 @@ def test_circle_perimeter_andres(): assert_array_equal(img, img_) -def test_circle_perimeter_wu(): +def test_circle_perimeter_aa(): img = np.zeros((15, 15), 'uint8') - rr, cc, val = circle_perimeter(7, 7, 0, method='wu') + rr, cc, val = circle_perimeter_aa(7, 7, 0) img[rr, cc] = 1 assert(np.sum(img) == 1) assert(img[7][7] == 1) img = np.zeros((17, 17), 'uint8') - rr, cc, val = circle_perimeter(8, 8, 7, method='wu') + rr, cc, val = circle_perimeter_aa(8, 8, 7) img[rr, cc] = val * 255 img_ = np.array( [[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],