问题
Dear fellow stackoverflow users,
I face a problem as follows: I would like to fit a 3D ellipsoid to 3D data points within my python script.
The starting data are a set of x, y and z coordinates (cartesian coordinates). What I would like to get are a and c in the defining equation of the best-fit ellipsoid of the convex hull of the 3D data points.
The equation is, in the properly rotated and translated coordinate system:
So the tasks I would ideally like to do are:
Find convex hull of 3D data points
Fit best-fit ellipsoid to the convex hull and get a and c
Do you know whether there is some library allowing to do this in Python with minimal lines of code? Or do I have to explicitly code every of these steps with my limited math knowledge (which essentially amounts to zero when it comes to find best fit ellipsoid)?
Thanks in advance for your help and have a nice day!
回答1:
All right, I found my solution by combining the convex hull algorithm of scipy with some python function found on this website.
Let us assume that you get a numpy vector of x coordinates, a numpy vector of y coordinates, and a numpy vector of z coordinates, named x, y and z. This worked for me:
from scipy.spatial import ConvexHull, convex_hull_plot_2d
import numpy as np
from numpy.linalg import eig, inv
def ls_ellipsoid(xx,yy,zz): #finds best fit ellipsoid. Found at http://www.juddzone.com/ALGORITHMS/least_squares_3D_ellipsoid.html
#least squares fit to a 3D-ellipsoid
# Ax^2 + By^2 + Cz^2 + Dxy + Exz + Fyz + Gx + Hy + Iz = 1
#
# Note that sometimes it is expressed as a solution to
# Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz = 1
# where the last six terms have a factor of 2 in them
# This is in anticipation of forming a matrix with the polynomial coefficients.
# Those terms with factors of 2 are all off diagonal elements. These contribute
# two terms when multiplied out (symmetric) so would need to be divided by two
# change xx from vector of length N to Nx1 matrix so we can use hstack
x = xx[:,np.newaxis]
y = yy[:,np.newaxis]
z = zz[:,np.newaxis]
# Ax^2 + By^2 + Cz^2 + Dxy + Exz + Fyz + Gx + Hy + Iz = 1
J = np.hstack((x*x,y*y,z*z,x*y,x*z,y*z, x, y, z))
K = np.ones_like(x) #column of ones
#np.hstack performs a loop over all samples and creates
#a row in J for each x,y,z sample:
# J[ix,0] = x[ix]*x[ix]
# J[ix,1] = y[ix]*y[ix]
# etc.
JT=J.transpose()
JTJ = np.dot(JT,J)
InvJTJ=np.linalg.inv(JTJ);
ABC= np.dot(InvJTJ, np.dot(JT,K))
# Rearrange, move the 1 to the other side
# Ax^2 + By^2 + Cz^2 + Dxy + Exz + Fyz + Gx + Hy + Iz - 1 = 0
# or
# Ax^2 + By^2 + Cz^2 + Dxy + Exz + Fyz + Gx + Hy + Iz + J = 0
# where J = -1
eansa=np.append(ABC,-1)
return (eansa)
def polyToParams3D(vec,printMe): #gets 3D parameters of an ellipsoid. Found at http://www.juddzone.com/ALGORITHMS/least_squares_3D_ellipsoid.html
# convert the polynomial form of the 3D-ellipsoid to parameters
# center, axes, and transformation matrix
# vec is the vector whose elements are the polynomial
# coefficients A..J
# returns (center, axes, rotation matrix)
#Algebraic form: X.T * Amat * X --> polynomial form
if printMe: print('\npolynomial\n',vec)
Amat=np.array(
[
[ vec[0], vec[3]/2.0, vec[4]/2.0, vec[6]/2.0 ],
[ vec[3]/2.0, vec[1], vec[5]/2.0, vec[7]/2.0 ],
[ vec[4]/2.0, vec[5]/2.0, vec[2], vec[8]/2.0 ],
[ vec[6]/2.0, vec[7]/2.0, vec[8]/2.0, vec[9] ]
])
if printMe: print('\nAlgebraic form of polynomial\n',Amat)
#See B.Bartoni, Preprint SMU-HEP-10-14 Multi-dimensional Ellipsoidal Fitting
# equation 20 for the following method for finding the center
A3=Amat[0:3,0:3]
A3inv=inv(A3)
ofs=vec[6:9]/2.0
center=-np.dot(A3inv,ofs)
if printMe: print('\nCenter at:',center)
# Center the ellipsoid at the origin
Tofs=np.eye(4)
Tofs[3,0:3]=center
R = np.dot(Tofs,np.dot(Amat,Tofs.T))
if printMe: print('\nAlgebraic form translated to center\n',R,'\n')
R3=R[0:3,0:3]
R3test=R3/R3[0,0]
# print('normed \n',R3test)
s1=-R[3, 3]
R3S=R3/s1
(el,ec)=eig(R3S)
recip=1.0/np.abs(el)
axes=np.sqrt(recip)
if printMe: print('\nAxes are\n',axes ,'\n')
inve=inv(ec) #inverse is actually the transpose here
if printMe: print('\nRotation matrix\n',inve)
return (center,axes,inve)
#let us assume some definition of x, y and z
#get convex hull
surface = np.stack((conf.x,conf.y,conf.z), axis=-1)
hullV = ConvexHull(surface)
lH = len(hullV.vertices)
hull = np.zeros((lH,3))
for i in range(len(hullV.vertices)):
hull[i] = surface[hullV.vertices[i]]
hull = np.transpose(hull)
#fit ellipsoid on convex hull
eansa = ls_ellipsoid(hull[0],hull[1],hull[2]) #get ellipsoid polynomial coefficients
print("coefficients:" , eansa)
center,axes,inve = polyToParams3D(eansa,False) #get ellipsoid 3D parameters
print("center:" , center)
print("axes:" , axes)
print("rotationMatrix:", inve)
来源:https://stackoverflow.com/questions/58501545/python-fit-3d-ellipsoid-oblate-prolate-to-3d-points