Ploting 2d projection of 3d cube with tricontourf in python - python

I have a cube of which I know the x,y,z positions of its vertices, I also have an array relating faces to vertices (the faces are composed of 2 triangles):
import numpy as np
x = np.array([ 0.16257299, -0.370805 , -1.09232295, 1.62570095,
-1.62570095, 1.09232295, 0.370805 , -0.16257299])
y = np.array([-1.71022499, -0.81153202, -0.52910602, -0.36958599,
0.369587 , 0.52910602, 0.81153202, 1.71022499])
z = np.array([ 0.22068501, -1.48456001, 1.23566902, 0.469576 ,
-0.469576 , -1.23566902, 1.48456001, -0.22068501])
faces = ([[3, 0, 1],[6, 7, 4],[3, 6, 2],[0, 2, 4],[1, 4, 7],[6, 3, 5],
[1, 5, 3],[4, 2, 6],[2, 0, 3],[4, 1, 0],[7, 5, 1],[5, 7, 6]])
I manage to plot the 3D visualization of the cube with the following:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x,y,z, triangles = faces)
But what I would like is to plot the 2D projection of the cube on the Y axis, I tried the following:
valuesOfFaces = [5,10,9,1,2,3,7,8]
import matplotlib.pyplot as plt
%matplotlib notebook
fig, ax = plt.subplots()
ax.tricontourf(x,z,valuesOfFaces,triangles = faces,zdir='y',levels=np.sort(valuesOfFaces))
But it results in the following:
What I would like is to be able to color each face given a constant value and also that faces that are not visible do not appear. Is that possible with matplotlib ? If yes how would you suggest I proceed ?

You're lucky that I happen to have answered this question, Plot 3D convex closed regions in matplot lib,
recently. The approach can be quite similar. You first simplify the triangles into faces of the cube (this is done in the linked answer) and then just have to remove the faces which are hidden. Here the approach would be sort the faces by their center of mass along the viewing direction and remove the last 3 faces.
Finally projecting to 2D is done by removing the y dimension.
from scipy.spatial import ConvexHull
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
import mpl_toolkits.mplot3d as a3
from mpl_toolkits.mplot3d import Axes3D
x = np.array([ 0.16257299, -0.370805 , -1.09232295, 1.62570095,
-1.62570095, 1.09232295, 0.370805 , -0.16257299])
y = np.array([-1.71022499, -0.81153202, -0.52910602, -0.36958599,
0.369587 , 0.52910602, 0.81153202, 1.71022499])
z = np.array([ 0.22068501, -1.48456001, 1.23566902, 0.469576 ,
-0.469576 , -1.23566902, 1.48456001, -0.22068501])
verts = np.c_[x,y,z]
hull = ConvexHull(verts)
simplices = hull.simplices
org_triangles = [verts[s] for s in simplices]
class Faces():
def __init__(self,tri, sig_dig=12, method="convexhull"):
self.method=method
self.tri = np.around(np.array(tri), sig_dig)
self.grpinx = list(range(len(tri)))
norms = np.around([self.norm(s) for s in self.tri], sig_dig)
_, self.inv = np.unique(norms,return_inverse=True, axis=0)
def norm(self,sq):
cr = np.cross(sq[2]-sq[0],sq[1]-sq[0])
return np.abs(cr/np.linalg.norm(cr))
def isneighbor(self, tr1,tr2):
a = np.concatenate((tr1,tr2), axis=0)
return len(a) == len(np.unique(a, axis=0))+2
def order(self, v):
if len(v) <= 3:
return v
v = np.unique(v, axis=0)
n = self.norm(v[:3])
y = np.cross(n,v[1]-v[0])
y = y/np.linalg.norm(y)
c = np.dot(v, np.c_[v[1]-v[0],y])
if self.method == "convexhull":
h = ConvexHull(c)
return v[h.vertices]
else:
mean = np.mean(c,axis=0)
d = c-mean
s = np.arctan2(d[:,0], d[:,1])
return v[np.argsort(s)]
def simplify(self):
for i, tri1 in enumerate(self.tri):
for j,tri2 in enumerate(self.tri):
if j > i:
if self.isneighbor(tri1,tri2) and \
self.inv[i]==self.inv[j]:
self.grpinx[j] = self.grpinx[i]
groups = []
for i in np.unique(self.grpinx):
u = self.tri[self.grpinx == i]
u = np.concatenate([d for d in u])
u = self.order(u)
groups.append(u)
return groups
def order_along_axis(self,faces,axis):
midpoints = np.array([f.mean(axis=0) for f in faces])
s = np.dot(np.array(axis),midpoints.T)
return np.argsort(s)
def remove_last_n(self, faces, order, n=1):
return np.array(faces)[order][::-1][n:][::-1]
f = Faces(org_triangles, sig_dig=4)
g = f.simplify()
order = f.order_along_axis(g, [0,1,0])
g = f.remove_last_n(g, order, 3)
# Reduce dimension, ommit y axis:
g2D = g[:,:,[0,2]]
fig = plt.figure(figsize=(8,3))
ax = fig.add_subplot(121, projection="3d")
ax2 = fig.add_subplot(122)
colors = np.random.rand(len(g),3)
pc = a3.art3d.Poly3DCollection(g, facecolors=colors,
edgecolor="k", alpha=0.9)
ax.add_collection3d(pc)
pc2 = PolyCollection(g2D, facecolors=colors,
edgecolor="k", alpha=0.9)
ax2.add_collection(pc2)
ax2.autoscale()
ax2.set_aspect("equal")
ax.set_xlim([-1.5,2])
ax.set_ylim([-1.5,2])
ax.set_zlim([-1.5,2])
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()

