Search code examples
pythonimage-processingscikit-image

Skimage.draw.ellipse generates two undesired lines


I have a boolean image, where the zeros are the background, and I want to plot the ellipse that encloses the major and minor axis of an object retrieved from skimage.measure.regionprops. The module skimage.draw.ellipse_perimeter generates the expected ellipse but also two undesired lines.

Code (the input image is here):

import skimage
import skimage.draw
from skimage.measure import label
from skimage.measure import regionprops
import matplotlib.pyplot as plt

# load example image
TP_mask = plt.imread('https://i.sstatic.net/UYLE0.png')

# connect region with same integer value
region = label(TP_mask)

# obtain RegionProperties
props = regionprops(region)
props = props[0]

# define centroid
y0,x0 = props.centroid

# draw ellipse perimeter
rr,cc = skimage.draw.ellipse_perimeter(int(x0),int(y0),int(props.minor_axis_length*0.5),int(props.major_axis_length*0.5), orientation = props.orientation)

# plot
plt.plot(rr,cc, color = 'yellow')
plt.imshow(TP_mask, cmap = 'gray')
plt.show()

However, if I create a simplified example as follows, I obtain the expected ellipse. Could someone help me understand what am I doing wrong?

import numpy as np

img = np.zeros((1000,1000))
img[200:800,200:400] = 1

region = label(img)

props = regionprops(region)

props = props[0]

y0,x0 = props.centroid

rr,cc = skimage.draw.ellipse_perimeter(int(x0),int(y0),int(props.minor_axis_length*0.5),int(props.major_axis_length*0.5), orientation = props.orientation)

plt.plot(rr,cc, color = 'yellow')
plt.imshow(img, cmap = 'gray')
plt.show()

enter image description here


Solution

  • It turns out that the coordinates returned by the draw module are designed to index into an array, as shown in this example, rather than plot:

    rr, cc = ellipse_perimeter(120, 400, 60, 20, orientation=math.pi / 4.)
    img[rr, cc, :] = (1, 0, 1)
    

    To use plt.plot and do a line plot, the coordinates need to be sorted as they go around the circle/ellipse. They are not properly sorted by default because the ellipse is actually drawn in four separate quadrants, which you can find by looking at the relevant part of the source code. (A clue: the lines hit exactly where the ellipse is vertical or horizontal.)

    Since you have a convex surface, computing the angle between each point and the centre of the ellipse is enough to sort the points. Try the following:

    fig, ax = plt.subplots()
    _ = ax.imshow(TP_mask, cmap='gray')
    
    angle = np.arctan2(rr - np.mean(rr), cc - np.mean(cc))
    sorted_by_angle = np.argsort(angle)
    rrs = rr[sorted_by_angle]
    ccs = cc[sorted_by_angle]
    
    _ = ax.plot(rrs, ccs, color='red')
    

    Which gives:

    enter image description here