Reputation: 11
I have a set of data points in 3D (X,Y,Z) in a given plane (3D). and i hope to fit an epllipse to those points.
I found a lot of answers about how to fit the ellipse in 2D points. So more precisely, my question is how to transform 3D data(x,y,z) points -> 2D data(x,y)?
Upvotes: 1
Views: 1171
Reputation: 31
Here is my Python code for this problem. this link helped me through my implementation: https://meshlogic.github.io/posts/jupyter/curve-fitting/fitting-a-circle-to-cluster-of-3d-points/
import numpy as np
from skimage.measure import EllipseModel
#-------------------------------------------------------------------------------
# RODRIGUES ROTATION
# - Rotate given points based on a starting and ending vector
# - Axis k and angle of rotation theta given by vectors n0,n1
# P_rot = P*cos(theta) + (k x P)*sin(theta) + k*<k,P>*(1-cos(theta))
#-------------------------------------------------------------------------------
def rodrigues_rot(P, n0, n1):
# If P is only 1d array (coords of single point), fix it to be matrix
if P.ndim == 1:
P = P[np.newaxis,:]
# Get vector of rotation k and angle theta
n0 = n0/np.linalg.norm(n0)
n1 = n1/np.linalg.norm(n1)
k = np.cross(n0,n1)
k = k/np.linalg.norm(k)
theta = np.arccos(np.dot(n0,n1))
# Compute rotated points
P_rot = np.zeros((len(P),3))
for i in range(len(P)):
P_rot[i] = P[i]*np.cos(theta) + np.cross(k,P[i])*np.sin(theta) + k*np.dot(k,P[i])*(1-np.cos(theta))
return P_rot
def fit_an_ellipse(P):
P_mean = P.mean(axis=0)
P_centered = P - P_mean
# Fitting plane by SVD for the mean-centered data
U,s,V = np.linalg.svd(P_centered, full_matrices=False)
# Normal vector of fitting plane is given by 3rd column in V
# Note linalg.svd returns V^T, so we need to select 3rd row from V^T
# normal on 3d plane
normal = V[2,:]
# Project points to coords X-Y in 2D plane
P_xy = rodrigues_rot(P_centered, normal, [0,0,1])
# Use skimage EllipseModel to fit an ellipse to set of 2d points
ell = EllipseModel()
ell.estimate(P_xy[:, :2])
# Generate n 2D points on the fitted elippse
n = 100
xy = ell.predict_xy(np.linspace(0, 2 * np.pi, n))
# Convert the 2D generated points to the 3D space
points = []
for i in range(len(xy)):
points.append([xy[i, 0], xy[i, 1], 0])
points = np.array(points)
ellipse_points_3d = rodrigues_rot(points, [0,0,1], normal) + P_mean
return ellipse_points_3d
to test the code you can run this and check the output result:
import numpy as np
import plotly
import plotly.graph_objs as go
P = np.array([[52.21818786, 7.86337722, 57.83456389],
[30.55316226, 32.36591494, 14.35753359],
[59.77387002, 14.29531811, 53.6462596 ],
[42.85677086, 32.67223954, -5.95323959],
[44.46449002, 1.43144171, 54.0253186 ],
[27.6464027 , 19.80836045, -1.5754063 ],
[63.48591069, 6.88329618, 57.55556516],
[44.19484831, 28.32302575, 6.01730042],
[46.09443886, 2.71782362, 57.98617489],
[22.55050927, 30.28315605, 42.5642505 ],
[20.16244533, 18.55944689, 34.06871328],
[69.4591254 , 33.62256919, 40.91996533],
[24.42183439, 5.95578526, 35.80224431],
[70.09161495, 24.03152634, 45.77915268],
[28.68122335, -6.64788396, 37.53577535],
[59.84340586, 23.47833222, 60.01530894],
[23.98376474, 14.23114661, 32.43676647],
[73.28044481, 29.29426891, 39.28801852],
[28.48679585, -5.33220296, 36.04206575],
[54.66351746, 15.7561502 , 51.20981383],
[38.33444206, -0.08003422, 41.2639318 ],
[57.27722964, 39.91662965, 20.63778872],
[43.24856256, 7.79042068, 50.95451935],
[64.68788661, 31.78841088, 27.19632274],
[41.67377653, -0.18313508, 49.56081237],
[60.577958 , 35.8138609 , 28.9005053 ]])
ellipse_points = fit_an_ellipse(P)
lines = []
lines.append(go.Scatter3d(x=P[:, 0], y=P[:, 1], \
z=P[:, 2],name='P'\
,opacity=1, ))
lines.append(go.Scatter3d(x=ellipse_points[:, 0], y=ellipse_points[:, 1], \
z=ellipse_points[:, 2],name='ellipse_points'\
,opacity=1, ))
plotly.offline.iplot(lines)
the output result: the 3d fitting result
you can try the code yourself in colab: https://colab.research.google.com/drive/1snI2-_S1CY8iUtszRP1bzsFULEQYdJym?usp=sharing
Upvotes: 3
Reputation: 2331
In standard projections the ellipse (and circle is elipse with a = b = r) will be projected in ellipse or a line. So I will use this behaviour so the 3D ellipse you want will be defined by different ellipse you can calculate.
I will not show the code, but the approach can be:
[x,y,z]
z=f(x,y)
[x,y,f(x,y)]
vectors[x,y]
pairs in the matrix resulted from the search (ignoring the z
part can be understtod as projection to x-y plane).[x_fit,y_fit]
[x_fit,y_fit,f(x_fit,y_fit)]
Upvotes: 0