Related

Plotting a line between 2-D point to its corresponding value in 3-D

I am trying to plot a dashed line in a 3-D Matplotlib plot. I would like to get a dashed line between each (x_pt, y_pt) to its corresponding z_pt.
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
matplotlib.rcParams['mathtext.fontset'] = 'cm'
matplotlib.rcParams['axes.labelsize'] = 13
def z_function(x, y):
a = 1
b = 5.1/(4*np.pi**2)
c = 5/np.pi
r = 6
s = 10
t = 1/(8*np.pi)
return a*(y - b*x**2 + c*x - r)**2 + s*(1 - t)*np.cos(x) + s
x = np.linspace(-5, 10, 100)
y = np.linspace(0, 15, 100)
indexes = np.random.randint(0, 100, 5)
x_pt = x[indexes]
y_pt = y[indexes]
z_pt = z_function(x_pt, y_pt)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(x_pt, y_pt, color='k', marker='x', depthshade=False)
ax.scatter(x_pt, y_pt, z_pt, color='k', marker='^', depthshade=False)
ax.set_xticks([-5, 0, 5, 10])
ax.set_yticks([0, 5, 10, 15])
ax.set_zticks([100, 200, 300])
ax.view_init(30, -120)
ax.set_xlabel(r'$x_1$')
ax.set_ylabel(r'$x_2$')
ax.zaxis.set_rotate_label(False)
ax.set_zlabel(r'$f(x)$', rotation=0)
ax.w_xaxis.pane.fill = False
ax.w_yaxis.pane.fill = False
ax.w_zaxis.pane.fill = False
plt.show()
Can anyone help me with this?
If I understand your problem correctly, you need to connect the point (x,y,0) to (x,y,z) like so:
for x_,y_,z_ in zip(x_pt, y_pt, z_pt):
ax.plot([x_,x_],[y_,y_],[0,z_], '--', c='grey')
It should be as simple as:
ax.plot(x_pt, y_pt, zs=z_pt, color='blue', marker='--', depthshade=False)
alternatively using:
ax.plot3D(x_pt, y_pt, z_pt, marker='--')
UPDATE:
You will need to create extra dummy coordinates for each point on the x-y axis, like so:
import numpy as np
n = 10 # number of points in the line
for i in len(x_pt):
x_range = np.linspace(0, x_pt[i], n)
y_range = np.linspace(0, y_pt[i], n)
ax.plot3D(x_range, y_range, [z_pt[i]]*n, marker='--')
NOTE: Untested

