6

I was trying to calculate the curvature of a surface given by array of points (x,y,z). Initially I was trying to fit a polynomial equation z=a + bx + cx^2 + dy + exy + fy^2) and then calculate the gaussian curvature

$ K = \frac{F_{xx}\cdot F_{yy}-{F_{xy}}^2}{(1+{F_x}^2+{F_y}^2)^2} $

However the problem is fitting if the surface is complex. I found this Matlab code to numerically calculate curvature. I wonder how to do the same in Python.

function [K,H,Pmax,Pmin] = surfature(X,Y,Z),
% SURFATURE -  COMPUTE GAUSSIAN AND MEAN CURVATURES OF A SURFACE
%   [K,H] = SURFATURE(X,Y,Z), WHERE X,Y,Z ARE 2D ARRAYS OF POINTS ON THE
%   SURFACE.  K AND H ARE THE GAUSSIAN AND MEAN CURVATURES, RESPECTIVELY.
%   SURFATURE RETURNS 2 ADDITIONAL ARGUEMENTS,
%   [K,H,Pmax,Pmin] = SURFATURE(...), WHERE Pmax AND Pmin ARE THE MINIMUM
%   AND MAXIMUM CURVATURES AT EACH POINT, RESPECTIVELY.


% First Derivatives
[Xu,Xv] = gradient(X);
[Yu,Yv] = gradient(Y);
[Zu,Zv] = gradient(Z);

% Second Derivatives
[Xuu,Xuv] = gradient(Xu);
[Yuu,Yuv] = gradient(Yu);
[Zuu,Zuv] = gradient(Zu);

[Xuv,Xvv] = gradient(Xv);
[Yuv,Yvv] = gradient(Yv);
[Zuv,Zvv] = gradient(Zv);

% Reshape 2D Arrays into Vectors
Xu = Xu(:);   Yu = Yu(:);   Zu = Zu(:); 
Xv = Xv(:);   Yv = Yv(:);   Zv = Zv(:); 
Xuu = Xuu(:); Yuu = Yuu(:); Zuu = Zuu(:); 
Xuv = Xuv(:); Yuv = Yuv(:); Zuv = Zuv(:); 
Xvv = Xvv(:); Yvv = Yvv(:); Zvv = Zvv(:); 

Xu          =   [Xu Yu Zu];
Xv          =   [Xv Yv Zv];
Xuu         =   [Xuu Yuu Zuu];
Xuv         =   [Xuv Yuv Zuv];
Xvv         =   [Xvv Yvv Zvv];

% First fundamental Coeffecients of the surface (E,F,G)
E           =   dot(Xu,Xu,2);
F           =   dot(Xu,Xv,2);
G           =   dot(Xv,Xv,2);

m           =   cross(Xu,Xv,2);
p           =   sqrt(dot(m,m,2));
n           =   m./[p p p]; 

% Second fundamental Coeffecients of the surface (L,M,N)
L           =   dot(Xuu,n,2);
M           =   dot(Xuv,n,2);
N           =   dot(Xvv,n,2);

[s,t] = size(Z);

% Gaussian Curvature
K = (L.*N - M.^2)./(E.*G - F.^2);
K = reshape(K,s,t);

% Mean Curvature
H = (E.*N + G.*L - 2.*F.*M)./(2*(E.*G - F.^2));
H = reshape(H,s,t);

% Principal Curvatures
Pmax = H + sqrt(H.^2 - K);
Pmin = H - sqrt(H.^2 - K);
Shai
  • 111,146
  • 38
  • 238
  • 371
