From abcd613593ef1a49f7cf912cf23e7fa5da73659a Mon Sep 17 00:00:00 2001 From: Alexey Umnov Date: Wed, 17 Sep 2014 19:13:36 +0400 Subject: [PATCH] Fixed ellipse symmetry and added some tests. --- skimage/draw/draw.py | 40 +++++--- skimage/draw/tests/test_draw.py | 160 +++++++++++++++++++++++++++----- 2 files changed, 166 insertions(+), 34 deletions(-) diff --git a/skimage/draw/draw.py b/skimage/draw/draw.py index e61df40c..f60a67ee 100644 --- a/skimage/draw/draw.py +++ b/skimage/draw/draw.py @@ -7,6 +7,15 @@ def _coords_inside_image(rr, cc, shape): return rr[mask], cc[mask] +def _ellipse_in_shape(shape, center, radiuses): + """Generate coordinates of points within ellipse bounded by shape.""" + y, x = np.ogrid[0:shape[0], 0:shape[1]] + cy, cx = center + ry, rx = radiuses + distances = ((y - cy) / ry) ** 2 + ((x - cx) / rx) ** 2 + return np.nonzero(distances < 1) + + def ellipse(cy, cx, yradius, xradius, shape=None): """Generate coordinates of pixels within ellipse. @@ -48,21 +57,26 @@ def ellipse(cy, cx, yradius, xradius, shape=None): """ - 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 - + center = np.array([cy, cx]) + radiuses = np.array([yradius, xradius]) if shape is not None: - return _coords_inside_image(rr, cc, shape) + return _ellipse_in_shape(shape, center, radiuses) + else: + # rounding here is necessary to avoid rounding issues later + upper_left = np.floor(center - radiuses) - return rr, cc + shifted_center = center - upper_left + + # Shifted center is in interval [radiuses, radiuses + 1], so + # the ellipse must fit in [0, 2*radiuses + 1]. + bounding_shape = np.ceil(2 * radiuses + 1) + + rr, cc = _ellipse_in_shape(bounding_shape, shifted_center, radiuses) + rr.flags.writeable = True + cc.flags.writeable = True + rr += upper_left[0] + cc += upper_left[1] + return rr, cc def circle(cy, cx, radius, shape=None): diff --git a/skimage/draw/tests/test_draw.py b/skimage/draw/tests/test_draw.py index 3cb8c2a6..dce29f6a 100644 --- a/skimage/draw/tests/test_draw.py +++ b/skimage/draw/tests/test_draw.py @@ -298,30 +298,134 @@ def test_circle_perimeter_aa(): assert_array_equal(img, img_) -def test_ellipse(): - img = np.zeros((15, 15), 'uint8') +def test_ellipse_trivial(): + img = np.zeros((2, 2), 'uint8') + rr, cc = ellipse(0.5, 0.5, 0.5, 0.5) + img[rr, cc] = 1 + img_correct = np.array([ + [0, 0], + [0, 0] + ]) + assert_array_equal(img, img_correct) + img = np.zeros((2, 2), 'uint8') + rr, cc = ellipse(0.5, 0.5, 1.1, 1.1) + img[rr, cc] = 1 + img_correct = np.array([ + [1, 1], + [1, 1], + ]) + assert_array_equal(img, img_correct) + + img = np.zeros((3, 3), 'uint8') + rr, cc = ellipse(1, 1, 0.9, 0.9) + img[rr, cc] = 1 + img_correct = np.array([ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0], + ]) + assert_array_equal(img, img_correct) + + img = np.zeros((3, 3), 'uint8') + rr, cc = ellipse(1, 1, 1.1, 1.1) + img[rr, cc] = 1 + img_correct = np.array([ + [0, 1, 0], + [1, 1, 1], + [0, 1, 0], + ]) + assert_array_equal(img, img_correct) + + img = np.zeros((3, 3), 'uint8') + rr, cc = ellipse(1, 1, 1.5, 1.5) + img[rr, cc] = 1 + img_correct = np.array([ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + ]) + assert_array_equal(img, img_correct) + + +def test_ellipse_generic(): + img = np.zeros((4, 4), 'uint8') + rr, cc = ellipse(1.5, 1.5, 1.1, 1.7) + img[rr, cc] = 1 + img_ = np.array([ + [0, 0, 0, 0], + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 0, 0, 0], + ]) + assert_array_equal(img, img_) + + img = np.zeros((5, 5), 'uint8') + rr, cc = ellipse(2, 2, 1.7, 1.7) + img[rr, cc] = 1 + img_ = np.array([ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 0, 0], + ]) + assert_array_equal(img, img_) + + img = np.zeros((10, 10), 'uint8') + rr, cc = ellipse(5, 5, 3, 4) + img[rr, cc] = 1 + img_ = np.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], + ]) + assert_array_equal(img, img_) + + img = np.zeros((10, 10), 'uint8') + rr, cc = ellipse(4.5, 5, 3.5, 4) + img[rr, cc] = 1 + img_ = np.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, 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, 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], + ]) + assert_array_equal(img, img_) + + img = np.zeros((15, 15), 'uint8') rr, cc = ellipse(7, 7, 3, 7) img[rr, cc] = 1 - - img_ = np.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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 0, 1, 1, 1, 1, 1, 1, 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, 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, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] - ) - + img_ = np.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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 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, 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, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]) assert_array_equal(img, img_) @@ -352,6 +456,20 @@ def test_ellipse_with_shape(): assert_array_equal(img, img_) +def test_ellipse_negative(): + rr, cc = ellipse(-3, -3, 1.7, 1.7) + rr_, cc_ = np.nonzero(np.array([ + [0, 0, 0, 0, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 1, 0], + [0, 0, 0, 0, 0], + ])) + + assert_array_equal(rr, rr_ - 5) + assert_array_equal(cc, cc_ - 5) + + def test_ellipse_perimeter_dot_zeroangle(): # dot, angle == 0 img = np.zeros((30, 15), 'uint8')