How to identify certain coordinates (x,y) for its color after matplotlib.pyplot fill function?

Is there any way to input a certain coordinates (x,y) to pyplot and output its filling color in return?
ex. (0,0) -> Red , (0.75,0) -> blue , (1,1) ->white
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(15,15))
x,y,x2,y2=[],[],[],[]
for i in np.linspace(0,2,300):
x.append(np.cos(np.pi*i))
y.append(np.sin(np.pi*i))
x2.append(0.5*np.cos(np.pi*i))
y2.append(0.5*np.sin(np.pi*i))
plt.fill(x,y,'b')
plt.fill(x2,y2,'r')
Color Image
Here is one possible method. Let's begin with a modified code (based on yours):
# Modified code
import numpy as np
import matplotlib.pyplot as plt
from shapely.geometry import Point, Polygon # additional code
plt.figure(figsize=(15,15))
x,y,x2,y2=[],[],[],[]
for i in np.linspace(0,2,300):
x.append(np.cos(np.pi*i))
y.append(np.sin(np.pi*i))
x2.append(0.5*np.cos(np.pi*i))
y2.append(0.5*np.sin(np.pi*i))
# grab the fill objects for use later
# .fill return: matplotlib.patches.Polygon object
blue_pgn = plt.fill(x,y,'b') #Plot filled polygons
red_pgn = plt.fill(x2,y2,'r')
plt.show()
It produces the same plot as your code does, but also exposes 2 useful objects, blue_pgn and red_pgn .
This is the second part:
# Create geometries from objects in the plot
blue_geom = Polygon(blue_pgn[0].get_xy())
red_geom = Polygon(red_pgn[0].get_xy())
# create a function
def from_xy_to_color(x,y):
point_xy = Point(x,y)
if red_geom.contains(point_xy):
print("xy:",x,y,", color:","Red")
elif blue_geom.contains(point_xy):
print("xy:",x,y,", color:","Blue")
else:
print("xy:",x,y,", color:","White")
# test all the points
xys = [[0,0], [.75,0], [1,1]]
for xy in xys:
#print(xy)
from_xy_to_color(*xy)
The output from this part:
xy: 0 0 , color: Red
xy: 0.75 0 , color: Blue
xy: 1 1 , color: White
from matplotlib.backends.backend_agg import FigureCanvasAgg
import numpy as np
import matplotlib.pyplot as plt
def circle_coords():
t = np.linspace(0, 2, 300) * np.pi
x = np.cos(t)
y = np.sin(t)
return x, y
def plot(fig):
ax = fig.gca()
x, y = circle_coords()
ax.fill(x, y, 'b')
ax.fill(x / 2, y / 2, 'r')
def capture():
plt.Figure((4, 4), dpi=20)
fig = plt.gcf()
canvas = FigureCanvasAgg(fig)
plot(fig)
canvas.draw()
r = canvas_to_numpy(canvas)
plt.close()
return r
def canvas_to_numpy(canvas):
s, (width, height) = canvas.print_to_buffer()
x = np.frombuffer(s, np.uint8)
return x.reshape((height, width, 4))
def random_points(shape, n_points=4):
height, width = shape
x = np.random.uniform(0, width, size=n_points)
y = np.random.uniform(0, height, size=n_points)
return np.vstack([x, y]).T
def main():
arr = capture()
p = [[360, 274],
[379, 48],
[117, 216]]
fig = plt.gcf()
ax = fig.gca()
np.set_printoptions(precision=2)
print(p)
for x, y in p:
r, g, b, a = arr[y, x] / 255
c = f"{r, g, b}"
print(arr[y, x])
ax.text(x, y, c)
ax.scatter(x, y, c="yellow")
plt.imshow(arr)
plt.show()
plt.close()
main()
See: Matplotlib figure to image as a numpy array

How to draw circles on the perimeter of a circle?

