Matplotlib like matlab's trisurf

10,795

Solution 1

Depending on your performance needs, mayavi is likely to be best suited for this - as per Davis comment.

However, matplotlib comes with plot_trisurf to which you can perfectly pass generic UVW, X, Y , Z as you describe.

Exemple with a torus mesh:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri

R = 1.
r = 0.8
n = 50
m = 50

def torus_triangles(n, m):
    """ Returns triangles to mesh a (n, m) torus """
    tri = []
    for i in range(n):
        for j in range(m):
            a = i + j*(n)
            b = ((i+1) % n) + j*n
            d = i + ((j+1) % m) * n
            c = ((i+1) % n) + ((j+1) % m) * n
            tri += [[a, b, d], [b, c, d]]
    return np.array(tri, dtype=np.int32)

theta0 = np.linspace(0, (2*np.pi), n, endpoint=False)
phi0 = np.linspace(0, (2*np.pi), m, endpoint=False)
theta, phi = np.meshgrid(theta0, phi0)

x = (R + r * np.sin(phi)) * np.cos(theta)
y = (R + r * np.sin(phi)) * np.sin(theta)
z = r * np.cos(phi)

triangles = torus_triangles(n , m)
triang = mtri.Triangulation(x.ravel(), y.ravel(), triangles)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(triang, z.ravel(), lw=0.2, edgecolor="black", color="grey",
                alpha=0.5)

plt.show()

enter image description here

Solution 2

I was also looking for a solution to this problem and this discussion helped me to succeed. Here is how it works:

  1. A solution to very similar problem was already given as a link here in the comment from GBy (see above: Colouring the surface of a sphere with a set of scalar values in matplotlib)

  2. Transfering the knowledge to the problem here it results in creating an additional array containing the amplitudes and assigning it to the "underlying ScalarMappable through set_array method". The corresponding python code looks like this:

    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib import cm
    from matplotlib import pyplot as plt
    import numpy as np
    
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    colors = np.mean(CorticalImage[Face], axis=1)
    collec = ax.plot_trisurf(Xcoordinates, Ycoordinates, Zcoordinates, triangles=Face, cmap=cm.jet, linewidth=0.2)
    collec.set_array(colors)
    collec.autoscale()
    ax.view_init(30, 0)
    cbar = fig.colorbar(collec)
    

The arrays Xcoordinates, Ycoordinates, Zcoordinates contain the X, Y and Z coordinates of the mesh nodes. When checking their shape with e.g. Xcoordinates.shape it should look like this (750,), where 750 is the number of mesh nodes. The matrix Face is the same as the matrix UVW in the original question asked by Larry. It is "a 2D m-x-3 matrix in which each row is a triplet of indices into the point cloud". If you check the shape of the matrix Face it should be something like (1496, 3), where 1496 is the number of triangles in the mesh and 3 is the number of nodes in one triangle. Finally, the array CorticalImage contains the amplitudes for every node in the mesh and these are the values, which we want to use for the colors of the mesh (and not the Z values). The shape of that array should be like the shapes of the coordinates arrays, i.e. (750,).

IMPORTANT!!! You can see that the number of nodes and the number of triangles are not equal. This is almost always the case. Additionally, the amplitudes are usually given for the nodes and not for the triangles. Consequently, the amplitudes should be calculated for the triangles in order to get the right colors in the plot. This is done in the line colors = np.mean(CorticalImage[Face], axis=1).

Solution 3

Plotly has an open-source trisurf Python implementation that is closer to MATLAB's trisurf().

Python code and examples here:

https://plot.ly/python/tri-surf/

python trisurf

Solution 4

Thought for completeness sake I would add a mayavi example here, using the mesh from GBy's answer.

import numpy as np
from mayavi import mlab 
from tvtk.api import tvtk

R = 1.
r = 0.8
n = 50
m = 50

def torus_triangles(n, m):
    """ Returns triangles to mesh a (n, m) torus """
    tri = []
    for i in range(n):
        for j in range(m):
            a = i + j*(n)
            b = ((i+1) % n) + j*n
            d = i + ((j+1) % m) * n
            c = ((i+1) % n) + ((j+1) % m) * n
            tri += [[a, b, d], [b, c, d]]
    return np.array(tri, dtype=np.int32)

theta0 = np.linspace(0, (2*np.pi), n, endpoint=False)
phi0 = np.linspace(0, (2*np.pi), m, endpoint=False)
theta, phi = np.meshgrid(theta0, phi0)

x = (R + r * np.sin(phi)) * np.cos(theta)
y = (R + r * np.sin(phi)) * np.sin(theta)
z = r * np.cos(phi)

triangles = torus_triangles(n , m)

mesh = mlab.triangular_mesh(x,y,z, triangles, representation='wireframe',color=(0,0,1) )

mlab.show()

Yielding:

enter image description here

Share:
10,795
Larry
Author by

Larry

Updated on June 04, 2022

Comments

  • Larry
    Larry about 2 years

    To make a long story short, I'd like to plot a generic 3D triangle mesh in python. Matplotlib seems to be the ideal candidate, but I'd go with any 3D rendering that can do what I'm about to describe.

    Suppose I have a triangle mesh defined by X, Y, and Z, the 3D coordinates of a point cloud, each a vector of length n, and UVW, a 2D m-x-3 matrix in which each row is a triplet of indices into the point cloud. This triplet represents an individual triangle. In other words, I have m triangles over n points. In Matlab, to generated a 3D plot, I just do:

    trisurf(UVW, X, Y, Z)
    

    Does anyone have any experience with this? In particular, can mplots trisurf be shoehorned to work?

  • Larry
    Larry almost 10 years
    Thanks! That's pretty close, but it looks like it requires X, Y, and Z to exist on a rectangular grid, which, in my case, they do not.
  • GBy
    GBy almost 10 years
    No need to be on a rectangular mesh, in fact if you look at the mesh I had to split each rectangle into 2 triangles in order to be able to use ´plot_trisurf´.
  • Andrea Lazzarotto
    Andrea Lazzarotto over 7 years
    Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.