I have a cubic grid as shown in the picture below.
I would like to list the vertices of each sub-cube, so I would end up with a nested list of sub-cubes with their corresponding list of vertices.
My initial attempt was to use a generator,
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
dims = [9,9,9]
spacer = 3
subBoxCoords = np.array([(x, y, z) for x in range(0, dims[0], spacer) for y in range(0, dims[1], spacer) for z in range(0, dims[2], spacer)])
ax.scatter(subBoxCoords[:,0], subBoxCoords[:,1], subBoxCoords[:,2], c='k', marker='o')
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
This does give me the desired shape but coordinates are ordered in a manner that vertices extraction of the sub-boxes is not straight forward. Also I would like to generalize this to boxes of arbitrary dimension so hard coding in intervals is not a solution.
So, then I thought I would use meshgrid,
nx,ny, nz = (3,3,3)
x = np.linspace(0, 10, nx)
y = np.linspace(0, 10, ny)
z = np.linspace(0, 10, nz)
xv, yv, zv = np.meshgrid(x, y, z, indexing='xy')
ax.scatter(xv, yv, zv, c='g', marker='^')
This appears to be a very powerful way to achieve what I want but I am getting confused. Is there a direct way access vertices in the meshgrid in the manner vertex(x,y,z)? Or even a straight forward way to extract sub-cubes?
It seems to me that the solution is tantalizingly close but I just cant grasp it!
meshgrid is probably what you need, but the shape of the array returned by meshgrid is where it gets confusing. Meshgrid returns three coordinate arrays, all the same shape. The shape of each of xv, yv, zv is (len(x), len(y), len(z)). So, to extract the coordinate at the corner (0, 2, 1), you would write xv[0, 2, 1], yv[0, 2, 1], zv[0, 2, 1]
To extract all of the subcubes' corners' coordinates, it helps to observe that, because of the way the arrays returned by meshgrid are ordered sequentially, xv[:-1, :-1, :-1] returns only the x-coordinates of the near-left-bottom corners of each subcube. Likewise, xv[1:, 1:, 1:] returns the far-right-top corners of each subcube. The other six corners are given by the other six combinations of the slices :-1 and 1: (xv[:-1, 1:, :-1] gives the far-left-top corner, for example).
So, iterate through all eight combinations of :-1 and 1: to get eight parallel arrays of three parallel arrays of x, y, z coordinates for the eight corners of all len(x)-1 * len(y-1) * len(z-1) subcubes. (If you need your subcube corner coordinate arrays to be in a particular shape or axis order, or if you want to use a single index to specify the subcube rather than three, use rollaxis, swapaxis and shape as needed.)
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import itertools
nx, ny, nz = (3,3,3)
x = np.linspace(0, 10, nx)
y = np.linspace(0, 10, ny)
z = np.linspace(0, 10, nz)
xv, yv, zv = np.meshgrid(x, y, z, indexing='xy')
slices = slice(None, -1), slice(1, None)
cornerSlices = list(itertools.product(slices, slices, slices))
corners = np.array([(xv[s], yv[s], zv[s]) for s in cornerSlices])
# The shape of `corners` is `(len(cornerSlices), 3, len(x-1), len(y-1), len(z-1)`
# The axes of `corners` represent, in the same order: the corner index; the cartesian
# coordinate axis (the index into [x, y, z]); the x, y, and z indexes of the subcube.
# Plot the first subcube (subcube 0, 0, 0)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
subcube = corners[:, :, 0, 0, 0]
subcubeX = subcube [:, 0]
subcubeY = subcube [:, 1]
subcubeZ = subcube [:, 2]
ax.scatter(subcubeX , subcubeY , subcubeZ , c='g', marker='^')
There's invariably a way to get the indexes into xv, yv, zv instead of getting the values, since the values are duplicated quite a few times in the corners array. It would involve slicing arrays of indexes into xv, yv, zv instead of slicing the arrays themselves. My head is already spinning after getting this far into the ndarray voodoo, so I'll leave that as an exercise.
If I understand the question correctly -
x = np.linspace(0,10,3)
xs = np.array(np.meshgrid(x,x,x))
points = np.transpose(xs.reshape(3,-1))
points
Related
I want to code a program to generate an array with coordinates to follow for drawing a shape like the white here, given are the blue points. Does anyone know how to do something like that or at least can give me a tip?
You could use e.g. InterpolatedUnivariateSpline to interpolate the points. As these spline functions are usually 1D, you could calculate x and y positions separately, depending on a new variable t going from 0 to 1.
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
# positions of the given points
px = [1, 4, 3, 2, 5]
py = [1, 3, 4, 3, 1]
# 5 t-values, at t=0 in point 1, at t=1 reaching point 5
pt = np.linspace(0, 1, len(px))
# sx and sy are functions that interpolate the points at the given t-values
sx = interpolate.InterpolatedUnivariateSpline(pt, px)
sy = interpolate.InterpolatedUnivariateSpline(pt, py)
# calculate many intermediate values
t = np.linspace(0, 1, 500)
x = sx(t)
y = sy(t)
# show the original points together with the spline
fig, ax = plt.subplots(facecolor='black')
ax.axis('off')
plt.scatter(px, py, s=80, color='skyblue')
plt.plot(x, y, color='white')
for i, (xi, yi) in enumerate(zip(px, py), start=1):
ax.text(xi, yi, f'\n {i}', ha='left', va='center', size=30, color='yellow')
plt.show()
I cannot make it clear for me, how pyplot trisurf works. All the examples I have seen on the Internet use numpy, pandas and other stuff impeding understanding this tool
Pyplot docs say it requires X, Y and Z as 1D arrays. But if I try to provide them, it issues a RuntimeError: Error in qhull Delaunay triangulation calculation: singular input data (exitcode=2); use python verbose option (-v) to see original qhull error. I tried using python list and numpy arange
What are exactly those 1D arrays the tool wants me to provide?
plot_trisurf, when no explicit triangles are given, connects nearby 3D points with triangles to form some kind of surface. X is a 1D array (or a list) of the x-coordinates of these points (similar for Y and Z).
It doesn't work too well when all points lie on the same 3D line. For example, setting all X, Y and Z to [1, 2, 3] will result in a line, not a triangle. P1=(1,1,1), P2=(2,2,2), P3=(3,3,3). The n'th point will use the n'th x, the n'th y and the n'th z. A simple example would be ´ax.plot_trisurf([0, 1, 1], [0, 0, 1], [1, 2, 3])`.
Here is an example:
from mpl_toolkits import mplot3d
import matplotlib.pyplot as plt
from math import sin, cos, pi
fig = plt.figure(figsize=(14, 9))
ax1 = fig.add_subplot(1, 2, 1, projection='3d')
ax1.plot_trisurf([0, 1, 1], [0, 0, 1], [1, 2, 3],
facecolor='cornflowerblue', edgecolor='crimson', alpha=0.4, linewidth=4, antialiased=True)
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
N = 12
X = [0] + [sin(a * 2 * pi / N) for a in range(N)]
Y = [0] + [cos(a * 2 * pi / N) for a in range(N)]
Z = [1] + [0 for a in range(N)]
ax2.plot_trisurf(X, Y, Z,
facecolor='cornflowerblue', edgecolor='crimson', alpha=0.4, linewidth=4, antialiased=True)
plt.show()
So I have an equation lets say: x^2 + y^2
Currently, I can make an array that defines the equation, calculates the array based off input parameters and and prints out an array:
def equation(x,y):
return x**2 + y**2
def calculate(x, y, xmin, ymin):
out = []
for i in range(x_min, xs):
row = []
for j in range(y_min, ys):
row.append(equation(i, j))
out.append(row)
return out
The output array calculates the values based off the indicies
output such that (0,0) is upper left. Given an array with length and width, how can I calculate the equation so that the (0,0) is centered and follows a cartesian plane?
To center your data around 0,0,0 and plot the result, you could do something like the following:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def equation(x,y):
return x**2 + y**2
x = [(i-50)/10 for i in range(0,100,1)]
y = x
z = [equation(i, j) for i, j in zip(x, y)]
# plot the function
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(x, y, z, c='r', marker='o')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# rotate the plot so we can see the 3 dimensions
for angle in range(0, 360):
ax.view_init(30, angle)
plt.draw()
plt.pause(.001)
Result:
Basically you would just need to redefine what you are iterating over to put (0, 0) in the middle. I would suggest that you use a library like numpy though and take advantage of vectorized functions to speed up (and simplify) your code. For example:
import numpy as np
x = np.linspace(-1, 1, 11) # create 11 points arranged from -1 to 1
X, Y = np.meshgrid(x, x) # this produces the input values you'd get out of your double loop
result = np.square(X) + np.square(Y) # your equation, applied to everything at once
I made an odd number of points centered at 0 so that we would actually have an input value of (0, 0) right in the center. We can plot the result with the following:
from matplotlib import pyplot as plt
plt.imshow(result)
Note that the axis ticks are wrong here because imshow doesn't care what our original inputs were, but that dark spot in the center is your (0, 0) input point.
How can I interpolate a hysteresis loop at specific x points? Multiple related questions/answers are available on SOF regarding B-spline interpolation using scipy.interpolate.splprep (other questions here or here). However, I have hundreds of hysteresis loops at very similar (but not exactly same) x positions and I would like to perform B-spline interpolation on all of them at specific x coordinates.
Taking a previous example:
import numpy as np
from scipy import interpolate
from matplotlib import pyplot as plt
x = np.array([23, 24, 24, 25, 25])
y = np.array([13, 12, 13, 12, 13])
# append the starting x,y coordinates
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
# fit splines to x=f(u) and y=g(u), treating both as periodic. also note that s=0
# is needed in order to force the spline fit to pass through all the input points.
tck, u = interpolate.splprep([x, y], s=0, per=True)
# evaluate the spline fits for 1000 evenly spaced distance values
xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck)
# plot the result
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, 'or')
ax.plot(xi, yi, '-b')
plt.show()
Is it possible to provide specific x values to interpolate.splev? I get unexpected results:
x2, y2 = interpolate.splev(np.linspace(start=23, stop=25, num=30), tck)
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, 'or')
ax.plot(x2, y2, '-b')
plt.show()
The b-spline gives x and y positions for a given u (between 0 and 1).
Getting y positions for a given x position involves solving for the inverse. As there can be many y's corresponding to one x (in the given example there are places with 4 y's, for example at x=24).
A simple way to get a list of (x,y)'s for x between two limits, is to create a filter:
import numpy as np
from scipy import interpolate
from matplotlib import pyplot as plt
x = np.array([23, 24, 24, 25, 25])
y = np.array([13, 12, 13, 12, 13])
# append the starting x,y coordinates
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
tck, u = interpolate.splprep([x, y], s=0, per=True)
# evaluate the spline fits for 1000 evenly spaced distance values
xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck)
# plot the result
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, 'or')
ax.plot(xi, yi, '-b')
filter = (xi >= 24) & (xi <= 25)
x2 = xi[filter]
y2 = yi[filter]
ax.scatter(x2, y2, color='c')
plt.show()
I have some z=f(x,y) data which i would like to plot. The issue is that (x,y) are not part of a "nice" rectangle, but rather arbitrary parallelograms, as shown in the attached image (this particular one is also a rectangle, but you could think of more general cases). So I am having a hard time figuring out how I can use plot_surface in this case, as this usually will take x and y as 2d arrays, and here my x-and y-values are 1d. Thanks.
Abritrary points can be supplied as 1D arrays to matplotlib.Axes3D.plot_trisurf. It doesn't matter whether they follow a specific structure.
Other methods which would depend on the structure of the data would be
Interpolate the points on a regular rectangular grid. This can be accomplished using scipy.interpolate.griddata. See example here
Reshape the input arrays such that they live on a regular and then use plot_surface(). Depending on the order by which the points are supplied, this could be a very easy solution for a grid with "parallelogramic" shape.
As can be seen from the sphere example, plot_surface() also works in cases of very unequal grid shapes, as long as it's structured in a regular way.
Here are some examples:
For completeness, find here the code that produces the above image:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
f = lambda x,y: np.sin(x+0.4*y)*0.23+1
fig = plt.figure(figsize=(5,6))
plt.subplots_adjust(left=0.1, top=0.95,wspace=0.01)
ax0 = fig.add_subplot(322, projection="3d")
ma = 6*(np.random.rand(100)-0.5)
mb = 6*(np.random.rand(100)-0.5)
phi = np.pi/4
x = 1.7*ma*np.cos(phi) + 1.7*mb*np.sin(phi)
y = -1.2*ma*np.sin(phi) +1.2* mb*np.cos(phi)
z = f(x,y)
ax0.plot_trisurf(x,y,z)
ax1 = fig.add_subplot(321)
ax0.set_title("random plot_trisurf()")
ax1.set_aspect("equal")
ax1.scatter(x,y, marker="+", alpha=0.4)
for i in range(len(x)):
ax1.text(x[i],y[i], i , ha="center", va="center", fontsize=6)
n = 10
a = np.linspace(-3, 3, n)
ma, mb = np.meshgrid(a,a)
phi = np.pi/4
xm = 1.7*ma*np.cos(phi) + 1.7*mb*np.sin(phi)
ym = -1.2*ma*np.sin(phi) +1.2* mb*np.cos(phi)
shuf = np.c_[xm.flatten(), ym.flatten()]
np.random.shuffle(shuf)
x = shuf[:,0]
y = shuf[:,1]
z = f(x,y)
ax2 = fig.add_subplot(324, projection="3d")
ax2.plot_trisurf(x,y,z)
ax3 = fig.add_subplot(323)
ax2.set_title("unstructured plot_trisurf()")
ax3.set_aspect("equal")
ax3.scatter(x,y, marker="+", alpha=0.4)
for i in range(len(x)):
ax3.text(x[i],y[i], i , ha="center", va="center", fontsize=6)
x = xm.flatten()
y = ym.flatten()
z = f(x,y)
X = x.reshape(10,10)
Y = y.reshape(10,10)
Z = z.reshape(10,10)
ax4 = fig.add_subplot(326, projection="3d")
ax4.plot_surface(X,Y,Z)
ax5 = fig.add_subplot(325)
ax4.set_title("regular plot_surf()")
ax5.set_aspect("equal")
ax5.scatter(x,y, marker="+", alpha=0.4)
for i in range(len(x)):
ax5.text(x[i],y[i], i , ha="center", va="center", fontsize=6)
for axes in [ax0, ax2,ax4]:
axes.set_xlim([-3.5,3.5])
axes.set_ylim([-3.5,3.5])
axes.set_zlim([0.9,2.0])
axes.axis("off")
plt.savefig(__file__+".png")
plt.show()
If your data is in order, and you know the size of the parallgram, a reshape will probably suffice:
ax.surface(x.reshape(10, 10), y.reshape(10, 10), z.reshape(10, 10))
Will work if the parallelogram has 10 points on each side, and the points are ordered in a zigzag pattern