I'm trying to plot something like this:
I don't know how to find the center of smaller circles in for loops. First, I've tried to plot it with smaller number of circles(for example 2) but I don't know why the smaller circles are semi-circles??
My try:
import numpy as np
import matplotlib.pyplot as plt
r = 2, h = 1, k = 1
axlim = r + np.max((abs(h),np.max(abs(k))))
x = np.linspace(-axlim, axlim, 100)
X,Y = np.meshgrid(x,x)
F = (X-h)**2 + (Y-k)**2 - r**2
plt.contour(X,Y,F,0)
F1 = (X-(h+r))**2 + (Y-k)**2 - (r/3)**2
plt.contour(X,Y,F1,0)
F2 = (X-h)**2 + (Y-(k+r))**2 - (r/3)**2
plt.contour(X,Y,F2,0)
plt.gca().set_aspect('equal')
plt.axis([-4*r, 4*r, -4*r,4*r])
# plt.axis('off')
plt.show()
The output:
Sine, cosine and an angle evenly divided over the range 0, 2picould be used:
import numpy as np
import matplotlib.pyplot as plt
num_circ = 7
rad_large = 7
rad_small = 6
thetas = np.linspace(0, 2 * np.pi, num_circ, endpoint=False)
fig, ax = plt.subplots()
ax.add_patch(plt.Circle((0, 0), rad_large, fc='none', ec='navy'))
for theta in thetas:
ax.add_patch(plt.Circle((rad_large * np.cos(theta), rad_large * np.sin(theta),), rad_small, fc='none', ec='crimson'))
ax.autoscale_view() # calculate the limits for the x and y axis
ax.set_aspect('equal') # show circles as circles
plt.show()

Quiver 3d (python matplotlib): how to get same color for arrow shaft and arrow head? (Quiver bug?)

The program below plots 3d arrows. I set the color for arrows depending on elevation (z coordinate).
WHAT WORKS: Arrow shafts get the correct color
PROBLEM: Arrow heads (whole or part) get a different color. Ugly.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import
import matplotlib.cm as cm
n = 5
n3 = n*n*n
u, v, w = np.arange(n3), np.arange(n3), np.arange(n3) # Positions
x, y, z = np.arange(n3), np.arange(n3), np.arange(n3) # Vector components
colors = [(0, 0, 255)]*n3
mid = n/2
for i in range(n):
for j in range(n):
for k in range(n):
ii = i*n*n + j*n + k
u[ii], v[ii], w[ii] = -(j-mid), i-mid, 0 # Whorl
x[ii], y[ii], z[ii] = i, j, k
colors[ii] = (1, 0, 0)
if abs(k-(n-1)/2) < 1.1: colors[ii] = (0, 1, 0)
if abs(k-(n-1)/2) < 0.1: colors[ii] = (0, 0, 1)
figure = plt.figure()
axes = figure.gca(projection='3d') # gca = get/create current axes
q = axes.quiver(x, y, z, u, v, w, color=colors, length = 0.5, normalize=True)
plt.show() # Finally display the plot
Output:
I think the problem is you didn't specify the color of the arrowheads, so matplotlib is cycling through your colors variable to color the arrowheads.
After your for loop, use the code below instead to fix the color of the arrowheads. Each vector is composed of 3 elements: a line for the body fo the vector plus two lines for the arrowhead. So if you have n vectors, you'll need to provide n*3 colors to specify the colors properly.
Let's say you're plotting 2 vectors. Here's how the colors for the vector elements should be sorted in the array of colors: line1col, line2col, l1arrow1col, l1arrow2col, l2arrow1col, l2arrow2col, l3arrow1col, l3arrow2col.
# fix arrowhead colors
cs = colors[:]
for c in colors:
cs.append(c)
cs.append(c)
figure = plt.figure(figsize=(13, 13))
axes = figure.gca(projection="3d") # gca = get/create current axes
q = axes.quiver(x, y, z, u, v, w, color=cs, length=0.5, normalize=True)
plt.show() # Finally display the plot
Here's a simpler example with 3 vectors that illustrates how to color the vectors.
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.quiver(
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[-3, 0, 0],
[-4, 4, -1],
[-2, 2, 1],
color=plt.cm.viridis(
[200, 50, 100, 200, 200, 50, 50, 100, 100]
), # line1, line2, line3, l1arrow1, l1arrow2, l2arrow1, l2arrow2, l3arrow1, l3arrow2
arrow_length_ratio=0.3,
)
ax.set(ylim=(-5, 5), xlim=(-5, 5), zlim=(-5, 5))
plt.show()

