Mayavi: interpolate face colors in triangular_mesh - python

I have pieced together the
following code to plot a triangular mesh with the colors specified by an
additional scalar function:
#! /usr/bin/env python
import numpy as np
from mayavi import mlab
# Create cone
n = 8
t = np.linspace(-np.pi, np.pi, n)
z = np.exp(1j*t)
x = z.real.copy()
y = z.imag.copy()
z = np.zeros_like(x)
triangles = [(0, i, i+1) for i in range(n)]
x = np.r_[0, x]
y = np.r_[0, y]
z = np.r_[1, z]
t = np.r_[0, t]
# These are the scalar values for each triangle
f = np.mean(t[np.array(triangles)], axis=1)
# Plot it
mesh = mlab.triangular_mesh(x, y, z, triangles,
representation='wireframe',
opacity=0)
cell_data = mesh.mlab_source.dataset.cell_data
cell_data.scalars = f
cell_data.scalars.name = 'Cell data'
cell_data.update()
mesh2 = mlab.pipeline.set_active_attribute(mesh,
cell_scalars='Cell data')
mlab.pipeline.surface(mesh2)
mlab.show()
This works reasonably well. However, instead of having every triangle
with a uniform color and sharp transitions between the triangles, I'd
much rather have a smooth interpolation over the entire surface.
Is there a way to do that?

I think you want to use point data instead of cell data. With cell data, a single scalar value is not localized to any point. It is assigned to the entire face. It looks like you just want to assign the t data to the vertices instead. The default rendering of point scalars will smoothly interpolate across each face.
point_data = mesh.mlab_source.dataset.point_data
point_data.scalars = t
point_data.scalars.name = 'Point data'
point_data.update()
mesh2 = mlab.pipeline.set_active_attribute(mesh,
point_scalars='Point data')

Related

How to make 3-D sphere cluster?

#I want to generate 3-D sphere cluster which contains number of points in it.
I am using this code but I am unable to generate.#
import math
N = 50
thetavec = np.linspace(0,pi,N)
phivec = np.linspace(0,2*pi,2*N)
[th, ph] = np.meshgrid(thetavec,phivec)
R = np.ones(th.shape)
x = R*numpy.sin(th)*numpy.cos(ph)
y = R*numpy.sin(th)*numpy.sin(ph)
z = R*numpy.cos(th)
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z, cmap='viridis', edgecolor='none')
ax.set_title('sphere')
I have generated these spherical clusters from matlab but now I want to generate from python:
After that code I am getting points on the sphere but I want the points to be inside the sphere as shown in 1st image means the number of points building a cluster in spherical shape
Help me out
Problem 1: You're using math.sin and math.cos, which expect numbers but you're giving them arrays (th and ph). Use numpy.sin and numpy.cos
Problem 2: I think you have misunderstood how spherical coordinates work. In spherical coordinates, we have R, theta, and phi where R is the _distance of the point from the origin.
When you use R = 1, you get points on a sphere of radius 1. To get points inside the sphere, you have to use a smaller radius than 1. For example, if you want to create 10 concentric spheres inside your sphere of radius 1, define an rvec using linspace and add it to the meshgrid like so:
N = 50
thetavec = np.linspace(0,pi,N)
phivec = np.linspace(0,2*pi,2*N)
rvec = np.linspace(0, 1, 10)
[th, ph, R] = np.meshgrid(thetavec,phivec, rvec)
ax = plt.axes(projection='3d')
ax.plot(x.flatten(), y.flatten(), z.flatten(), '.')
Alternatively, if you don't want concentric circles but just want random points inside the sphere, you can use random numbers using numpy.random.rand. Unlike numpy.ones, numpy.random.rand requires the shape of the array as separate arguments instead of a single tuple, so we unpack the th.shape tuple using *th.shape.
N = 50
max_rad = 1
thetavec = np.linspace(0,pi,N)
phivec = np.linspace(0,2*pi,2*N)
[th, ph] = np.meshgrid(thetavec,phivec)
R = np.random.rand(*th.shape) * max_rad
x = R*np.sin(th)*np.cos(ph)
y = R*np.sin(th)*np.sin(ph)
z = R*np.cos(th)
ax = plt.axes(projection='3d')
ax.plot(x.flatten(), y.flatten(), z.flatten(), '*')

How can I calculate arbitrary values from a spline created with scipy.interpolate.Rbf?

I have several data points in 3 dimensional space (x, y, z) and have interpolated them using scipy.interpolate.Rbf. This gives me a spline nicely representing the surface of my 3D object. I would now like to determine several x and y pairs that have the same, arbitrary z value. I would like to do that in order to compute the cross section of my 3D object at any given value of z. Does someone know how to do that? Maybe there is also a better way to do that instead of using scipy.interpolate.Rbf.
Up to now I have evaluated the cross sections by making a contour plot using matplotlib.pyplot and extracting the displayed segments. 3D points and interpolated spline
segments extracted using a contour plot
I was able to solve the problem. I have calculated the area by triangulating the x-y data and cutting the triangles with the z-plane I wanted to calculate the cross-sectional area of (z=z0). Specifically, I have searched for those triangles whose z-values are both above and below z0. Then I have calculated the x and y values of the sides of these triangles where the sides are equal to z0. Then I use scipy.spatial.ConvexHull to sort the intersected points. Using the shoelace formula I can then determine the area.
I have attached the example code here:
import numpy as np
from scipy import spatial
import matplotlib.pyplot as plt
# Generation of random test data
n = 500
x = np.random.random(n)
y = np.random.random(n)
z = np.exp(-2*(x-.5)**2-4*(y-.5)**2)
z0 = .75
# Triangulation of the test data
triang= spatial.Delaunay(np.array([x, y]).T)
# Determine all triangles where not all points are above or below z0, i.e. the triangles that intersect z0
tri_inter=np.zeros_like(triang.simplices, dtype=np.int) # The triangles which intersect the plane at z0, filled below
i = 0
for tri in triang.simplices:
if ~np.all(z[tri] > z0) and ~np.all(z[tri] < z0):
tri_inter[i,:] = tri
i += 1
tri_inter = tri_inter[~np.all(tri_inter==0, axis=1)] # Remove all rows with only 0
# The number of interpolated values for x and y has twice the length of the triangles
# Because each triangle intersects the plane at z0 twice
x_inter = np.zeros(tri_inter.shape[0]*2)
y_inter = np.zeros(tri_inter.shape[0]*2)
for j, tri in enumerate(tri_inter):
# Determine which of the three points are above and which are below z0
points_above = []
points_below = []
for i in tri:
if z[i] > z0:
points_above.append(i)
else:
points_below.append(i)
# Calculate the intersections and put the values into x_inter and y_inter
t = (z0-z[points_below[0]])/(z[points_above[0]]-z[points_below[0]])
x_new = t * (x[points_above[0]]-x[points_below[0]]) + x[points_below[0]]
y_new = t * (y[points_above[0]]-y[points_below[0]]) + y[points_below[0]]
x_inter[j*2] = x_new
y_inter[j*2] = y_new
if len(points_above) > len(points_below):
t = (z0-z[points_below[0]])/(z[points_above[1]]-z[points_below[0]])
x_new = t * (x[points_above[1]]-x[points_below[0]]) + x[points_below[0]]
y_new = t * (y[points_above[1]]-y[points_below[0]]) + y[points_below[0]]
else:
t = (z0-z[points_below[1]])/(z[points_above[0]]-z[points_below[1]])
x_new = t * (x[points_above[0]]-x[points_below[1]]) + x[points_below[1]]
y_new = t * (y[points_above[0]]-y[points_below[1]]) + y[points_below[1]]
x_inter[j*2+1] = x_new
y_inter[j*2+1] = y_new
# sort points to calculate area
hull = spatial.ConvexHull(np.array([x_inter, y_inter]).T)
x_hull, y_hull = x_inter[hull.vertices], y_inter[hull.vertices]
# Calculation of are using the shoelace formula
area = 0.5*np.abs(np.dot(x_hull,np.roll(y_hull,1))-np.dot(y_hull,np.roll(x_hull,1)))
print('Area:', area)
plt.figure()
plt.plot(x_inter, y_inter, 'ro')
plt.plot(x_hull, y_hull, 'b--')
plt.triplot(x, y, triangles=tri_inter, color='k')
plt.show()

