3D truncated cone in python - python

I want to plot a truncated cone by using exactly the same method used in
Plotting a solid cylinder centered on a plane in Matplotlib; which plots a cylinder when two points on the center of each base and the radius are known. On the other hand, I want to plot a truncated cone when the coordinates of the two points on the center of its bases and the radius of each base are known.
It seems that I just should change the second last line of the function in the following program which plots a cylinder, but I could not do this in all of my efforts.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
import pylab as pllt
fig = pllt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
#ax = pllt.subplot2grid((2,2), (0,0), rowspan=2, projection='3d')
#axis and radius
def cylinder(p0,p1,R,ccc):
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 1, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#print n1,'\t',norm(n1)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 80)
theta = np.linspace(0, 2 * np.pi, 80)
#use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
#generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z,color=ccc,linewidth=0, antialiased=False)
A0 = np.array([1, 3, 2])
A1 = np.array([8, 5, 9])
ax.set_xlim(0,10)
ax.set_ylim(0,10)
ax.set_zlim(0,10)
cylinder(A0,A1,1,'blue')
pllt.show()
I think I should change the radius as a function of v=p1-p0 as mentioned in:
http://mathworld.wolfram.com/ConicalFrustum.html to be able to do this.
Please let me know if there is any way to do this.

Instead of a constant radius, R, make it change from R0 to R1:
R = np.linspace(R0, R1, n)
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
import pylab as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
def truncated_cone(p0, p1, R0, R1, color):
"""
Based on https://stackoverflow.com/a/39823124/190597 (astrokeat)
"""
# vector in direction of axis
v = p1 - p0
# find magnitude of vector
mag = norm(v)
# unit vector in direction of axis
v = v / mag
# make some vector not in the same direction as v
not_v = np.array([1, 1, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
# make vector perpendicular to v
n1 = np.cross(v, not_v)
# print n1,'\t',norm(n1)
# normalize n1
n1 /= norm(n1)
# make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
# surface ranges over t from 0 to length of axis and 0 to 2*pi
n = 80
t = np.linspace(0, mag, n)
theta = np.linspace(0, 2 * np.pi, n)
# use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
R = np.linspace(R0, R1, n)
# generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R *
np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, color=color, linewidth=0, antialiased=False)
A0 = np.array([1, 3, 2])
A1 = np.array([8, 5, 9])
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
truncated_cone(A0, A1, 1, 5, 'blue')
plt.show()

Related

Spherical coordinates plot in pyqtgraph

I'm just starting with pyqtgraph and I want to make 3d surface plots in spherical coordinates. I've taken a look at the example GLSurfacePlot.py from the documentation but there are only plots in cartesian coordinates.
This is the plot I want to make (it's a half wave dipole radiation pattern):
How to plot r(theta, phi) with pyqtgraph?
EDIT: I could do it with matplotlib mplot3d, here is the script:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
k = 2*np.pi
long = 0.5
theta = np.linspace(0, np.pi, 361)
phi = np.linspace(0, 2*np.pi, 361)
PHI, THETA = np.meshgrid(phi, theta)
R = np.absolute((np.cos(k*long/2*np.cos(THETA))-np.cos(k*long/2))/np.sin(THETA))
R = np.nan_to_num(R)
X = R * np.sin(THETA) * np.cos(PHI)
Y = R * np.sin(THETA) * np.sin(PHI)
Z = R * np.cos(THETA)
fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')
cmap = plt.get_cmap('jet')
plot = ax.plot_surface(X, Y, Z, rstride=10, cstride=10, facecolors=cmap(R),linewidth=0, antialiased=False, alpha=1)
plt.show()
The problem is that it's too slow when rotating and zooming it, and I definitely need that feature for my application, that's why I'm trying to do it with pyqtgraph.
Drawing this type of equations is not possible through GLSurfacePlotItem, in this case you must use GLMeshItem, but for this you must create an appropriate MeshData, so it takes as a reference sphere obtaining the following function:
def DipoleData(rows, cols, func, args=None):
verts = np.empty((rows+1, cols, 3), dtype=float)
phi = (np.arange(rows+1) * 2*np.pi *(1+2/rows)/ rows).reshape(rows+1, 1)
th = ((np.arange(cols) * np.pi / cols).reshape(1, cols))
if args is not None:
r = func(th, phi, *args)
else:
r = func(th, phi)
s = r* np.sin(th)
verts[...,2] = r * np.cos(th)
verts[...,0] = s * np.cos(phi)
verts[...,1] = s * np.sin(phi)
verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom
faces = np.empty((rows*cols*2, 3), dtype=np.uint)
rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]])
rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]])
for row in range(rows):
start = row * cols * 2
faces[start:start+cols] = rowtemplate1 + row * cols
faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols
faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom
## adjust for redundant vertexes that were removed from top and bottom
vmin = cols-1
faces[faces<vmin] = vmin
faces -= vmin
vmax = verts.shape[0]-1
faces[faces>vmax] = vmax
return gl.MeshData(vertexes=verts, faces=faces)
It is then used in the following example:
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 3
w.show()
w.setWindowTitle('Half Wave Dipole Radiation Pattern')
def r_theta_phi(theta, phi, k, l):
return np.absolute((np.cos((k*l/2)*np.cos(theta)) -np.cos(k*l/2))/np.sin(theta))
p = 2*np.pi
q = 0.5
md = DipoleData(100, 100, r_theta_phi, args=(p, q))
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[:,0] = np.linspace(0.1, 0.2, colors.shape[0])
colors[:,1] = np.linspace(0.2, 0.9, colors.shape[0])
colors[:,2] = np.linspace(0.0, 0.1, colors.shape[0])
md.setFaceColors(colors)
m = gl.GLMeshItem(meshdata=md, smooth=False)
w.addItem(m)
ax = gl.GLAxisItem()
ax.setSize(100,100,100)
w.addItem(ax)
g = gl.GLGridItem()
g.scale(0.2, 0.2, 0.2)
w.addItem(g)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Obtaining what is shown in the following image:

