diff --git a/skimage/draw/__init__.py b/skimage/draw/__init__.py index a5ba8869..bffb5847 100644 --- a/skimage/draw/__init__.py +++ b/skimage/draw/__init__.py @@ -1,5 +1,5 @@ -from ._draw import line, polygon, ellipse, ellipse_perimeter, \ - circle, circle_perimeter, set_color, bresenham +from .draw import circle, ellipse, set_color +from ._draw import line, polygon, ellipse_perimeter, circle_perimeter __all__ = ['line', 'polygon', @@ -7,5 +7,4 @@ __all__ = ['line', 'ellipse_perimeter', 'circle', 'circle_perimeter', - 'set_color', - 'bresenham'] + 'set_color'] diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 9dbcd914..72376b7a 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -8,7 +8,6 @@ import numpy as np cimport numpy as cnp from libc.math cimport sqrt from skimage._shared.geometry cimport point_in_polygon -from skimage._shared.utils import deprecated def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2): @@ -28,6 +27,24 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2): May be used to directly index into an array, e.g. ``img[rr, cc] = 1``. + Examples + -------- + >>> from skimage.draw import line + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> rr, cc = line(1, 1, 8, 8) + >>> img[rr, cc] = 1 + >>> img + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8) + """ cdef cnp.ndarray[cnp.intp_t, ndim=1, mode="c"] rr, cc @@ -74,9 +91,6 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2): return rr, cc -bresenham = deprecated('skimage.draw.line')(line) - - def polygon(y, x, shape=None): """Generate coordinates of pixels within polygon. @@ -87,7 +101,7 @@ def polygon(y, x, shape=None): x : (N,) ndarray X-coordinates of vertices of polygon. shape : tuple, optional - image shape which is used to determine maximum extents of output pixel + Image shape which is used to determine maximum extents of output pixel coordinates. This is useful for polygons which exceed the image size. By default the full extents of the polygon are used. @@ -98,6 +112,26 @@ def polygon(y, x, shape=None): May be used to directly index into an array, e.g. ``img[rr, cc] = 1``. + Examples + -------- + >>> from skimage.draw import polygon + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> x = np.array([1, 7, 4, 1]) + >>> y = np.array([1, 2, 8, 1]) + >>> rr, cc = polygon(y, x) + >>> img[rr, cc] = 1 + >>> img + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8) + """ cdef Py_ssize_t nr_verts = x.shape[0] @@ -133,82 +167,6 @@ def polygon(y, x, shape=None): return np.array(rr, dtype=np.intp), np.array(cc, dtype=np.intp) -def ellipse(double cy, double cx, double yradius, double xradius, shape=None): - """Generate coordinates of pixels within ellipse. - - Parameters - ---------- - cy, cx : double - Centre coordinate of ellipse. - yradius, xradius : double - Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``. - shape : tuple, optional - image shape which is used to determine maximum extents of output pixel - coordinates. This is useful for ellipses which exceed the image size. - By default the full extents of the ellipse are used. - - Returns - ------- - rr, cc : ndarray of int - Pixel coordinates of ellipse. - May be used to directly index into an array, e.g. - ``img[rr, cc] = 1``. - - """ - - cdef Py_ssize_t minr = int(max(0, cy - yradius)) - cdef Py_ssize_t maxr = int(math.ceil(cy + yradius)) - cdef Py_ssize_t minc = int(max(0, cx - xradius)) - cdef Py_ssize_t maxc = int(math.ceil(cx + xradius)) - - # make sure output coordinates do not exceed image size - if shape is not None: - maxr = min(shape[0] - 1, maxr) - maxc = min(shape[1] - 1, maxc) - - cdef Py_ssize_t r, c - - # output coordinate arrays - cdef list rr = list() - cdef list cc = list() - - for r in range(minr, maxr+1): - for c in range(minc, maxc+1): - if sqrt(((r - cy) / yradius)**2 + ((c - cx) / xradius)**2) < 1: - rr.append(r) - cc.append(c) - - return np.array(rr, dtype=np.intp), np.array(cc, dtype=np.intp) - - -def circle(double cy, double cx, double radius, shape=None): - """Generate coordinates of pixels within circle. - - Parameters - ---------- - cy, cx : double - Centre coordinate of circle. - radius: double - Radius of circle. - shape : tuple, optional - image shape which is used to determine maximum extents of output pixel - coordinates. This is useful for circles which exceed the image size. - By default the full extents of the circle are used. - - Returns - ------- - rr, cc : ndarray of int - Pixel coordinates of circle. - May be used to directly index into an array, e.g. - ``img[rr, cc] = 1``. - Notes - ----- - This function is a wrapper for skimage.draw.ellipse() - """ - - return ellipse(cy, cx, radius, radius, shape) - - def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, method='bresenham'): """Generate circle perimeter coordinates. @@ -223,7 +181,6 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, bresenham : Bresenham method andres : Andres method - Returns ------- rr, cc : (N,) ndarray of int @@ -241,8 +198,27 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, References ---------- .. [1] J.E. Bresenham, "Algorithm for computer control of a digital - plotter", 4 (1965) 25-30. - .. [2] E. Andres, "Discrete circles, rings and spheres", 18 (1994) 695-706. + plotter", 4 (1965) 25-30. + .. [2] E. Andres, "Discrete circles, rings and spheres", + 18 (1994) 695-706. + + Examples + -------- + >>> from skimage.draw import circle_perimeter + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> rr, cc = circle_perimeter(4, 4, 3) + >>> img[rr, cc] = 1 + >>> img + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 1, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8) """ @@ -311,6 +287,24 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, .. [1] J. Kennedy "A fast Bresenham type algorithm for drawing ellipses". + Examples + -------- + >>> from skimage.draw import ellipse_perimeter + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> rr, cc = ellipse_perimeter(5, 5, 3, 4) + >>> img[rr, cc] = 1 + >>> img + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 1, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 1, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8) + """ # If both radii == 0, return the center to avoid infinite loop in 2nd set @@ -373,31 +367,3 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, ychange += twobsquared return np.array(py, dtype=np.intp) + cy, np.array(px, dtype=np.intp) + cx - - -def set_color(img, coords, color): - """Set pixel color in the image at the given coordinates. - - Coordinates that exceed the shape of the image will be ignored. - - Parameters - ---------- - img : (M, N, D) ndarray - Image - coords : ((P,) ndarray, (P,) ndarray) - Coordinates of pixels to be colored. - color : (D,) ndarray - Color to be assigned to coordinates in the image. - - Returns - ------- - img : (M, N, D) ndarray - The updated image. - - """ - - rr, cc = coords - rr_inside = np.logical_and(rr >= 0, rr < img.shape[0]) - cc_inside = np.logical_and(cc >= 0, cc < img.shape[1]) - inside = np.logical_and(rr_inside, cc_inside) - img[rr[inside], cc[inside]] = color diff --git a/skimage/draw/draw.py b/skimage/draw/draw.py new file mode 100644 index 00000000..d01bc2b0 --- /dev/null +++ b/skimage/draw/draw.py @@ -0,0 +1,156 @@ +# coding: utf-8 +import numpy as np + + +def _coords_inside_image(rr, cc, shape): + mask = (rr >= 0) & (rr < shape[0]) & (cc >= 0) & (cc < shape[1]) + return rr[mask], cc[mask] + + +def ellipse(cy, cx, yradius, xradius, shape=None): + """Generate coordinates of pixels within ellipse. + + Parameters + ---------- + cy, cx : double + Centre coordinate of ellipse. + yradius, xradius : double + Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``. + shape : tuple, optional + Image shape which is used to determine maximum extents of output pixel + coordinates. This is useful for ellipses which exceed the image size. + By default the full extents of the ellipse are used. + + Returns + ------- + rr, cc : ndarray of int + Pixel coordinates of ellipse. + May be used to directly index into an array, e.g. + ``img[rr, cc] = 1``. + + Examples + -------- + >>> from skimage.draw import ellipse + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> rr, cc = ellipse(5, 5, 3, 4) + >>> img[rr, cc] = 1 + >>> img + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8) + + """ + + dr = 1 / float(yradius) + dc = 1 / float(xradius) + + r, c = np.ogrid[-1:1:dr, -1:1:dc] + rr, cc = np.nonzero(r ** 2 + c ** 2 < 1) + + rr.flags.writeable = True + cc.flags.writeable = True + rr += cy - yradius + cc += cx - xradius + + if shape is not None: + _coords_inside_image(rr, cc, shape) + + return rr, cc + + +def circle(cy, cx, radius, shape=None): + """Generate coordinates of pixels within circle. + + Parameters + ---------- + cy, cx : double + Centre coordinate of circle. + radius: double + Radius of circle. + shape : tuple, optional + Image shape which is used to determine maximum extents of output pixel + coordinates. This is useful for circles which exceed the image size. + By default the full extents of the circle are used. + + Returns + ------- + rr, cc : ndarray of int + Pixel coordinates of circle. + May be used to directly index into an array, e.g. + ``img[rr, cc] = 1``. + Notes + ----- + This function is a wrapper for skimage.draw.ellipse() + + Examples + -------- + >>> from skimage.draw import circle + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> rr, cc = circle(4, 4, 5) + >>> img[rr, cc] = 1 + >>> img + array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8) + + """ + + return ellipse(cy, cx, radius, radius, shape) + + +def set_color(img, coords, color): + """Set pixel color in the image at the given coordinates. + + Coordinates that exceed the shape of the image will be ignored. + + Parameters + ---------- + img : (M, N, D) ndarray + Image + coords : ((P,) ndarray, (P,) ndarray) + Coordinates of pixels to be colored. + color : (D,) ndarray + Color to be assigned to coordinates in the image. + + Returns + ------- + img : (M, N, D) ndarray + The updated image. + + Examples + -------- + >>> from skimage.draw import line, set_color + >>> img = np.zeros((10, 10), dtype=np.uint8) + >>> rr, cc = line(1, 1, 20, 20) + >>> set_color(img, (rr, cc), 1) + >>> img + array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + [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