Modelling an asteroid with Matplotlib using surface and wireframe - python

I am trying to model an asteroid using plot_surface and plot_wireframe. I have x y and z values for the points on the surface of the asteroid. The wireframe is accurate to the shape of the asteroid but the surface plot does not fit the wireframe. How can I get the surface plot to fit the wireframe or how could i use the wireframe to get a 3d solid model? Here is my code for the model:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.mlab import griddata
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
data = np.genfromtxt('data.txt')
x = data[:,0]
y = data[:,1]
z = data[:,2]
ax.plot_wireframe(x, y, z, rstride=1, cstride=1, alpha=1)
xi = np.linspace(min(x), max(x))
yi = np.linspace(min(y), max(y))
X, Y = np.meshgrid(xi, yi)
Z = griddata(x, y, z, xi, yi)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
plt.show()
The data is in this format, although there are a lot more lines in the original file:
-1.7738946051191869E-002 4.3461451610545973E-002 1.3393057231408241
-0.29733561550902488 0.32305812106837900 1.3393057231408241
-0.29733561550902488 0.16510132228266330 1.3548631099230350
-0.21872587865015569 2.4170900455101410E-002 1.3610011616437809
1.4452975249810950E-002 -0.20900795344486520 1.3610011616437809
1.5732454381265970E-002 -0.20900795344486520 1.3608751439485580
-0.34501536374240321 0.51320241386595655 1.3158820995876130
-0.40193014435941982 0.45628763324893978 1.3158820995876130
-0.42505849480150409 0.28183419537116011 1.3307863198123011
-0.18994178462386799 -0.19294290416565860 1.3424523041534830
1.4452975249810939E-002 -0.39733766403933751 1.3424523041534830
5.8021940902131752E-002 -0.57108837516584876 1.3210481842104100
9.3746267961881152E-002 -0.61017602710257668 1.3136798474111200
0.26609469681891229 -0.43782759824554562 1.3136798474111200
0.17938460413447810 0.39179924148155021 1.2357401964919650
8.9613011902522258E-002 0.42818009222325598 1.2584008460875080
0.33671539027096409 -0.47165177581327772 1.2965073126705291
0.53703772594296528 -0.47165177581327777 1.2357401964919561
-0.19242375014122229 0.71021685426700043 1.2584008460875080
-0.34501536374240321 0.66763766324752027 1.2904902860951690
Hope you can help

Could you provide an image of what is happening? I'm guessing that you are getting an image where the surface of the asteroid seems to be jumping all over the place. Is that correct? If so it could be caused by the plotter not knowing the order of the points.
If we have a set of points, which contains all the points of a unit circle, then we would expect a drawing of those points to create a unit circle. If, however, you decided to connect each point to two other points, then it wouldn't necessarily look like a circle. If (for some reason) you connected one point to another point on the other side of the circle and continued to do that until each point was connected to two other points, it may or may not look like a circle because each point isn't necessarily connected to the adjacent points.
The same is true for the your asteroid. You need to come up with some scheme so that the plotter knows how to connect the points, otherwise you will continue to have the same problem.
The following example of a circle should illustrate my point:
import math
import matplotlib.pylab as plt
import random
thetaList = range(360)
random.shuffle(thetaList)
degToRad = lambda x: float(x) * math.pi / float(180)
x = [math.cos(degToRad(theta)) for theta in thetaList]
y = [math.sin(degToRad(theta)) for theta in thetaList]
#plot the cirlce
plt.plot(x,y)
plt.show()

Related

Only plot part of a 3d figure using matplotlib

I got a problem when I was plotting a 3d figure using matplotlib of python. Using the following python function, I got this figure:
Here X, Y are meshed grids and Z and Z_ are functions of X and Y. C stands for surface color.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
def plot(X, Y, Z, Z_, C):
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(
X, Y, Z, rstride=1, cstride=1,
facecolors=cm.jet(C),
linewidth=0, antialiased=False, shade=False)
surf_ = ax.plot_surface(
X, Y, Z_, rstride=1, cstride=1,
facecolors=cm.jet(C),
linewidth=0, antialiased=False, shade=False)
ax.view_init(elev=7,azim=45)
plt.show()
But now I want to cut this figure horizontally and only the part whose z is between -1 and 2 remain.
What I want, plotted with gnuplot, is this:
I have tried ax.set_zlim3d and ax.set_zlim, but neither of them give me the desired figure. Does anybody know how to do it using python?
Nice conical intersections you have there:)
What you're trying to do should be achieved by setting the Z data you want to ignore to NaN. Using graphene's tight binding band structure as an example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# generate dummy data (graphene tight binding band structure)
kvec = np.linspace(-np.pi,np.pi,101)
kx,ky = np.meshgrid(kvec,kvec)
E = np.sqrt(1+4*np.cos(3*kx/2)*np.cos(np.sqrt(3)/2*ky) + 4*np.cos(np.sqrt(3)/2*ky)**2)
# plot full dataset
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(kx,ky,E,cmap='viridis',vmin=-E.max(),vmax=E.max(),rstride=1,cstride=1)
ax.plot_surface(kx,ky,-E,cmap='viridis',vmin=-E.max(),vmax=E.max(),rstride=1,cstride=1)
# focus on Dirac cones
Elim = 1 #threshold
E[E>Elim] = np.nan
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#ax.plot_surface(kx2,ky2,E2,cmap='viridis',vmin=-Elim,vmax=Elim)
#ax.plot_surface(kx2,ky2,-E2,cmap='viridis',vmin=-Elim,vmax=Elim)
ax.plot_surface(kx,ky,E,cmap='viridis',rstride=1,cstride=1,vmin=-Elim,vmax=Elim)
ax.plot_surface(kx,ky,-E,cmap='viridis',rstride=1,cstride=1,vmin=-Elim,vmax=Elim)
plt.show()
The results look like this:
Unfortunately, there are problems with the rendering of the second case: the apparent depth order of the data is messed up in the latter case: cones in the background are rendered in front of the front ones (this is much clearer in an interactive plot). The problem is that there are more holes than actual data, and the data is not connected, which confuses the renderer of plot_surface. Matplotlib has a 2d renderer, so 3d visualization is a bit of a hack. This means that for complex overlapping surfaces you'll more often than not get rendering artifacts (in particular, two simply connected surfaces are either fully behind or fully in front of one another).
We can get around the rendering bug by doing a bit more work: keeping the data in a single surface by not using nans, but instead colouring the the surface to be invisible where it doesn't interest us. Since the surface we're plotting now includes the entire original surface, we have to set the zlim manually in order to focus on our region of interest. For the above example:
from matplotlib.cm import get_cmap
# create a color mapping manually
Elim = 1 #threshold
cmap = get_cmap('viridis')
colors_top = cmap((E + Elim)/2/Elim) # listed colormap that maps E from [-Elim, Elim] to [0.0, 1.0] for color mapping
colors_bott = cmap((-E + Elim)/2/Elim) # same for -E branch
colors_top[E > Elim, -1] = 0 # set outlying faces to be invisible (100% transparent)
colors_bott[-E < -Elim, -1] = 0
# in nature you would instead have something like this:
#zmin,zmax = -1,1 # where to cut the _single_ input surface (x,y,z)
#cmap = get_cmap('viridis')
#colors = cmap((z - zmin)/(zmax - zmin))
#colors[(z < zmin) | (z > zmax), -1] = 0
# then plot_surface(x, y, z, facecolors=colors, ...)
# or for your specific case where you have X, Y, Z and C:
#colors = get_cmap('viridis')(C)
#colors[(z < zmin) | (z > zmax), -1] = 0
# then plot_surface(x, y, z, facecolors=colors, ...)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# pass the mapped colours as the facecolors keyword arg
s1 = ax.plot_surface(kx, ky, E, facecolors=colors_top, rstride=1, cstride=1)
s2 = ax.plot_surface(kx, ky, -E, facecolors=colors_bott, rstride=1, cstride=1)
# but now we need to manually hide the invisible part of the surface:
ax.set_zlim(-Elim, Elim)
plt.show()
Here's the output:
Note that it looks a bit different from the earlier figures because 3 years have passed in between and the current version of matplotlib (3.0.2) has very different (and much prettier) default styles. In particular, edges are now transparent in surface plots. But the main point is that the rendering bug is gone, which is evident if you start rotating the surface around in an interactive plot.

Draw seamless distribution of tweets

I've collected tweets from twitter now I'm trying to draw the distribution of tweets geographically. To do that, I divide the entire square area into small square and count number of tweets in each square. Finally, I use matplotlib to draw the following figure:
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.3, cmap='Accent')
The problem is that the elevation map is not smooth. I'd like a way to draw smooth curve from the data. One example for that in 2D is when we have a histogram of image, we can draw smooth curve over the distribution as follows:
So my question is that is there a way to draw a smooth surface from the discrete data?
Expanding on my answer, here's what you can get with resampling and smoothing (gaussian_filter())/spline interpolation (RectBivariateSpline). Note that it would be nice of you to provide a template code that plots your graph, but since you haven't, I had to improvise.
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def plot(name, method):
numpy.random.seed(123)
x = numpy.linspace(0, 50, 51)
X, Y = numpy.meshgrid(x, x)
Z = numpy.zeros((x.size, x.size))
for n in range(50):
i = numpy.random.randint(0, x.size)
j = numpy.random.randint(0, x.size)
Z[i, j] = numpy.abs(numpy.random.normal()) * 1000
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
if method == 0:
# regular plot
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.3, cmap='Accent')
else:
# create a finer grid
resample_coeff = 2
Z2 = numpy.repeat(Z, resample_coeff, 0).repeat(resample_coeff, 1)
x2 = numpy.linspace(x[0], x[-1], x.size * resample_coeff)
X2, Y2 = numpy.meshgrid(x2, x2)
if method == 1:
# smoothing
from scipy.ndimage.filters import gaussian_filter
Z2 = gaussian_filter(Z2, 1)
elif method == 2:
# interpolation
from scipy.interpolate import RectBivariateSpline
spline = RectBivariateSpline(
x, x, Z, bbox=[x[0], x[-1], x[0], x[-1]])
Z2 = spline.ev(X2, Y2)
ax.plot_surface(X2, Y2, Z2, rstride=1, cstride=1, alpha=0.3, cmap='Accent')
fig.savefig(name)
if __name__ == '__main__':
plot('t0.png', 0)
plot('t1.png', 1)
plot('t2.png', 2)
Initial graph:
Smoothing:
Interpolation (notice the negative regions; that's polynomial interpolation for you):

How do I "force" pixel-registered grid for plotting x,y,z,s on 3d surface?

I want to plot a small, say 4x4 array on a plane in 3d. I have a 2d array s which I preview with plt.imshow and get a correct 4x4 cell image. However, when I plot s onto a x, y, z plane (x, y, and z are each a 4x4 array) I get a resampled(?) 3x3 cell image mapped onto the plane. Is there an easy way (there probably is...) to force plot_surface to treat the data points as pixel-registered with a known pixel size (dx,dy,dz) so that the data is plotted as a 4x4 cell image?
Here is a short and simple code to illustrate my problem:
s = np.random.rand(4,4)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.imshow(s, interpolation='nearest')
x = np.array([1,1,1,1])
y = np.array([1,2,3,4])
z = y.T
xi, yi = np.meshgrid(x, y)
zi = z*np.ones((4,4))
fig = plt.figure(2)
ax = fig.gca(projection='3d')
color = plt.cm.jet(s)
ax.plot_surface(xi, yi, zi,
rstride=1, cstride=1,
facecolors=color, shade=False,
vmin=s.min(), vmax=s.max())
Any suggestions?
So the solution I found to work for me is to calculate the vertices of the polygon around each data point using dx, dy, dz:
vx = [x-0.5*dx, x+0.5*dx, x+0.5*dx, x-0.5*dx]
vy = [y-0.5*dy, y+0.5*dy, y+0.5*dy, y-0.5*dy]
vz = [z-0.5*dz, z-0.5*dz, z+0.5*dz, z+0.5*dz]
then use Poly3DCollection from mpl_toolkits.mplot3d.art3d to draw the polygon.
I hope this helps someone else as-well.
Shahar

Shifting the triangles order to match colormap

This question is a sequel of a previous one but regarding this time the colormap and the order of the triangle. I want to interpolate experimental data over a surface so as to enable a continuous colormap with however the surface known only at its corner node. To interpolate, I put a canonical example which works quite well but fails on real data.
Indeed as shown in the example below, the initial triangulation results in two triangles with a huge gap between them, cf first picture. When the interpolation is done, it doesn't get any better and the colormap is also lost, cf. second picture. The best so far is by interverting z and y to get adjacent triangles from the beginning which results in a successful interpolation. However as you might notice in the third picture, the surface is tilted by 90° which is normal since I switch y for z and vice-versa.
However when I switch back y and z in the tri_surf function with ax.plot_trisurf(new.x, new_z, new.y, **kwargs), the colormap doesn't follow, cf. picture 4.
I thought of rotating the colormap in somehow or generate new triangles from the interpolated ones with triang = tri.Triangulation(new.x, new_z) but without any success. So any idea or hint about properly doing the initial triangulation with two adjacent triangles, as for the third picture, but with the surface oriented correclty and ultimately the colormap proportional to the Y-value.
import numpy
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.tri as tri
x=numpy.array([0.00498316, 0.00498316, 0.00996632, 0.00996632])
y=numpy.array([-0.00037677, -0.00027191, -0.00078681, -0.00088475])
z=numpy.array([0., -0.0049926, 0., -0.00744763])
# Initial Triangle
fig = plt.figure()
ax = Axes3D(fig)
triang = tri.Triangulation(x, y)
norm = plt.Normalize(vmax=y.max(), vmin=y.min())
ax.plot_trisurf(x, y, z, triangles=triang.triangles)
# Interpolated Triangle
fig = plt.figure()
ax = Axes3D(fig)
triang = tri.Triangulation(x, y)
refiner = tri.UniformTriRefiner(triang)
interpolator = tri.LinearTriInterpolator(triang, z)
new, new_z = refiner.refine_field(z, interpolator, subdiv=4)
kwargs = dict(triangles=new.triangles, cmap=cm.jet, norm=norm, linewidth=0, antialiased=False)
ax.plot_trisurf(new.x, new.y, new_z, **kwargs)
# Best so far
fig = plt.figure()
ax = Axes3D(fig)
triang = tri.Triangulation(x, z)
refiner = tri.UniformTriRefiner(triang)
interpolator = tri.LinearTriInterpolator(triang, y)
new, new_z = refiner.refine_field(y, interpolator, subdiv=4)
kwargs = dict(triangles=new.triangles, cmap=cm.jet, norm=norm, linewidth=0, antialiased=False)
ax.plot_trisurf(new.x, new.y, new_z, **kwargs)
plt.show()
Apparently the automatic triangulation doesn't produce the right triangles for you, but you can specify how you want your triangles manually:
triang = tri.Triangulation(x, y, [[3,2,1],[1,2,0]])
# alternatively:
triang = tri.Triangulation(x, y, [[3,2,0],[1,3,0]])
These two ways give rather different results:
However, now the interpolation becomes awkward, because for some (x,y) there are multiple z-values.. One way of bypassing this issue is interpolating and plotting the 2 large triangles separately:
import numpy
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.tri as tri
def plot_refined_tri(x, y, z, ax, subdiv=4, **kwargs):
triang = tri.Triangulation(x, y)
refiner = tri.UniformTriRefiner(triang)
interpolator = tri.LinearTriInterpolator(triang, z)
new, new_z = refiner.refine_field(z, interpolator, subdiv=subdiv)
ax.plot_trisurf(new.x, new.y, new_z, triangles=new.triangles, **kwargs)
x=numpy.array([0.00498316, 0.00498316, 0.00996632, 0.00996632])
y=numpy.array([-0.00037677, -0.00027191, -0.00078681, -0.00088475])
z=numpy.array([0., -0.0049926, 0., -0.00744763])
fig = plt.figure()
ax = Axes3D(fig)
# note: I normalized on z-values to "fix" the colormap
norm = plt.Normalize(vmax=z.max(), vmin=z.min())
kwargs = kwargs = dict(linewidth=0.2, cmap=cm.jet, norm=norm)
idx = [3,2,1]
plot_refined_tri(x[idx], y[idx], z[idx], ax, **kwargs)
idx = [1,2,0]
plot_refined_tri(x[idx], y[idx], z[idx], ax, **kwargs)
plt.show()
Result:

Adding water flow arrows to Matplotlib Contour Plot

I am generating a groundwater elevation contour with Matplotlib. See below
Here is what I have now; how can I add water flow arrows like the image below?
I want to add arrows to make it look like this:
If anyone has some ideas and/or code samples that would be greatly appreciated.
You'll need a recent (>= 1.2) version of matplotlib, but streamplot does this. You just need to take the negative gradient of your head (a.k.a. "water table" for surface aquifers) grid.
As a quick example generated from random point observations of head:
import numpy as np
from scipy.interpolate import Rbf
import matplotlib.pyplot as plt
# Make data repeatable
np.random.seed(1981)
# Generate some random wells with random head (water table) observations
x, y, z = np.random.random((3, 10))
# Interpolate these onto a regular grid
xi, yi = np.mgrid[0:1:100j, 0:1:100j]
func = Rbf(x, y, z, function='linear')
zi = func(xi, yi)
# -- Plot --------------------------
fig, ax = plt.subplots()
# Plot flowlines
dy, dx = np.gradient(-zi.T) # Flow goes down gradient (thus -zi)
ax.streamplot(xi[:,0], yi[0,:], dx, dy, color='0.8', density=2)
# Contour gridded head observations
contours = ax.contour(xi, yi, zi, linewidths=2)
ax.clabel(contours)
# Plot well locations
ax.plot(x, y, 'ko')
plt.show()

Categories

Resources