Plotting a solid cylinder centered on a plane in Matplotlib

I fit a plane to a bunch of points in 3d and initially gave it an arbitrary size using np.meshgrid, but now I'm trying to plot a cylinder centered on that plane and oriented the same way (such that the plane fit would cut the height of the cylinder in half), but with a specified radius and height. The only examples of cylinders plotted in matplotlib I can find are hollow and usually open at the top and bottom. I want the one I plot to be solid so I can clearly see what points it's enclosing.
Here's a minimum working example with a randomly generated plane. Since the plane I'm using is always given by a point and a normal vector, the cylinder should be based off of those things as well (plus a provided radius, and height to extend above and below the plane).
from __future__ import division #Enables new-style division
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
import numpy as np
cen_x = 0
cen_y = 0
cen_z = 0
origin = np.array([cen_x,cen_y,cen_z])
normal = np.array([np.random.uniform(-1,1),np.random.uniform(-1,1),np.random.uniform(0,1)])
a = normal[0]
b = normal[1]
c = normal[2]
#equation for a plane is a*x+b*y+c*z+d=0 where [a,b,c] is the normal
#so calculate d from the normal
d = -origin.dot(normal)
# create x,y meshgrid
xx, yy = np.meshgrid(np.arange(cen_x-1,cen_x+1,0.01),np.arange(cen_y-1,cen_y+1,0.01))
# calculate corresponding z
zz = (-a * xx - b * yy - d) * 1./c
halo_x = [-0.3, -0.9, 0.8, 1.3, -0.1, 0.5]
halo_y = [0.8, 1.1, -0.5, -0.7, -1.2, 0.1]
halo_z = [1.0, -0.4, 0.3, -1.2, 0.9, 1.2]
fig = plt.figure(figsize=(9,9))
plt3d = fig.gca(projection='3d')
plt3d.plot_surface(xx, yy, zz, color='r', alpha=0.4)
plt3d.set_xlim3d(cen_x-3,cen_x+3)
plt3d.set_ylim3d(cen_y-3,cen_y+3)
plt3d.set_zlim3d(cen_z-3,cen_z+3)
plt3d.set_xlabel('X')
plt3d.set_ylabel('Y')
plt3d.set_zlabel('Z')
plt.show()
I have modified a solution to a question How to add colors to each individual face of a cylinder using matplotlib, removing the fancy shading and adding end caps. If you want to show the enclosed points, you can use alpha=0.5 or some such to make the cylinder semi-transparent.
The orientation of the cylinder is defined by a unit vector v with length mag, which could be the normal to your surface.
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Sun Oct 2 18:33:10 2016
Modified from https://stackoverflow.com/questions/38076682/how-to-add-colors-to-each-individual-face-of-a-cylinder-using-matplotlib
to add "end caps" and to undo fancy coloring.
#author: astrokeat
"""
import numpy as np
from matplotlib import pyplot as plt
from scipy.linalg import norm
#axis and radius
p0 = np.array([1, 3, 2]) #point at one end
p1 = np.array([8, 5, 9]) #point at other end
R = 5
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 0, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 2)
theta = np.linspace(0, 2 * np.pi, 100)
rsample = np.linspace(0, R, 2)
#use meshgrid to make 2d arrays
t, theta2 = np.meshgrid(t, theta)
rsample,theta = np.meshgrid(rsample, theta)
#generate coordinates for surface
# "Tube"
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta2) * n1[i] + R * np.cos(theta2) * n2[i] for i in [0, 1, 2]]
# "Bottom"
X2, Y2, Z2 = [p0[i] + rsample[i] * np.sin(theta) * n1[i] + rsample[i] * np.cos(theta) * n2[i] for i in [0, 1, 2]]
# "Top"
X3, Y3, Z3 = [p0[i] + v[i]*mag + rsample[i] * np.sin(theta) * n1[i] + rsample[i] * np.cos(theta) * n2[i] for i in [0, 1, 2]]
ax=plt.subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, color='blue')
ax.plot_surface(X2, Y2, Z2, color='blue')
ax.plot_surface(X3, Y3, Z3, color='blue')
plt.show()
The result:

How to close the ends of a cylinder in matplotlib

I am trying to make a 'closed' cylinder in matplotlib but I am not sure how to go about doing this. So far I have a cylinder with the ends open, the code for this is as follows:
#make a cylinder without the ends closed
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = [0,0,0]
#radius = R
p0 = np.array(origin)
p1 = np.array([8, 8, 8])
origin = np.array(origin)
R = 4
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 0, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 600)
theta = np.linspace(0, 2 * np.pi, 100)
#use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
#generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
#make the color for the faces
col1 = plt.cm.autumn(np.ones(600)) # linear gradient along the t-axis
col1 = np.repeat(col1[np.newaxis,:, :], 100, axis=0) # expand over the theta-axis
ax.plot_surface(X, Y,Z, facecolors = col1, shade = True,edgecolors = "None", alpha = 0.4, linewidth = 0)
plt.show()
Running this code produces the following image
How would I close the ends of the cylinder with a solid circle (i.e. disk)?
A quick and easy way that's similar to your other code is to generate a surface using strips from r=0 to r=R. Right before plt.show() add the following lines:
R = np.array([0,R])
# cap at t=0
X, Y, Z = [p0[i] + np.outer(R, np.sin(theta)) * n1[i] + np.outer(R, np.cos(theta))*n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, edgecolors = "r", alpha=.4, linewidth = .1)
# cap at t=mag
X, Y, Z = [p0[i] + v[i]*mag + np.outer(R, np.sin(theta)) * n1[i] + np.outer(R, np.cos(theta))*n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, edgecolors = "r", alpha=.4, linewidth = .1)
Here the colors are more for illustrative purposes, mostly so you can see the strips. The result looks like:

Facecolor changing edgecolor in matplotlib

I am trying to remove the edge color in the plot of a cylinder where I have set an alpha and facecolors. However, if I also set the facecolors, I can still see the edge colors. If I remove the alpha = 0.5 statement then the problem is resolved, however I need the alpha to be <1 . Here is an example:
You can still see the blue edgecolors even tough I have set the edgecolor to None.
This is the code where I use plot_surface()
ax.plot_surface(X, Y,Z, edgecolor = "None", facecolors = col1, alpha = 0.5)
Yet the edge colors are still there? However, if I remove the facecolors statement inside plot_surface() then the edge colors are no longer there. Here is the complete code:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import random
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = np.array([0, 0, 0])
#axis and radius
p0 = np.array([0, 0, 0])
p1 = np.array([8, 8, 8])
R = 4
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 0, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 200)
theta = np.linspace(0, 2 * np.pi, 100)
#use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
#generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
col1 = plt.cm.Blues(np.linspace(0,1,200)) # linear gradient along the t-axis
col1 = np.repeat(col1[np.newaxis,:, :], 100, axis=0) # expand over the theta- axis
ax.plot_surface(X, Y,Z, edgecolor = None, facecolors = col1, alpha = 0.5)
#plot axis
ax.plot(*zip(p0, p1), color = 'red')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
plt.axis('off')
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
plt.show()
Setting linewidth=0 in plot_surface() solves this problem:
ax.plot_surface(X, Y, Z, edgecolor=None, facecolors=col1, alpha=0.5, linewidth=0)
p.s.: I didn't find this worth an answer, but per: Question with no answers, but issue solved in the comments (or extended in chat), I added it as a quick answer so the question can be marked as solved

Plotting the temperature distribution on a sphere with python

I have the following problem:
a have N points on a sphere specified by a array x, with x.shape=(N,3). This array contains their cartesian coordinates. Furthermore, at each point, I have a specified temperature. This quantity is saved in an array T, with T.shape=(N,).
Is there any straight forward way to map this temperature distribution into the plane using different colors?
If it simplifies the task, the position can also be given in polar coordinates (\theta,\phi).
To plot your data, you can use Basemap. The only problem is, that both contour and contourf routines needs gridded data. Here is example with naive (and slow) IDW-like interpolation on sphere. Any comments are welcome.
import numpy as np
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
def cart2sph(x, y, z):
dxy = np.sqrt(x**2 + y**2)
r = np.sqrt(dxy**2 + z**2)
theta = np.arctan2(y, x)
phi = np.arctan2(z, dxy)
theta, phi = np.rad2deg([theta, phi])
return theta % 360, phi, r
def sph2cart(theta, phi, r=1):
theta, phi = np.deg2rad([theta, phi])
z = r * np.sin(phi)
rcosphi = r * np.cos(phi)
x = rcosphi * np.cos(theta)
y = rcosphi * np.sin(theta)
return x, y, z
# random data
pts = 1 - 2 * np.random.rand(500, 3)
l = np.sqrt(np.sum(pts**2, axis=1))
pts = pts / l[:, np.newaxis]
T = 150 * np.random.rand(500)
# naive IDW-like interpolation on regular grid
theta, phi, r = cart2sph(*pts.T)
nrows, ncols = (90,180)
lon, lat = np.meshgrid(np.linspace(0,360,ncols), np.linspace(-90,90,nrows))
xg,yg,zg = sph2cart(lon,lat)
Ti = np.zeros_like(lon)
for r in range(nrows):
for c in range(ncols):
v = np.array([xg[r,c], yg[r,c], zg[r,c]])
angs = np.arccos(np.dot(pts, v))
idx = np.where(angs == 0)[0]
if idx.any():
Ti[r,c] = T[idx[0]]
else:
idw = 1 / angs**2 / sum(1 / angs**2)
Ti[r,c] = np.sum(T * idw)
# set up map projection
map = Basemap(projection='ortho', lat_0=45, lon_0=15)
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0, 360, 30))
map.drawparallels(np.arange(-90, 90, 30))
# compute native map projection coordinates of lat/lon grid.
x, y = map(lon, lat)
# contour data over the map.
cs = map.contourf(x, y, Ti, 15)
plt.title('Contours of T')
plt.show()
One way to do this is to set facecolors by mapping your heat data through the colormap.
Here's an example:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
u = np.linspace(0, 2 * np.pi, 80)
v = np.linspace(0, np.pi, 80)
# create the sphere surface
x=10 * np.outer(np.cos(u), np.sin(v))
y=10 * np.outer(np.sin(u), np.sin(v))
z=10 * np.outer(np.ones(np.size(u)), np.cos(v))
# simulate heat pattern (striped)
myheatmap = np.abs(np.sin(y))
ax.plot_surface(x, y, z, cstride=1, rstride=1, facecolors=cm.hot(myheatmap))
plt.show()
Here, my "heatmap" is just stripes along the y-axis, which I made using the function np.abs(np.sin(y)), but anything that goes form 0 to 1 will work (and, of course, it needs to match the shapes on x, etc.

Categories

Resources