How to rotate a cylinder without causing a 'sheared' appearance

I have plotted a 'tear drop' shaped cylinder in matplotlib. To obtain the tear drop shape I plotted a normal cylinder from theta = 0 to theta = pi and an ellipse from theta = pi to theta = 2pi. However I am now trying to 'spin' the cylinder around it's axis which here is given conveniently by the z-axis.
I tried using the rotation matrix for rotating around the z-axis which Wikipedia gives as:
However when I try to rotate through -pi/3 radians, the cylinder becomes very disfigured.
Is there anyway to prevent this from happening?
Here is my code:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sin, cos, pi
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(0,2*pi, 1200)
Z = np.linspace(0,5,1000+600)
Z,theta = np.meshgrid(Z, theta)
X = []
Y = []
R = 0.003
#calculate the x and y values
for i in theta:
cnt = 0
tempX = []
tempY = []
for j in i:
#circle
if(i[0]<=pi):
tempX.append(R*cos(j))
tempY.append(R*sin(j))
cnt+=1
#ellipse
else:
tempX.append(R*cos(j))
tempY.append(0.006*sin(j))
X.append(tempX)
Y.append(tempY)
X1 = np.array(X)
Y1 = np.array(Y)
#rotate around the Z axis
a = -pi/3
for i in range(len(X)):
X1[i] = cos(a)*X1[i]-sin(a)*Y1[i]
Y1[i] = sin(a)*X1[i]+cos(a)*Y1[i]
#plot
ax.plot_surface(X1,Y1,Z,linewidth = 0, shade = True, alpha = 0.3)
ax.set_xlim(-0.01,0.01)
ax.set_ylim(-0.01, 0.01)
azimuth = 173
elevation = 52
ax.view_init(elevation, azimuth)
plt.show()
Your rotating is flawed: To calculate Y1[i] you need the old value of X1[i], but you already updated it. You can try something like
X1[i], Y1[i] = cos(a)*X1[i]-sin(a)*Y1[i], sin(a)*X1[i]+cos(a)*Y1[i]
if you want to make the matrix multiplication a bit more obvious (and fix the bug) you could also do the following (please doublecheck that the matrix is correct and that the multiplication is in the right order, I did not test this):
rotation_matrix = np.array([[cos(a), -sin(a)], [sin(a), cos(a)]])
x, y = zip(*[(x,y) # rotation_matrix for x,y in zip(x,y)])
the # is new in 3.5 and for numpy array it's defined to be the matrix multiplication. If you are on a version below 3.5 you can use np.dot.
The zip(*...) is necessary to get a pair of lists instead of a list of pairs. See also this answer

Symmetric streamplot with matplotlib

I'm trying to plot the streamlines of a magnetic field around a sphere using matplotlib, and it does work quite nicely. However, the resulting image is not symmetric, but it should be (I think).
This is the code used to generate the image. Excuse the length, but I thought it would be better than just posting a non-working snippet. Also, it's not very pythonic; that's because I converted it from Matlab, which was easier than I expected.
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def cart2spherical(x, y, z):
r = np.sqrt(x**2 + y**2 + z**2)
phi = np.arctan2(y, x)
theta = np.arccos(z/r)
if r == 0:
theta = 0
return (r, theta, phi)
def S(theta, phi):
S = np.array([[np.sin(theta)*np.cos(phi), np.cos(theta)*np.cos(phi), -np.sin(phi)],
[np.sin(theta)*np.sin(phi), np.cos(theta)*np.sin(phi), np.cos(phi)],
[np.cos(theta), -np.sin(theta), 0]])
return S
def computeB(r, theta, phi, a=1, muR=100, B0=1):
delta = (muR - 1)/(muR + 2)
if r > a:
Bspherical = B0*np.array([np.cos(theta) * (1 + 2*delta*a**3 / r**3),
np.sin(theta) * (delta*a**3 / r**3 - 1),
0])
B = np.dot(S(theta, phi), Bspherical)
else:
B = 3*B0*(muR / (muR + 2)) * np.array([0, 0, 1])
return B
Z, X = np.mgrid[-2.5:2.5:1000j, -2.5:2.5:1000j]
Bx = np.zeros(np.shape(X))
Bz = np.zeros(np.shape(X))
Babs = np.zeros(np.shape(X))
for i in range(len(X)):
for j in range(len(Z)):
r, theta, phi = cart2spherical(X[0, i], 0, Z[j, 0])
B = computeB(r, theta, phi)
Bx[i, j], Bz[i, j] = B[0], B[2]
Babs[i, j] = np.sqrt(B[0]**2 + B[1]**2 + B[2]**2)
fig=plt.figure()
ax=fig.add_subplot(111)
plt.streamplot(X, Z, Bx, Bz, color='k', linewidth=0.8*Babs, density=1.3,
minlength=0.9, arrowstyle='-')
ax.add_patch(Circle((0, 0), radius=1, facecolor='none', linewidth=2))
plt.axis('equal')
plt.axis('off')
fig.savefig('streamlines.pdf', transparent=True, bbox_inches='tight', pad_inches=0)
First of all, for curiosity, why would you want to plot symmetric data? Why plotting half of isn't fine?
Said that, this is a possible hack. You can use mask arrays as Hooked suggested to plot half of it:
mask = X>0
BX_OUT = Bx.copy()
BZ_OUT = Bz.copy()
BX_OUT[mask] = None
BZ_OUT[mask] = None
res = plt.streamplot(X, Z, BX_OUT, BZ_OUT, color='k',
arrowstyle='-',linewidth=1,density=2)
then you save in res the result from streamplot, extract the lines and plot them with the opposite X coordinate.
lines = res.lines.get_paths()
for l in lines:
plot(-l.vertices.T[0],l.vertices.T[1],'k')
I used this hack to extract streamlines and arrows from a 2D plot, then apply a 3D transformation and plot it with mplot3d. A picture is in one of my questions here.
Quoting from the documentation:
density : float or 2-tuple
Controls the closeness of streamlines. When density = 1,
the domain is divided into
a 25x25 grid—density linearly scales this grid.
Each cell in the grid can have, at most, one traversing streamline.
For different densities in each direction, use [density_x, density_y].
so you are getting aliasing effects between the cells it uses to decide where the stream lines are, and the symmetries of your problem. You need to carefully choose your grid size (of the data) and the density.
It is also sensitive to where the box boundaries are relative to the top of the sphere. Is the center of your sphere on a data grid point or between the data grid points? If it is on a grid point then the box that contains the center point will be different than the boxes adjacent to it.
I am not familiar with exactly how it decides which stream lines to draw, but I could imagine that it is some sort of greedy algorithm and hence will give different results walking towards the high density region and away density region.
To be clear, you issue is not that the stream lines are wrong, they are valid stream lines, it is that you find the result not aesthetically pleasing.
Use a mask to separate the two regions of interest:
mask = np.sqrt(X**2+Z**2)<1
BX_OUT = Bx.copy()
BZ_OUT = Bz.copy()
BX_OUT[mask] = None
BZ_OUT[mask] = None
plt.streamplot(X, Z, BX_OUT, BZ_OUT, color='k',
arrowstyle='-', density=2)
BX_IN = Bx.copy()
BZ_IN = Bz.copy()
BX_IN[~mask] = None
BZ_IN[~mask] = None
plt.streamplot(X, Z, BX_IN, BZ_IN, color='r',
arrowstyle='-', density=2)
The resulting plot isn't exactly symmetric, but by giving the algorithm a hint, it's far closer than what you had before. Play with the density of the grid via meshgrid and the density parameter to achieve the effect you are looking for.
Use physics, instead... The magnetic field is symmetrical with respect to the z (vertical) axis! So you just need two streamplot's:
plt.streamplot(X, Z, Bx, Bz, color='k', linewidth=0.8*Babs, density=1.3, minlength=0.9, arrowstyle='-')
plt.streamplot(-X, Z, -Bx, Bz, color='k', linewidth=0.8*Babs, density=1.3, minlength=0.9, arrowstyle='-')

Using numpy/scipy to calculate iso-surface from 3D array

I have a 3D numpy array that contains the values of a given function. I want to calculate a 2D iso-surface, or a set of iso-surfaces that represent certain values of this function.
In this particular case, each 1D column (column = myarray[i, j, :]) of the 3D array can be treated independently. So what I would like to know are the last index positions (2D array) where the function is equal to a certain value, say myvalue.
Some (slow) code to exemplify:
# myarray = 3D ndarray
import numpy as np
from scipy import interpolate
result = np.zeros(nx, ny)
z_values = np.arange(nz)
for i in range(nx):
for j in range(ny):
f = interpolate.interp1d(my_array[i, j], z_values)
result[i, j] = f(myvalue)
I know this can be sped up a bit with np.ndenumerate and other tricks, but was wondering if there is already a simpler way of doing this kind of iso-surface. I couldn't find anything in ndimage or other libraries. I know that mayavi2 and vtk have a lot of tools to deal with iso-surfaces, but my aim here is not visualisation -- I want to perform calculations on those iso-surface values, not display them. Plus, a lot of the iso-surface methods of vtk seem to involve polygons and the like, and what I need is just a 2D array of positions for each iso-surface value.
Using only numpy you can get a good solution using argsort, sort, take and the proper array manipulation. The function below uses a weighted average to compute the iso-surface:
def calc_iso_surface(my_array, my_value, zs, interp_order=6, power_parameter=0.5):
if interp_order < 1: interp_order = 1
from numpy import argsort, take, clip, zeros
dist = (my_array - my_value)**2
arg = argsort(dist,axis=2)
dist.sort(axis=2)
w_total = 0.
z = zeros(my_array.shape[:2], dtype=float)
for i in xrange(int(interp_order)):
zi = take(zs, arg[:,:,i])
valuei = dist[:,:,i]
wi = 1/valuei
clip(wi, 0, 1.e6, out=wi) # avoiding overflows
w_total += wi**power_parameter
z += zi*wi**power_parameter
z /= w_total
return z
This solution does not handle situations where there is more than one z corresponding to my_value. An application example to build the iso-surfaces below is given in the following code:
from numpy import meshgrid, sin, cos, pi, linspace
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
dx = 100; dy = 50; dz = 25
nx = 200; ny = 100; nz = 100
xs = linspace(0,dx,nx)
ys = linspace(0,dy,ny)
zs = linspace(0,dz,nz)
X,Y,Z = meshgrid( xs, ys, zs, dtype=float)
my_array = sin(0.3*pi+0.4*pi*X/dx)*sin(0.3*pi+0.4*pi*Y/dy)*(Z/dz)
fig = plt.figure()
ax = fig.gca(projection='3d')
z = calc_iso_surface( my_array, my_value=0.1, zs=zs, interp_order=6 )
ax.plot_surface(X[:,:,0], Y[:,:,0], z, cstride=4, rstride=4, color='g')
z = calc_iso_surface( my_array, my_value=0.2, zs=zs, interp_order=6 )
ax.plot_surface(X[:,:,0], Y[:,:,0], z, cstride=4, rstride=4, color='y')
z = calc_iso_surface( my_array, my_value=0.3, zs=zs, interp_order=6 )
ax.plot_surface(X[:,:,0], Y[:,:,0], z, cstride=4, rstride=4, color='b')
plt.ion()
plt.show()
You can also play with different interpolation functions. See below one example that takes the average of the two closest zs:
def calc_iso_surface_2(my_array, my_value, zs):
'''Takes the average of the two closest zs
'''
from numpy import argsort, take
dist = (my_array - my_value)**2
arg = argsort(dist,axis=2)
z0 = take(zs, arg[:,:,0])
z1 = take(zs, arg[:,:,1])
z = (z0+z1)/2
return z

Categories

Resources