pappu
  • 129
  • 2
  • 3
  • 8
  • 1
    Consider reading [whathaveyoutried.com](http://whathaveyoutried.com) and [numpy.scipy.org](http://numpy.scipy.org) – jedwards Jul 03 '12 at 19:16

7 Answers7

9

I hope I'm not too late here. I work with exactly the same problem (a product for the company I work to).

The first thing you must consider is that the points must represent a rectangular mesh. X is a 2D array, Y is a 2D array, and Z is a 2D array. If you have an unstructured cloudpoint, with a single matrix shaped Nx3 (the first column being X, the second being Y and the third being Z) then you can't apply this matlab function.

I have developed a Python equivalent of this Matlab script, where I only calculate Mean curvature (I guess you can get inspired by the script and adapt it to get all your desired curvatures) for the Z matrix, ignoring the X and Y by assuming the grid is square. I think you can "grasp" what and how I am doing, and adapt it for your needs:

note: This assumes that your data points are 1 unit apart.

def mean_curvature(Z):
    Zy, Zx  = numpy.gradient(Z)
    Zxy, Zxx = numpy.gradient(Zx)
    Zyy, _ = numpy.gradient(Zy)

    H = (Zx**2 + 1)*Zyy - 2*Zx*Zy*Zxy + (Zy**2 + 1)*Zxx
    H = -H/(2*(Zx**2 + Zy**2 + 1)**(1.5))

    return H
AJ Bensman
  • 550
  • 4
  • 11
heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • I know this might be silly question, but can you explain how the X,Y,Z 2D matrices encoded in the first case. for the Nx3 it is clear for me, and is there any approach that transform from one representation to another (3 2D matrices to Nx3 matrix) ? Thanks. – JustInTime Jun 18 '13 at 09:03
  • If you have three MxN matrices, or even one MxNx3 matrix (like three stacked 2d matrices) you have the points structured by index, they have a rectangular structure. If you have one Nx3 matrix, the points might or might not be ordered in "rows" and "columns". If you want to pass from MxNx3 to Nx3 (and possibly back), you can use one of many numpy functions (`ravel`, `reshape`, `rollaxis`, etc.) with care. In the other case, you can do it only if the points are already "ordered", otherwise, they represent an unstructured cloudpoint instead of a proper mesh. – heltonbiker Jun 18 '13 at 14:07
  • What's the reason for inverting the sign of `H`? – Jason Nov 02 '21 at 13:33
8

In case others stumble across this question, for completeness I offer the following code, inspired by heltonbiker.

Here is some python code to calculate Gaussian curvature as described by equation (3) in "Computation of Surface Curvature from Range Images Using Geometrically Intrinsic Weights"*, T. Kurita and P. Boulanger, 1992.

import numpy as np

def gaussian_curvature(Z):
    Zy, Zx = np.gradient(Z)                                                     
    Zxy, Zxx = np.gradient(Zx)                                                  
    Zyy, _ = np.gradient(Zy)                                                    
    K = (Zxx * Zyy - (Zxy ** 2)) /  (1 + (Zx ** 2) + (Zy **2)) ** 2             
    return K

Note:

  1. heltonbiker's method is essentially equation (4) from the paper
  2. heltonbiker's method is also the same on "Surfaces in 3D space, Mean Curvature" on Wikipedia: http://en.wikipedia.org/wiki/Mean_curvature)
  3. If you need both K and H then include the calculation of "K" (Gaussian curvature) in heltonbiker code and return K and H. Saves a little processing time.
  4. I assume the surface is defined as a function of two coordinates, e.g. z = Z(x, y). In my case Z is a range image.
Michael
  • 559
  • 6
  • 15
  • 1
    I'm late again, but have to say that your `K` calculation is beautiful. Now we are expanding our system's algorithms, and not only K, but also principal components (Pmin, Pmax) will be needed, and I hope to get them from this python codebase. – heltonbiker May 30 '14 at 19:03
6

Although very late, but no harm in posting. I modified the "surfature" function for use in Python. Disclaimer: I'm not the author original "surfature.m" code. Credits wherever they are due. Just presenting Python implementation.

def surfature(X,Y,Z):
    # where X, Y, Z matrices have a shape (lr+1,lb+1)

    #First Derivatives
    Xv,Xu=np.gradient(X)
    Yv,Yu=np.gradient(Y)
    Zv,Zu=np.gradient(Z)

    #Second Derivatives
    Xuv,Xuu=np.gradient(Xu)
    Yuv,Yuu=np.gradient(Yu)
    Zuv,Zuu=np.gradient(Zu)   

    Xvv,Xuv=np.gradient(Xv)
    Yvv,Yuv=np.gradient(Yv)
    Zvv,Zuv=np.gradient(Zv) 

    #Reshape to 1D vectors
    nrow=(lr+1)*(lb+1) #total number of rows after reshaping
    Xu=Xu.reshape(nrow,1)
    Yu=Yu.reshape(nrow,1)
    Zu=Zu.reshape(nrow,1)
    Xv=Xv.reshape(nrow,1)
    Yv=Yv.reshape(nrow,1)
    Zv=Zv.reshape(nrow,1)
    Xuu=Xuu.reshape(nrow,1)
    Yuu=Yuu.reshape(nrow,1)
    Zuu=Zuu.reshape(nrow,1)
    Xuv=Xuv.reshape(nrow,1)
    Yuv=Yuv.reshape(nrow,1)
    Zuv=Zuv.reshape(nrow,1)
    Xvv=Xvv.reshape(nrow,1)
    Yvv=Yvv.reshape(nrow,1)
    Zvv=Zvv.reshape(nrow,1)

    Xu=np.c_[Xu, Yu, Zu]
    Xv=np.c_[Xv, Yv, Zv]
    Xuu=np.c_[Xuu, Yuu, Zuu]
    Xuv=np.c_[Xuv, Yuv, Zuv]
    Xvv=np.c_[Xvv, Yvv, Zvv]

    #% First fundamental Coeffecients of the surface (E,F,G)
    E=np.einsum('ij,ij->i', Xu, Xu) 
    F=np.einsum('ij,ij->i', Xu, Xv) 
    G=np.einsum('ij,ij->i', Xv, Xv) 

    m=np.cross(Xu,Xv,axisa=1, axisb=1)
    p=sqrt(np.einsum('ij,ij->i', m, m))
    n=m/np.c_[p,p,p]

    #% Second fundamental Coeffecients of the surface (L,M,N)
    L= np.einsum('ij,ij->i', Xuu, n) 
    M= np.einsum('ij,ij->i', Xuv, n) 
    N= np.einsum('ij,ij->i', Xvv, n) 

    #% Gaussian Curvature
    K=(L*N-M**2)/(E*G-L**2)
    K=K.reshape(lr+1,lb+1)

    #% Mean Curvature
    H = (E*N + G*L - 2*F*M)/(2*(E*G - F**2))
    H = H.reshape(lr+1,lb+1)

    #% Principle Curvatures
    Pmax = H + sqrt(H**2 - K)
    Pmin = H - sqrt(H**2 - K)

    return Pmax,Pmin
tgg199
  • 71
  • 1
  • 3
  • 1
    Could you help verify this possible typo: `K=(L*N-M**2)/(E*G-L**2)` should be `K=(L*N-M**2)/(E*G-F**2)`? – Jason Nov 02 '21 at 13:24
2

heltonbiker's answer about mean curvature is great, but it assumes data points in the 2d arrays are 1 unit apart from each other. If your data points are, for example, .3 units apart from each other, you would need to divide every data point in the answer by .3 squared (.09) to account for this.

For the gaussian curvature in Michael's answer, you would need to multiply each data point by (1/.3**2)**2 (123.45)

4b0
  • 21,981
  • 30
  • 95
  • 142
AJ Bensman
  • 550
  • 4
  • 11
1

BSD-licensed Python source code for surface fits can be found at

https://github.com/zunzun/pyeq2

(I'm the author).

crypdick
  • 16,152
  • 7
  • 51
  • 74
0

I'd suggest the igl package, note that it is still in beta. Here is how to compute principle curvature on igl.

Currently you have to install through conda, but wheels will be released on PyPi in the future.

-1

Dot Product in Python

Derivates in Python

Reshaping in Python

Oddly enough all of these are SO questions. Take a gander around next time and you can likely find an answer. Also note that you'll want to be using NumPy for Python to do this. It's fairly intuitive to use. Matlibplot (or something like that) might be helpful for you too!

Community
  • 1
  • 1
Ben A.
  • 1,039
  • 6
  • 13