add shape option to draw functions

This commit is contained in:
François Boulogne
2014-11-09 22:05:59 +01:00
parent c3cf9890b7
commit 8e2f397700
2 changed files with 112 additions and 4 deletions
+62 -4
View File
@@ -10,6 +10,31 @@ from libc.math cimport sqrt, sin, cos, floor, ceil
from skimage._shared.geometry cimport point_in_polygon
def _coords_inside_image(rr, cc, shape, val=None):
"""
Return the coordinates inside an image of a given shape.
Parameters
----------
rr, cc : (N,) ndarray of int
Indices of pixels.
shape : tuple, optional
Image shape which is used to determine maximum extents of output pixel
coordinates.
val : ndarray of float
Values of pixels.
Returns
-------
rr, cc, val : (N,) ndarray (int, int, float)
Indices of pixels (and values if specified) inside the shape.
"""
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]
def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2):
"""Generate line pixel coordinates.
@@ -263,7 +288,7 @@ def polygon(y, x, shape=None):
def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
method='bresenham'):
method='bresenham', shape=None):
"""Generate circle perimeter coordinates.
Parameters
@@ -275,6 +300,10 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
method : {'bresenham', 'andres'}, optional
bresenham : Bresenham method (default)
andres : Andres method
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 polygon are used.
Returns
-------
@@ -361,11 +390,16 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
d = d + 2 * (y - x - 1)
y = y - 1
x = x + 1
if shape is not None:
return _coords_inside_image(np.array(rr, dtype=np.intp) + cy,
np.array(cc, dtype=np.intp) + cx,
shape)
return (np.array(rr, dtype=np.intp) + cy,
np.array(cc, dtype=np.intp) + cx)
def circle_perimeter_aa(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius):
def circle_perimeter_aa(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius,
shape=None):
"""Generate anti-aliased circle perimeter coordinates.
Parameters
@@ -374,6 +408,10 @@ def circle_perimeter_aa(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius):
Centre coordinate of circle.
radius: int
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 polygon are used.
Returns
-------
@@ -436,13 +474,18 @@ def circle_perimeter_aa(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius):
val.extend([1 - dceil, dceil] * 8)
dceil_prev = dceil
if shape is not None:
return _coords_inside_image(np.array(rr, dtype=np.intp) + cy,
np.array(cc, dtype=np.intp) + cx,
shape,
val=np.array(val, dtype=np.float))
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,
Py_ssize_t xradius, double orientation=0):
Py_ssize_t xradius, double orientation=0, shape=None):
"""Generate ellipse perimeter coordinates.
Parameters
@@ -453,6 +496,10 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius,
Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``.
orientation : double, optional (default 0)
Major axis orientation in clockwise direction as radians.
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 polygon are used.
Returns
-------
@@ -574,6 +621,9 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius,
py.extend(rr)
px.extend(cc)
if shape is not None:
return _coords_inside_image(np.array(py, dtype=np.intp),
np.array(px, dtype=np.intp), shape)
return np.array(py, dtype=np.intp), np.array(px, dtype=np.intp)
@@ -708,7 +758,7 @@ def _bezier_segment(Py_ssize_t y0, Py_ssize_t x0,
def bezier_curve(Py_ssize_t y0, Py_ssize_t x0,
Py_ssize_t y1, Py_ssize_t x1,
Py_ssize_t y2, Py_ssize_t x2,
double weight):
double weight, shape=None):
"""Generate Bezier curve coordinates.
Parameters
@@ -721,6 +771,10 @@ def bezier_curve(Py_ssize_t y0, Py_ssize_t x0,
Coordinates of the last control point.
weight : double
Middle control point weight, it describes the line tension.
shape : tuple, optional
Image shape which is used to determine maximum extents of output pixel
coordinates. This is useful for curves which exceed the image size.
By default the full extents of the polygon are used.
Returns
-------
@@ -833,4 +887,8 @@ def bezier_curve(Py_ssize_t y0, Py_ssize_t x0,
rr, cc = _bezier_segment(y0, x0, y1, x1, y2, x2, weight * weight)
px.extend(rr)
py.extend(cc)
if shape is not None:
return _coords_inside_image(np.array(px, dtype=np.intp),
np.array(py, dtype=np.intp), shape)
return np.array(px, dtype=np.intp), np.array(py, dtype=np.intp)
+50
View File
@@ -234,6 +234,17 @@ def test_circle_perimeter_bresenham():
assert_array_equal(img, img_)
def test_circle_perimeter_bresenham_shape():
img = np.zeros((15, 20), 'uint8')
rr, cc = circle_perimeter(7, 10, 9, method='bresenham', shape=(15, 20))
img[rr, cc] = 1
shift = 5
img_ = np.zeros((15 + 2 * shift, 20), 'uint8')
rr, cc = circle_perimeter(7 + shift, 10, 9, method='bresenham', shape=None)
img_[rr, cc] = 1
assert_array_equal(img, img_[shift:-shift, :])
def test_circle_perimeter_andres():
img = np.zeros((15, 15), 'uint8')
rr, cc = circle_perimeter(7, 7, 0, method='andres')
@@ -298,6 +309,16 @@ def test_circle_perimeter_aa():
assert_array_equal(img, img_)
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)
@@ -580,6 +601,17 @@ def test_ellipse_perimeter_nzeroangle():
assert_array_equal(img, img_)
def test_ellipse_perimeter_shape():
img = np.zeros((15, 20), 'uint8')
rr, cc = ellipse_perimeter(7, 10, 9, 9, 0, shape=(15, 20))
img[rr, cc] = 1
shift = 5
img_ = np.zeros((15 + 2 * shift, 20), 'uint8')
rr, cc = ellipse_perimeter(7 + shift, 10, 9, 9, 0, shape=None)
img_[rr, cc] = 1
assert_array_equal(img, img_[shift:-shift, :])
def test_bezier_segment_straight():
image = np.zeros((200, 200), dtype=int)
x0 = 50
@@ -726,6 +758,24 @@ def test_bezier_curved_weight_neq_1():
)
assert_equal(img, img_)
def test_bezier_curve_shape():
img = np.zeros((15, 20), 'uint8')
x1, y1 = (1, 5)
x2, y2 = (6, 11)
x3, y3 = (1, 14)
rr, cc = bezier_curve(x1, y1, x2, y2, x3, y3, 2, shape=(15, 20))
img[rr, cc] = 1
shift = 5
img_ = np.zeros((15 + 2 * shift, 20), 'uint8')
x1, y1 = (1 + shift, 5)
x2, y2 = (6 + shift, 11)
x3, y3 = (1 + shift, 14)
rr, cc = bezier_curve(x1, y1, x2, y2, x3, y3, 2, shape=None)
img_[rr, cc] = 1
assert_array_equal(img, img_[shift:-shift, :])
if __name__ == "__main__":
from numpy.testing import run_module_suite
run_module_suite()