Related
I have a 3d velocity vector field in a numpy array of shape (zlength, ylength, xlength, 3). The '3' contains the velocity components (u,v,w).
I can quite easily plot the vector field in the orthogonal x-y, x-z, and y-z planes using quiver, e.g.
X, Y = np.meshgrid(xvalues, yvalues)
xyfieldfig = plt.figure()
xyfieldax = xyfieldfig.add_subplot(111)
Q1 = xyfieldax.quiver(X, Y, velocity_field[zslice,:,:,0], velocity_field[zslice,:,:,1])
However, I'd like to be able to view the velocity field within an arbitrary plane.
I tried to project the velocity field onto a plane by doing:
projected_field = np.zeros(zlength,ylength,xlength,3)
normal = (nx,ny,nz) #normalised normal to the plane
for i in range(zlength):
for j in range(ylength):
for k in range(xlength):
projected_field[i,j,m] = velocity_field[i,j,m] - np.dot(velocity_field[i,j,m], normal)*normal
However, this (of course) still leaves me with a 3d numpy array with the same shape: (zlength, ylength, xlength, 3). The projected_field now contains velocity vectors at each (x,y,z) position that lie within planes at each local (x,y,z) position.
How do I project velocity_field onto a single plane? Or, how do I now plot my projected_field along one plane?
Thanks in advance!
You're close. Daniel F's suggestion was right, you just need to know how to do the interpolation. Here's a worked example
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
import scipy.interpolate
def norm(v,axis=0):
return np.sqrt(np.sum(v**2,axis=axis))
#Original velocity field
xpoints = np.arange(-.2, .21, 0.05)
ypoints = np.arange(-.2, .21, 0.05)
zpoints = np.arange(-.2, .21, 0.05)
x, y, z = np.meshgrid(xpoints,ypoints,zpoints,indexing='ij')
#Simple example
#(u,v,w) are the components of your velocity field
u = x
v = y
w = z
#Setup a template for the projection plane. z-axis will be rotated to point
#along the plane normal
planex, planey, planez =
np.meshgrid(np.arange(-.2,.2001,.1),
np.arange(-.2,.2001,.1), [0.1],
indexing='ij')
planeNormal = np.array([0.1,0.4,.4])
planeNormal /= norm(planeNormal)
#pick an arbirtrary vector for projection x-axis
u0 = np.array([-(planeNormal[2] + planeNormal[1])/planeNormal[0], 1, 1])
u1 = -np.cross(planeNormal,u0)
u0 /= norm(u0)
u1 /= norm(u1)
#rotation matrix
rotation = np.array([u0,u1,planeNormal]).T
#Rotate plane to get projection vertices
rotatedVertices = rotation.dot( np.array( [planex.flatten(), planey.flatten(), planez.flatten()]) ).T
#Now you can interpolate gridded vector field to rotated vertices
uprime = scipy.interpolate.interpn( (xpoints,ypoints,zpoints), u, rotatedVertices, bounds_error=False )
vprime = scipy.interpolate.interpn( (xpoints,ypoints,zpoints), v, rotatedVertices, bounds_error=False )
wprime = scipy.interpolate.interpn( (xpoints,ypoints,zpoints), w, rotatedVertices, bounds_error=False )
#Projections
cosineMagnitudes = planeNormal.dot( np.array([uprime,vprime,wprime]) )
uProjected = uprime - planeNormal[0]*cosineMagnitudes
vProjected = vprime - planeNormal[1]*cosineMagnitudes
wProjected = wprime - planeNormal[2]*cosineMagnitudes
The number of lines could be reduced using some tensordot operations if you wanted to get fancy. Also this or some close variant it would work without indexing='ij' in meshgrid.
Original field:
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(x, y, z, u, v, w, length=0.1, normalize=True)
Projected field:
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(rotatedVertices[:,0], rotatedVertices[:,1], rotatedVertices[:,2],
uprime, vprime,wprime, length=0.5, color='blue', label='Interpolation only')
ax.quiver(rotatedVertices[:,0], rotatedVertices[:,1], rotatedVertices[:,2],
uProjected, vProjected, wProjected, length=0.5, color='red', label='Interpolation + Projection')
plt.legend()
I am analyzing the magnetization mapping of a sample. After getting the gradient and its direction, I plotted them as an HSV (the direction from -π to π was mapped to Hue from 0 to 1, and Value was the normalized gradient) converted to RGB by img_rgb = mpl.colors.hsv_to_rgb(img_hsv).
I managed to add an HSV colorbar by using vmin and vmax, but this does not show the magnitude of the gradient:
plt.imshow(img_rgb, cmap='hsv', vmin=-180, vmax=180, extent=(0, 100, 0,100))
plt.xlabel('μm')
plt.ylabel('μm')
plt.colorbar()
My current plot:
Ideally, I would like to add a color wheel which encodes both the direction and the magnitude (maybe as something like a polar plot?). If that is not possible, adding a 2D plot which extends the current colorbar to include the gradient magnitude on the x-axis.
Subplots are obviously possible, but they seem like a kludge. Is there a better way?
First off, if you have two different parameters that you want to visualise simultaneously, you can do that by assigning two different channels to them (say red and green). This can be done by normalising your two 2d arrays and feeding them to imshow stacked similarly to this answer.
If you are content with a square-shaped 2d colormap, you can then get this colormap in the same way, by creating a meshgrid that you then again stack and feed to imshow:
from matplotlib import pyplot as plt
import numpy as np
##generating some data
x,y = np.meshgrid(
np.linspace(0,1,100),
np.linspace(0,1,100),
)
directions = (np.sin(2*np.pi*x)*np.cos(2*np.pi*y)+1)*np.pi
magnitude = np.exp(-(x*x+y*y))
##normalize data:
def normalize(M):
return (M-np.min(M))/(np.max(M)-np.min(M))
d_norm = normalize(directions)
m_norm = normalize(magnitude)
fig,(plot_ax, bar_ax) = plt.subplots(nrows=1,ncols=2,figsize=(8,4))
plot_ax.imshow(
np.dstack((d_norm,m_norm, np.zeros_like(directions))),
aspect = 'auto',
extent = (0,100,0,100),
)
bar_ax.imshow(
np.dstack((x, y, np.zeros_like(x))),
extent = (
np.min(directions),np.max(directions),
np.min(magnitude),np.max(magnitude),
),
aspect = 'auto',
origin = 'lower',
)
bar_ax.set_xlabel('direction')
bar_ax.set_ylabel('magnitude')
plt.show()
The result looks like this:
In principle the same thing should also be doable with a polar Axes, but according to a comment in this github ticket, imshow does not support polar axes and I couldn't make imshow fill the entire disc.
EDIT:
Thanks to ImportanceOfBeingErnest and his answer to another question (the color keyword did it), here now a 2d colormap on a polar axis using pcolormesh. There were a few caveats, most notable, the colors dimension needs to be one smaller than the meshgrid in theta direction, otherwise the colormap has a spiral form:
fig= plt.figure(figsize=(8,4))
plot_ax = fig.add_subplot(121)
bar_ax = fig.add_subplot(122, projection = 'polar')
plot_ax.imshow(
np.dstack((d_norm,m_norm, np.zeros_like(directions))),
aspect = 'auto',
extent = (0,100,0,100),
)
theta, R = np.meshgrid(
np.linspace(0,2*np.pi,100),
np.linspace(0,1,100),
)
t,r = np.meshgrid(
np.linspace(0,1,99),
np.linspace(0,1,100),
)
image = np.dstack((t, r, np.zeros_like(r)))
color = image.reshape((image.shape[0]*image.shape[1],image.shape[2]))
bar_ax.pcolormesh(
theta,R,
np.zeros_like(R),
color = color,
)
bar_ax.set_xticks(np.linspace(0,2*np.pi,5)[:-1])
bar_ax.set_xticklabels(
['{:.2}'.format(i) for i in np.linspace(np.min(directions),np.max(directions),5)[:-1]]
)
bar_ax.set_yticks(np.linspace(0,1,5))
bar_ax.set_yticklabels(
['{:.2}'.format(i) for i in np.linspace(np.min(magnitude),np.max(magnitude),5)]
)
bar_ax.grid('off')
plt.show()
This produces this figure:
I am having a similar problem when trying to visualize the radial and absolute components of a surface gradient.
I am converting the absolute value of the gradient plus the angle to a color via hsv (using the hue as the angle and the saturation and the value as the absolute value). This is the same as in magnetization plots as any vector field can be used replacing the gradient. The following function illustrates the idea. The full code is provided in the end of the answer.
import matplotlib.colors
# gradabs is the absolute gradient value,
# gradang is the angle direction, z the vector field
# the gradient was calculated of
max_abs = np.max(gradabs)
def grad_to_rgb(angle, absolute):
"""Get the rgb value for the given `angle` and the `absolute` value
Parameters
----------
angle : float
The angle in radians
absolute : float
The absolute value of the gradient
Returns
-------
array_like
The rgb value as a tuple with values [0..1]
"""
global max_abs
# normalize angle
angle = angle % (2 * np.pi)
if angle < 0:
angle += 2 * np.pi
return matplotlib.colors.hsv_to_rgb((angle / 2 / np.pi,
absolute / max_abs,
absolute / max_abs))
# convert to colors via hsv
grad = np.array(list(map(grad_to_rgb, gradang.flatten(), gradabs.flatten())))
# reshape
grad = grad.reshape(tuple(list(z.shape) + [3]))
The resulting graph is the following.
The full example code on showing the gradient field of a surface:
import numpy as np
import matplotlib.colors
import matplotlib.pyplot as plt
r = np.linspace(0, np.pi, num=100)
x, y = np.meshgrid(r, r)
z = np.sin(y) * np.cos(x)
fig = plt.figure()
ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.plot_surface(x, y, z)
# ax.imshow(z)
ax.set_title("Surface")
ax = fig.add_subplot(1, 3, 2)
ax.set_title("Gradient")
# create gradient
grad_y, grad_x = np.gradient(z)
# calculate length
gradabs = np.sqrt(np.square(grad_x) + np.square(grad_y))
max_abs = np.max(gradabs)
# calculate angle component
gradang = np.arctan2(grad_y, grad_x)
def grad_to_rgb(angle, absolute):
"""Get the rgb value for the given `angle` and the `absolute` value
Parameters
----------
angle : float
The angle in radians
absolute : float
The absolute value of the gradient
Returns
-------
array_like
The rgb value as a tuple with values [0..1]
"""
global max_abs
# normalize angle
angle = angle % (2 * np.pi)
if angle < 0:
angle += 2 * np.pi
return matplotlib.colors.hsv_to_rgb((angle / 2 / np.pi,
absolute / max_abs,
absolute / max_abs))
# convert to colors via hsv
grad = np.array(list(map(grad_to_rgb, gradang.flatten(), gradabs.flatten())))
# reshape
grad = grad.reshape(tuple(list(z.shape) + [3]))
ax.imshow(grad)
n = 5
gx, gy = np.meshgrid(np.arange(z.shape[0] / n), np.arange(z.shape[1] / n))
ax.quiver(gx * n, gy * n, grad_x[::n, ::n], grad_y[::n, ::n])
# plot color wheel
# Generate a figure with a polar projection, inspired by
# https://stackoverflow.com/a/48253413/5934316
ax = fig.add_subplot(1, 3, 3, projection='polar')
n = 200 # the number of secants for the mesh
t = np.linspace(0, 2 * np.pi, n)
r = np.linspace(0, max_abs, n)
rg, tg = np.meshgrid(r, t)
c = np.array(list(map(grad_to_rgb, tg.T.flatten(), rg.T.flatten())))
cv = c.reshape((n, n, 3))
m = ax.pcolormesh(t, r, cv[:,:,1], color=c, shading='auto')
m.set_array(None)
ax.set_yticklabels([])
plt.show()
I need to plot contour and quiver plots of scalar and vector fields defined on an uneven grid in (r,theta) coordinates.
As a minimal example of the problem I have, consider the contour plot of a Stream function for a magnetic dipole, contours of such a function are streamlines of the corresponeding vector field (in this case, the magnetic field).
The code below takes an uneven grid in (r,theta) coordinates, maps it to the cartesian plane and plots a contour plot of the stream function.
import numpy as np
import matplotlib.pyplot as plt
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
psi = np.zeros((N_r, N_theta))
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig, ax = plt.subplots()
# ax.plot(x,y,'b.') # plot grid points
ax.set_aspect('equal')
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
plt.show()
For some reason though, the plot I get doesn't look right:
If I interchange x and y in the contour function call, I get the desired result:
Same thing happens when I try to make a quiver plot of a vector field defined on the same grid and mapped to the x-y plane, except that interchanging x and y in the function call no longer works.
It seems like I made a stupid mistake somewhere but I can't figure out what it is.
If psi = m * np.sin(theta_matrix)**2 / r_matrix
then psi increases as theta goes from 0 to pi/2 and psi decreases as r increases.
So a contour line for psi should increase in r as theta increases. That results
in a curve that goes counterclockwise as it radiates out from the center. This is
consistent with the first plot you posted, and the result returned by the first version of your code with
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
An alternative way to confirm the plausibility of the result is to look at a surface plot of psi:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_aspect('equal')
ax.plot_surface(x, y, psi, rstride=8, cstride=8, alpha=0.3)
ax.contour(x, y, psi, colors='black',levels=contour_levels)
plt.show()
I want to plot N planes (say 10) parallel to XZ axis and equidistant to each other using python. If possible it would be nice to select the number of planes from user. It will be like, if user gives "20" then 20 planes will be drawn in 3D. This is what I did.But I would like to know is there a method to call each plane or like to get each plane's equation ??
import numpy as np
import itertools
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
plt3d = plt.figure().gca(projection='3d')
xx, zz = np.meshgrid(range(10), range(10))
yy =0.5
for _ in itertools.repeat(None, 20):
plt3d.plot_surface(xx, yy, zz)
plt.hold(True)
yy=yy+.1
plt.show()
Here is an example how to implement what you need in a very generic way.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
from pylab import meshgrid,linspace,zeros,dot,norm,cross,vstack,array,matrix,sqrt
def rotmatrix(axis,costheta):
""" Calculate rotation matrix
Arguments:
- `axis` : Rotation axis
- `costheta` : Rotation angle
"""
x,y,z = axis
c = costheta
s = sqrt(1-c*c)
C = 1-c
return matrix([[ x*x*C+c, x*y*C-z*s, x*z*C+y*s ],
[ y*x*C+z*s, y*y*C+c, y*z*C-x*s ],
[ z*x*C-y*s, z*y*C+x*s, z*z*C+c ]])
def plane(Lx,Ly,Nx,Ny,n,d):
""" Calculate points of a generic plane
Arguments:
- `Lx` : Plane Length first direction
- `Ly` : Plane Length second direction
- `Nx` : Number of points, first direction
- `Ny` : Number of points, second direction
- `n` : Plane orientation, normal vector
- `d` : distance from the origin
"""
x = linspace(-Lx/2,Lx/2,Nx)
y = linspace(-Ly/2,Ly/2,Ny)
# Create the mesh grid, of a XY plane sitting on the orgin
X,Y = meshgrid(x,y)
Z = zeros([Nx,Ny])
n0 = array([0,0,1])
# Rotate plane to the given normal vector
if any(n0!=n):
costheta = dot(n0,n)/(norm(n0)*norm(n))
axis = cross(n0,n)/norm(cross(n0,n))
rotMatrix = rotmatrix(axis,costheta)
XYZ = vstack([X.flatten(),Y.flatten(),Z.flatten()])
X,Y,Z = array(rotMatrix*XYZ).reshape(3,Nx,Ny)
dVec = (n/norm(n))*d
X,Y,Z = X+dVec[0],Y+dVec[1],Z+dVec[2]
return X,Y,Z
if __name__ == "__main__":
# Plot as many planes as you like
Nplanes = 10
# Set color list from a cmap
colorList = cm.jet(linspace(0,1,Nplanes))
# List of Distances
distList = linspace(-10,10,Nplanes)
# Plane orientation - normal vector
normalVector = array([0,1,1]) # Y direction
# Create figure
fig = plt.figure()
ax = fig.gca(projection='3d')
# Plotting
for i,ypos in enumerate(linspace(-10,10,10)):
# Calculate plane
X,Y,Z = plane(20,20,100,100,normalVector,distList[i])
ax.plot_surface(X, Y, Z, rstride=5, cstride=5,
alpha=0.8, color=colorList[i])
# Set plot display parameters
ax.set_xlabel('X')
ax.set_xlim(-10, 10)
ax.set_ylabel('Y')
ax.set_ylim(-10, 10)
ax.set_zlabel('Z')
ax.set_zlim(-10, 10)
plt.show()
If you need to rotate the plane around the normal vector, you can also use the rotation matrix for that.
Cheers
I am trying to use python3 and matplotlib (version 1.4.0) to plot a scalar function defined on the surface of a sphere. I would like to have faces distributed relatively evenly over the sphere, so I am not using a meshgrid. This has led me to use plot_trisurf to plot my function. I have tested it with a trivial scalar function, and am having the problem that there are rendering artefacts along the edges of the faces:
The code I used to create the plot is below:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.tri as mtri
from scipy.spatial import ConvexHull
def points_on_sphere(N):
""" Generate N evenly distributed points on the unit sphere centered at
the origin. Uses the 'Golden Spiral'.
Code by Chris Colbert from the numpy-discussion list.
"""
phi = (1 + np.sqrt(5)) / 2 # the golden ratio
long_incr = 2*np.pi / phi # how much to increment the longitude
dz = 2.0 / float(N) # a unit sphere has diameter 2
bands = np.arange(N) # each band will have one point placed on it
z = bands * dz - 1 + (dz/2) # the height z of each band/point
r = np.sqrt(1 - z*z) # project onto xy-plane
az = bands * long_incr # azimuthal angle of point modulo 2 pi
x = r * np.cos(az)
y = r * np.sin(az)
return x, y, z
def average_g(triples):
return np.mean([triple[2] for triple in triples])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = points_on_sphere(2**12)
Triples = np.array(list(zip(X, Y, Z)))
hull = ConvexHull(Triples)
triangles = hull.simplices
colors = np.array([average_g([Triples[idx] for idx in triangle]) for
triangle in triangles])
collec = ax.plot_trisurf(mtri.Triangulation(X, Y, triangles),
Z, shade=False, cmap=plt.get_cmap('Blues'), array=colors,
edgecolors='none')
collec.autoscale()
plt.show()
This problem appears to have been discussed in this question, but I can't seem to figure out how to set the edgecolors to match the facecolors. The two things I've tried are setting edgecolors='face' and calling collec.set_edgecolors() with a variety of arguments, but those throw AttributeError: 'Poly3DCollection' object has no attribute '_facecolors2d'.
How am I supposed to set the edgecolor equal to the facecolor in a trisurf plot?
You can set antialiased argument of plot_trisurf() to False. Here is the result: