Create surface grid from point cloud data in Python - python

Here is an example creating a point cloud which I then want to fit a grided surface to. The problem comes at the end when I try to pass in meshgrid arrays to a function which interpolated the data:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Create some point cloud data:
c = 1
a = 3
b = 4
slice = {}
t = np.linspace(0,2*np.pi,50)
for s in np.linspace(1,9,10):
c = 5*s
r = (-s**2+10.0*s)/10.0
X = r*np.cos(t)
Y = r*np.sin(t)
Z = c*(Y**2/b**2 - X**2/a**2) + c
slice[str(int(s))] = np.vstack([X,Y,Z])
# Visualize it:
fig = plt.figure()
ax = fig.gca(projection = '3d')
for k,v in slice.iteritems():
print type(v)
print np.shape(v)
X = v[0,:]
Y = v[1,:]
Z = v[2,:]
ax.scatter(X,Y,Z)
plt.show()
It looks like this:
I now need to create a surface mesh based on these points. There are multiple interpretations of surface in this case because I just have a point cloud rather than a function z = f(x,y) but the correct surface in this case should be the one that creates a hollow "warped cylinder". I thought of attacking the problem like this:
# stack all points from each slice into one vector for each coordinate:
Xs = []
Ys = []
Zs = []
for k,v in slice.iteritems():
#ax.plot_surface(X,Y,Z)
X = v[0,:]
Y = v[1,:]
Z = v[2,:]
Xs = np.hstack((Xs,X))
Ys = np.hstack((Ys,Y))
Zs = np.hstack((Zs,Z))
XX, YY = np.meshgrid(Xs,Ys)
from scipy import interpolate
f = interpolate.interp2d(Xs,Ys,Zs, kind = 'cubic')
ZZ = f(XX,YY)
which I would then be able to plot using
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot_surface(XX, YY, ZZ)
plt.show()
However the interpolated function does not seem to accept arrays as inputs so this method might not work. Can anyone come up with a suggestion on how to do this properly?
Edit:
Actually the data is obviously not able to be represented as a function as it would not be one to one.

I stumbled upon the same question and wondered why it has not been solved in the last 7 years. Here's my solution for any future reader based on plot_trisurf (and the corresponding code examples).
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
# Create some point cloud data:
a = 3
b = 4
# def grid of parametric variables
u = np.linspace(0,2*np.pi,50)
v = np.linspace(1,9,50)
U, V = np.meshgrid(u, v)
U, V = U.flatten(), V.flatten()
# Triangulate parameter space to determine the triangles
tri = mtri.Triangulation(U, V)
# get the transformed data as list
X,Y,Z = [],[],[]
for _u in u:
for _v in v:
r = (-_v**2+10.0*_v)/10.0
x = r*np.cos(_u)
y = r*np.sin(_u)
z = 5*_v*(y**2/b**2 - x**2/a**2) + 5*_v
X.append(x)
Y.append(y)
Z.append(z)
# Visualize it:
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.scatter(X,Y,Z, s=1, c='r')
ax.plot_trisurf(X, Y, Z, triangles=tri.triangles, alpha=.5)
plt.show()
This produces the following plot.

Related

How to plot multiple lines from a loop on one 3d plot in Python?

Basically, I am looping generation of rays in Python and I'm trying to plot them all on the same graph. They should all be on a circle of radius 0.1. Each ray should be at a position on the circle that is varied by the arg which is in this case the theta. Also, just to mention (although I don't think it's that relevant) I am doing OOP here.
I get correct rays but I can't get them on the same 3d graph and I'm not sure how I'm supposed to do it. I thought using plt.show() would give me a graph with all 24 rays but it just plots 24 graphs.
Here is the relevant bit of code for reference:
r = 0.1
arg = 0
for i in range (0,24):
arg += np.pi/12
x = r*np.sin(arg)
y = r*np.cos(arg)
l = ray.Ray(r=np.array([x,y,0]),v=np.array([0.5,0,5]))
c = ray.SphericalRefraction(z0 = 100, curv = 0.0009, n1 = 1.0, n2 = 1.5, ar = 5)
c.propagate_ray(l)
o = ray.OutputPlane(250)
o.outputintercept(l)
points = np.array(l.vertices())
fig = plt.figure()
ax = plt.axes(projection='3d')
#ax = fig.add_subplot(1,2,1,projection='3d')
#plt.plot(points[:,2],points[:,0])
ax.plot3D(points[:,0],points[:,1],points[:,2])
plt.show()
Expanding on the comment by Mercury, the figure and also axes object must be created outside the loop.
import matplotlib.pyplot as plt
import numpy as np
r = 0.1
arg = 0
fig = plt.figure()
ax = plt.axes(projection='3d')
for i in range(0,24):
arg += np.pi/12 * i
v1 = r*np.sin(arg)
v2 = r*np.cos(arg)
# ...
# using sample data
x = []
y = []
z = []
for j in range(2):
x.append(j*v1)
y.append(j*v2)
z.append(j)
# add vertex to the axes object
ax.plot3D(x, y, z)
plt.show()

How to fix "not enough values to unpack" when trying to plot 3D data as colormesh?

With matplotlib I am trying to plot 3D data as a 2D colormap. Each point has a x and a y coordinate, and a 'height' z. This height should determine the color a certain x/y region is colored in.
Here is the code I have been trying:
import random
import numpy as np
import matplotlib.pyplot as plt
x = []
y = []
z = []
for index in range(100):
a = random.random()
b = random.random()
c = np.exp(-a*a - b*b)
x.append(a)
y.append(b)
z.append(c)
cmap = plt.get_cmap('PiYG')
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z, cmap=cmap)
But it gives an error
ValueError: not enough values to unpack (expected 2, got 1)
Maybe I am trying the wrong thing?
Remark: The three lists x,y,z and calculated for the example above, but in reality I have just three lists with "random" numbers in it I want to vizualize. I cannot calculate z given x and y.
I could also use imshow to create the plot I want, but I have to convert my original data into a matrix first. Maybe there is a function I can use?
pcolormesh might not be the choice for this kind of problem. pcolormesh expects ordered cell edges as data rather than random data points. You could do this if you know your grid before hand e.g.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 1, 51)
# meshgrid makes a 2D grid of points
xx, yy = np.meshgrid(x, x)
z = np.exp(-xx**2 - yy*2)
fig, ax = plt.subplots()
ax.pcolormesh(xx, yy, z, cmap="PiYG")
which will give you
Alternatively, you could use one of the tri functions such as tripcolor with your existing setup
import random
import numpy as np
import matplotlib.pyplot as plt
x = []
y = []
z = []
for index in range(100):
a = random.random()
b = random.random()
c = np.exp(-a*a - b*b)
x.append(a)
y.append(b)
z.append(c)
fig, ax = plt.subplots()
ax.tripcolor(x, y, z, cmap="PiYG")
which will give
Note it would be simpler to use np.random to generate your data
x, y = np.random.random(size=(2, 100))
z = np.exp(-x**2 - y**2)
fig, ax = plt.subplots()
ax.tripcolor(x, y, z, cmap="PiYG")
There is an issue with x, y and z shapes: they have to be 2D arrays (matrices) but they are 1-dimensional.
In order to generate x and y axis, you could use:
x = []
y = []
for index in range(100):
x.append(random.random())
y.append(random.random())
Then you have to create a meshgrid:
X, Y = np.meshgrid(x, y)
Finally you can compute Z over the meshgrid:
Z = np.exp(-X**2 - Y**2)
In this way, your code:
cmap = plt.get_cmap('PiYG')
fig, ax = plt.subplots()
ax.pcolormesh(X, Y, Z, cmap=cmap)
gives:
If you you cannot compute Z on the meshgrid, then you should not use pcolormesh.
Some alternative could be:
3D scatterplot:
import random
import numpy as np
import matplotlib.pyplot as plt
x = []
y = []
z = []
for index in range(100):
a = random.random()
b = random.random()
c = np.exp(-a*a - b*b)
x.append(a)
y.append(b)
z.append(c)
cmap = plt.get_cmap('PiYG')
fig = plt.figure()
ax = fig.add_subplot(projection = '3d')
ax.scatter(x, y, z, cmap=cmap)
plt.show()
2D colored scatterplot:
import random
import numpy as np
import matplotlib.pyplot as plt
x = []
y = []
z = []
for index in range(100):
a = random.random()
b = random.random()
c = np.exp(-a*a - b*b)
x.append(a)
y.append(b)
z.append(c)
cmap = plt.get_cmap('PiYG')
plt.style.use('seaborn-darkgrid')
fig, ax = plt.subplots()
ax.scatter(x, y, c = z, cmap=cmap)
plt.show()

Python Plot 3d Vectors

Supppse that I wanted to take the following three [x,y,z] coordinates:
[0.799319 -3.477045e-01 0.490093]
[0.852512 9.113778e-16 -0.522708]
[0.296422 9.376042e-01 0.181748]
And plot them as vectors where the vector's start at the origin [0,0,0]. How can I go about doing this? I've been trying to use matplotlib's quiver, but I keep geting the following value error:
ValueError: need at least one array to concatenate
Here's my code (document_matrix_projections are the three coordinates above represented as a matrix):
D1, D2, D3 = zip(*document_matrix_projections)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.quiver(D1)
plt.show()
A good and pretty alternative to using matplolib's quiver() will be to plot using plotly which has the advantage of being interactive. The following function can plot vectors using the Scatter3d() in plotly. Here, the vectors have a big point to mark the direction instead of an arrowhead.
import numpy as np
import plotly.graph_objs as go
def vector_plot(tvects,is_vect=True,orig=[0,0,0]):
"""Plot vectors using plotly"""
if is_vect:
if not hasattr(orig[0],"__iter__"):
coords = [[orig,np.sum([orig,v],axis=0)] for v in tvects]
else:
coords = [[o,np.sum([o,v],axis=0)] for o,v in zip(orig,tvects)]
else:
coords = tvects
data = []
for i,c in enumerate(coords):
X1, Y1, Z1 = zip(c[0])
X2, Y2, Z2 = zip(c[1])
vector = go.Scatter3d(x = [X1[0],X2[0]],
y = [Y1[0],Y2[0]],
z = [Z1[0],Z2[0]],
marker = dict(size = [0,5],
color = ['blue'],
line=dict(width=5,
color='DarkSlateGrey')),
name = 'Vector'+str(i+1))
data.append(vector)
layout = go.Layout(
margin = dict(l = 4,
r = 4,
b = 4,
t = 4)
)
fig = go.Figure(data=data,layout=layout)
fig.show()
Plotting can be done simply by,
p0 = [0.799319, -3.477045e-01, 0.490093]
p1 = [0.852512, 9.113778e-16, -0.522708]
p2 = [0.296422, 9.376042e-01, 0.181748]
vector_plot([p0,p1,p2])
The output of the above looks:
The quiver() function needs locations of the arrows as X,Y,Z and U,V,W as the components of the arrow. So the following script can plot your data:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
p0 = [0.799319, -3.477045e-01, 0.490093]
p1 = [0.852512, 9.113778e-16, -0.522708]
p2 = [0.296422, 9.376042e-01, 0.181748]
origin = [0,0,0]
X, Y, Z = zip(origin,origin,origin)
U, V, W = zip(p0,p1,p2)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.quiver(X,Y,Z,U,V,W,arrow_length_ratio=0.01)
plt.show()
But the results are not pretty. If you would like to use Mayavi, the following works:
import numpy as np
import mayavi.mlab as m
p0 = [0.799319, -3.477045e-01, 0.490093]
p1 = [0.852512, 9.113778e-16, -0.522708]
p2 = [0.296422, 9.376042e-01, 0.181748]
origin = [0,0,0]
X, Y, Z = zip(origin,origin,origin)
U, V, W = zip(p0,p1,p2)
m.quiver3d(X,Y,Z,U,V,W)

Having trouble making 3D wireframe with matplotlib in python

So I have a dataset that I am trying to bin into a matrix and then make a wireframe plot out of. When I show the plot, all that shows is a flat surface along the x=y line of the 3d image. I would like the full matrix to show. I have included my code as well as a sample of the stats.txt:
from numpy import *
from pylab import *
f = open('stats.txt')
bins = 10
xs = []
ys = []
for line in f:
line = line.strip().split(' ')
xs.append(float(line[0]))
ys.append(float(line[1]))
xlin = linspace(min(xs),max(xs),bins+1)
ylin = linspace(min(ys),max(ys),bins+1)
matrix = zeros((bins,bins))
for i in range(bins):
for j in range(bins):
count = 0
for s in range(len(xs)):
if xs[s] >= xlin[i] and xs[s] <= xlin[i+1] and ys[s] >= ylin[j] and ys[s] <= ylin[j+1]:
count +=1
matrix[i,j] = count
print matrix
x = []
y = []
for i in range(bins):
x.append([0.,1.,2.,3.,4.,5.,6.,7.,8.,9.])
for i in range(bins):
y.append([0.,1.,2.,3.,4.,5.,6.,7.,8.,9.])
#for i in range(bins):
# y.append(linspace(0,bins-1,bins))
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
print shape(x)
print shape(y)
print shape(matrix)
ax.plot_wireframe(x, y, matrix)
#plt.imshow(matrix,cmap=plt.cm.ocean)
plt.show()
Sample of stats.txt:
10385.8694574 114.758131279
11379.8955938 -166.830995639
10347.5572407 165.168099188
11698.0834105 110.188708959
12100.3323331 185.316597413
11530.3943217 287.99795812
11452.2864796 474.890116234
12181.4426414 149.266756079
10962.8512477 -544.794117131
10601.2128384 49.782478266
The problem with your code is that your x-coordinates are in the same as the y-coordinates for every data point. Thus, you're effectively telling matplotlib that you only have values on the diagonal in the x-y-plane.
One possible solution would be to simply transpose your y-coordinates. However, using numpy's meshgrid (link) function is probably a lot more comfortable.
x,y = np.meshgrid(np.arange(bins),np.arange(bins))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(x, y, matrix)

How to draw circle by data with matplotlib + python?

I can draw a circle by scatter, which has been shown in the image. But I want to draw them buy a line, because there are many circles in total, I need to link nodes together for a certain circle. Thanks.
I the order of the points is random, you can change X-Y to polar, and sort the data by angle:
create some random order points first:
import pylab as pl
import numpy as np
angle = np.arange(0, np.pi*2, 0.05)
r = 50 + np.random.normal(0, 2, angle.shape)
x = r * np.cos(angle)
y = r * np.sin(angle)
idx = np.random.permutation(angle.shape[0])
x = x[idx]
y = y[idx]
Then use arctan2() to calculate the angle, and sort the data by it:
angle = np.arctan2(x, y)
order = np.argsort(angle)
x = x[order]
y = y[order]
fig, ax = pl.subplots()
ax.set_aspect(1.0)
x2 = np.r_[x, x[0]]
y2 = np.r_[y, y[0]]
ax.plot(x, y, "o")
ax.plot(x2, y2, "r", lw=2)
here is the output:
Here is one way to do it. This answer uses different methods than the linked possible duplicate, so may be worth keeping.
import matplotlib.pyplot as plt
from matplotlib import patches
fig = plt.figure(figsize=plt.figaspect(1.0))
ax = fig.add_subplot(111)
cen = (2.0,1.0); r = 3.0
circle = patches.Circle(cen, r, facecolor='none')
ax.add_patch(circle)
ax.set_xlim(-6.0, 6.0)
ax.set_ylim(-6.0, 6.0)
If all you have are the x and y points, you can use PathPatch. Here's a tutorial
If your data points are already in order, the plot command should work fine. If you're looking to generate a circle from scratch, you can use a parametric equation.
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> t = np.linspace(0,2*np.pi, 100)
>>> x = np.cos(t)
>>> y = np.sin(t)
>>> plt.plot(x,y)

Categories