I am trying to fit an ellipse over my different sets of 2D points using matplotlib. I'm using the ellipse fitting functions from here, and here's the code.
from matplotlib.patches import Ellipse import matplotlib.pyplot as plt import numpy def fitEllipse(x,y): x = x[:,np.newaxis] y = y[:,np.newaxis] D = np.hstack((x*x, x*y, y*y, x, y, np.ones_like(x))) S = np.dot(D.T,D) C = np.zeros([6,6]) C[0,2] = C[2,0] = 2; C[1,1] = -1 E, V = eig(np.dot(inv(S), C)) n = np.argmax(np.abs(E)) a = V[:,n] return a def ellipse_center(a): b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0] num = b*b-a*c x0=(c*d-b*f)/num y0=(a*f-b*d)/num return np.array([x0,y0]) def ellipse_axis_length( a ): b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0] up = 2*(a*f*f+c*d*d+g*b*b-2*b*d*f-a*c*g) down1=(b*b-a*c)*( (c-a)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a)) down2=(b*b-a*c)*( (a-c)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a)) res1=np.sqrt(up/down1) res2=np.sqrt(up/down2) return np.array([res1, res2]) def ellipse_angle_of_rotation2( a ): b,c,d,f,g,a = a[1]/2, a[2], a[3]/2, a[4]/2, a[5], a[0] if b == 0: if a > c: return 0 else: return np.pi/2 else: if a > c: return np.arctan(2*b/(a-c))/2 else: return np.pi/2 + np.arctan(2*b/(a-c))/2 However, when I plot the ellipse using matplotlib, sometimes the ellipse is fitted well, and sometimes I need to rotate the ellipse 90 degrees to have it fit (the blue ellipse is rotated 90, the red ellipse is no additional rotation) Here is my code.
def plot_ellipse(x, y): a = fitEllipse(x, y) center = ellipse_center(a) phi = ellipse_angle_of_rotation2(a) axes = ellipse_axis_length(a) a, b = axes ell = Ellipse(center, 2*a, 2*b, phi*180 / np.pi, facecolor=(1,0,0,0.2), edgecolor=(0,0,0,0.5)) ell_rotated = Ellipse(center, 2*a, 2*b, phi*180 / np.pi + 90, facecolor=(0,0,1,0.2), edgecolor=(0,0,0,0.5)) fig, ax = plt.subplots() ax.add_patch(ell) ax.add_patch(ell_rotated) plt.scatter(x, y) plt.show() x1 = np.array([238, 238, 238, 237, 237, 237, 237, 237, 236, 236, 236, 236, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 266, 267, 267, 268, 268, 269, 269, 270, 270, 271, 271, 271, 271, 271, 272, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 275, 275, 274, 274, 274, 274, 274, 274, 274, 274, 274, 273, 273, 273, 272, 272, 272, 272, 271, 271, 271, 270, 270, 269, 268, 268, 267, 266, 266, 265, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 256, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 235, 235, 234, 234, 233, 233, 232, 232, 232, 231, 231, 230, 230, 229, 229, 229, 229, 229, 229, 229, 229, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 229, 229, 229, 229, 229, 229, 229, 229, 229, 230, 230, 230, 230, 231, 231, 231, 232, 232, 232, 232, 233, 233, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242]) y1 = np.array([283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 293, 294, 295, 296, 297, 298, 299, 300, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, 300, 300, 299, 299, 298, 298, 297, 297, 296, 296, 296, 295, 294, 293, 292, 291, 290, 289, 288, 287, 286, 286, 285, 284, 283, 282, 281, 280, 279, 278, 277, 276, 276, 275, 274, 273, 272, 271, 270, 269, 268, 267, 266, 265, 264, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255, 254, 253, 252, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 233, 232, 232, 231, 230, 230, 229, 228, 228, 227, 227, 227, 227, 226, 226, 226, 226, 226, 226, 225, 225, 225, 225, 226, 226, 227, 228, 229, 229, 230, 231, 231, 232, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 299, 300, 301, 301, 302, 303, 304, 304, 305, 306]) x2 = np.array( [235, 236, 237, 238, 239, 240, 241, 242, 243, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 266, 267, 268, 269, 270, 270, 271, 272, 273, 274, 274, 274, 275, 275, 276, 276, 277, 277, 278, 278, 279, 279, 279, 279, 280, 280, 280, 280, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 281, 281, 281, 281, 281, 281, 281, 281, 281, 280, 280, 280, 280, 280, 279, 279, 279, 279, 278, 278, 277, 277, 276, 276, 275, 275, 274, 274, 274, 273, 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 263, 262, 261, 260, 259, 258, 257, 256, 255, 254, 253, 252, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 240, 239, 238, 237, 236, 235, 234, 233, 232, 232, 231, 231, 230, 230, 229, 229, 228, 228, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 226, 227, 227, 227, 227, 227, 227, 227, 227, 228, 228, 228, 228, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244]) y2 = np.array( [279, 280, 281, 282, 283, 284, 285, 286, 287, 287, 287, 288, 288, 289, 289, 290, 290, 290, 291, 291, 292, 292, 292, 292, 291, 291, 290, 290, 289, 289, 288, 288, 287, 287, 287, 286, 285, 284, 283, 282, 281, 280, 279, 278, 278, 277, 276, 275, 274, 273, 272, 271, 270, 269, 268, 267, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 224, 223, 222, 222, 221, 220, 219, 218, 217, 217, 216, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 214, 214, 214, 214, 214, 215, 215, 216, 216, 217, 217, 217, 218, 218, 219, 219, 219, 220, 221, 222, 223, 223, 224, 225, 226, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 282, 283, 284, 284, 285, 286, 287, 287, 288, 289]) plot_ellipse(x1, y1) plot_ellipse(x2, y2) And here are screenshots of the plots:
As you can see, the non-rotated (red) ellipse fits the x1,y1 data well, but the rotated ellipse (blue) fits the x2,y2 data.
I'm confused if I'm missing something here, when do I need to rotate the ellipse by 90º and when do I not need to?