Python: fit 3D ellipsoid (oblate/prolate) to 3D points

Question:

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:

1. Find convex hull of 3D data points

2. 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)?

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('npolynomialn',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 polynomialn',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 centern',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 aren',axes  ,'n')

inve=inv(ec) #inverse is actually the transpose here
if printMe: print('nRotation matrixn',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)
``````
Categories: questions
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.