Return surface triangle of 3D scipy.spatial.Delaunay

I have this problem. I try to triangulate points cloud by scipy.spatial.Delaunay. I used:
tri = Delaunay(points) # points: np.array() of 3d points
indices = tri.simplices
vertices = points[indices]
But, this code return tetrahedron. How is it possible return triangle of surface only?
Thanks
To get it to work as in code form, you have to parametrize the surface to 2D. For example in the case of ball (r,theta, psi), radius is constant (drop it out) and points are given by (theta,psi) which is 2D.
Scipy Delaunay is N-dimensional triangulation, so if you give 3D points it returns 3D objects. Give it 2D points and it returns 2D objects.
Below is a script that I used to create polyhedra for openSCAD. U and V are my parametrization (x and y) and these are the coordinates that I give to Delaunay. Note that now the "Delaunay triangulation properties" apply only in u,v coordinates (angles are maximized in uv -space not xyz -space, etc).
The example is a modified copy from http://matplotlib.org/1.3.1/mpl_toolkits/mplot3d/tutorial.html which originally uses Triangulation function (maps to Delaunay eventually?)
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.tri as mtri
from scipy.spatial import Delaunay
# u, v are parameterisation variables
u = np.array([0,0,0.5,1,1])
v = np.array([0,1,0.5,0,1])
x = u
y = v
z = np.array([0,0,1,0,0])
# Triangulate parameter space to determine the triangles
#tri = mtri.Triangulation(u, v)
tri = Delaunay(np.array([u,v]).T)
print 'polyhedron(faces = ['
#for vert in tri.triangles:
for vert in tri.simplices:
print '[%d,%d,%d],' % (vert[0],vert[1],vert[2]),
print '], points = ['
for i in range(x.shape[0]):
print '[%f,%f,%f],' % (x[i], y[i], z[i]),
print ']);'
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
# The triangles in parameter space determine which x, y, z points are
# connected by an edge
#ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=plt.cm.Spectral)
ax.plot_trisurf(x, y, z, triangles=tri.simplices, cmap=plt.cm.Spectral)
plt.show()
Below is the (slightly more structured) text output:
polyhedron(
faces = [[2,1,0], [3,2,0], [4,2,3], [2,4,1], ],
points = [[0.000000,0.000000,0.000000],
[0.000000,1.000000,0.000000],
[0.500000,0.500000,1.000000],
[1.000000,0.000000,0.000000],
[1.000000,1.000000,0.000000], ]);
It looks like you want to compute the convex hull of your point cloud. I think this is what you want to do:
from scipy.spatial import ConvexHull
hull = ConvexHull(points)
indices = hull.simplices
vertices = points[indices]
Following Jaime's answer, but elaborating a bit more with an example:
import matplotlib as mpl
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3
import numpy as np
import scipy as sp
from scipy import spatial as sp_spatial
def icosahedron():
h = 0.5*(1+np.sqrt(5))
p1 = np.array([[0, 1, h], [0, 1, -h], [0, -1, h], [0, -1, -h]])
p2 = p1[:, [1, 2, 0]]
p3 = p1[:, [2, 0, 1]]
return np.vstack((p1, p2, p3))
def cube():
points = np.array([
[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1],
[1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1],
])
return points
points = icosahedron()
# points = cube()
hull = sp_spatial.ConvexHull(points)
indices = hull.simplices
faces = points[indices]
print('area: ', hull.area)
print('volume: ', hull.volume)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.dist = 30
ax.azim = -140
ax.set_xlim([0, 2])
ax.set_ylim([0, 2])
ax.set_zlim([0, 2])
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
for f in faces:
face = a3.art3d.Poly3DCollection([f])
face.set_color(mpl.colors.rgb2hex(sp.rand(3)))
face.set_edgecolor('k')
face.set_alpha(0.5)
ax.add_collection3d(face)
plt.show()
Which should depict the following figure:

Categories

Resources