Plot density field depending on x, y, z coordinates with python - python

I have extracted nodal fields from my geodynamic simulation, but I'm stuck on visualizing it. Here I take rock density rho [kg/m3] as an example. The data are represented on the nodal points of a 3D rectangular grid, with a resolution of 10 * 3 * 10 km.
The lengths of my coordinates are X * Y * Z = 213 * 69 * 133 nodes, where X and Z are horizontal and Y is the vertical coordinate. The dimensions of rho are 69 * 213 * 133 (so y, x, z)
Ideally, I would like to draw some sort of box in (x,z,y) space and assign a color to it according to the value of rho. However, for now I'm fine with creating a color-coded scatter plot at the nodes. I've looked in many places, e.g. the matplotlib.mpl_toolkits.Axes3D documentation, and here, and almost all of them say something like
img = ax.scatter(x, z, y, c=rho, cmap=plt.hot())
Or versions using ax.plot_surface, or ax.plot_trisurf, and those explicitly require X, Y, Z to be of the same length.
ax.scatter also only seems to work when x, y, z and rho have the same length, which seems unnecessary to me... However I try, I get errors like (on calling the scatter command):
File "/usr/lib/python3/dist-packages/mpl_toolkits/mplot3d/axes3d.py", line 2354, in scatter
xs, ys, zs = np.broadcast_arrays(
File "<__array_function__ internals>", line 5, in broadcast_arrays
File "/usr/lib/python3/dist-packages/numpy/lib/stride_tricks.py", line 264, in broadcast_arrays
shape = _broadcast_shape(*args)
File "/usr/lib/python3/dist-packages/numpy/lib/stride_tricks.py", line 191, in _broadcast_shape
b = np.broadcast(*args[:32])
ValueError: shape mismatch: objects cannot be broadcast to a single shape
I looked up what that means, and supposedly it means that the dimensions don't match. But I checked in a debugger, and the dimensions are correct.
I tried flattening rho (same result) and I tried looping over each node and plotting each point separately (sample down below)
rho, x, y, z = read_data('/path/to/my/data')
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
for i in range(len(x)):
for j in range(len(z)):
for k in range(len(y)):
ax.scatter(x[i], z[j], y[k], c=ronew[k, i, j], cmap='hot')
cbar = plt.colorbar(orientation='horizontal')
cbar.set_label('Density [kg/m3]')
plt.xlabel("X [km]")
plt.ylabel("Z [km]")
plt.zlabel("Depth [km]")
plt.show()
I imagine I'm not the only one who needs to make these "4D" figures. Therefore, any help would be very much appreciated! To make helping easier, here's a sample data that produces the above copied error:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot as plt
x = np.arange(0, 10)
y = np.arange(0, 6)
z = np.arange(0, 20)
rho = np.random.randint(1000, 3500, (10, 6, 20))
fig = plt.figure()
print(x.shape, y.shape, z.shape, rho.shape)
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z, c=rho, cmap='hot')
plt.show()

Okay, so thanks to #JohanC 's comment I found out that the 4D scatter is not possible with matplotlib version 3.1.4 (the reason is beyond me). After upgrading matplotlib to 3.4.3 I had no problems after calling xx, yy, zz = np.meshgrid(x, y, z) and plotting like
ax.scatter(xx, zz, -yy, c=rho, cmap='hot')
I produced the following figure from the test code:
And this one from the real data file

Related

Plot a surface passing through all the data points in 3D space - Python

I am not really sure if this is possible to do, but essentially I have a list of data corresponding to x, y and z coordinates.
Below image shows the result when I plot these points using a scatter graph (which I created using Python pyplot library).
My question is, is there any way of plotting the graph of a plane that passes through all of these points instead of plotting them as single points?
When I searched online all I found was resources telling me how to find equation of plane passing though 3 points but as you can see I have many points.
Any help will be appreciated.
Let's say that to have your plot you use this code
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
ax.scatter(x, y, z)
plt.show()
and let's say that you know nrows, ncols, the number of rows (y) and columns (x) of your base grid.
If these assumptions are correct, then you can use this code to plot a surface connecting the points
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
ax.plot_surface(*(v.reshape(nrows, ncols) for v in (x, y, z)))
plt.xlabel('x') ; plt.ylabel('y')
plt.show()
or, if you want something fancier,
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'),
layout='constrained')
surf = ax.plot_surface(*(v.reshape(nrows, ncols) for v in(x, y, z)),
cmap='Blues_r', ec='gray', lw=0.2)
plt.xlabel('x') ; plt.ylabel('y')
plt.colorbar(surf)
plt.show()
The prelude to my code, if you want to check my results, is
import numpy as np
import matplotlib.pyplot as plt
nrows, ncols = 63, 126
x = np.linspace(0, 12.5, ncols)
y = np.linspace(-6.2, 6.2, nrows)
X, Y = np.meshgrid(x, y)
x, y = (V.flatten() for V in (X, Y))
z = np.sin(x)-np.cos(y)
fig, ax = ...
...

Plot 4D Contour in Python (X,Y,Z + Data)

I have a large set of measurements that I want to visualize in 4D using matplotlib in Python.
Currently, my variables are arranged in this way:
x = np.array(range(0, v1))
y = np.array(range(0, v2))
z = np.array(range(0, v3))
I have C which is a 3D array containing measurement values for each combination of the previous variables. So it has a dimension of v1*v2*v3.
Currently, I visualize my measurements using contourf function and I plot that for each z value. This results in 3D contour plot i.e. 2D + color map for the values. Now, I want to combine all the variables and look at the measurements in 4D dimensions (x, y, z, and color corresponding to the measurement value). What is the most efficient way to do this in python?
Regarding to #Sameeresque answer, I think the question was about a 4D graph like this (three coordinates x, y, z and a color as the fourth coordinate):
import numpy as np
import matplotlib.pyplot as plt
# only for example, use your grid
z = np.linspace(0, 1, 15)
x = np.linspace(0, 1, 15)
y = np.linspace(0, 1, 15)
X, Y, Z = np.meshgrid(x, y, z)
# Your 4dimension, only for example (use yours)
U = np.exp(-(X/2) ** 2 - (Y/3) ** 2 - Z ** 2)
# Creating figure
fig = plt.figure()
ax = plt.axes(projection="3d")
# Creating plot
ax.scatter3D(X, Y, Z, c=U, alpha=0.7, marker='.')
plt.show()
A 4D plot with (x,y,z) on the axis and the fourth being color can be obtained like so:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.array(range(0, 50))
y = np.array(range(0, 50))
z = np.array(range(0, 50))
colors = np.random.standard_normal(len(x))
img = ax.scatter(x, y, z, c=colors, cmap=plt.hot())
fig.colorbar(img)
plt.show()
A simple way to visualize your 4D function, call it W(x, y, z), could be producing a gif of the cross-section contour plots along the z-axis.
Package plot4d could help you do it. An example plotting an isotropic 4D function:
from plot4d import plotter
import numpy as np
plotter.plot4d(lambda x,y,z:x**2+y**2+z**2, np.linspace(0,1,20), wbounds=(0,3), fps=5)
The code above generates this gif:

Plot of 3D matrix with colour scale - Python

I would like to plot a 3D matrix - essentially a box of numbers, each labelled by an x, y, z triad of coordinates- by assigning a different colour to each of the x, y, z point, according to its magnitude (for example, bigger numbers in red and smaller numbers in blue).
I cannot plot sections of the matrix, I rather need to plot the whole matrix together.
If we call matrix3D my matrix, its elements are built this way:
matrix3D[x][y][z] = np.exp(-(x**2+y**2+z**2))
How can I obtain the desired plot?
EDIT: Using Mayavi2 Contour3D(), I have tried to write the following:
from mayavi import mlab
X = np.arange(0, n_x, 1)
Y = np.arange(0, n_z, 1)
Z = np.arange(0, n_z, 1)
X, Y, Z = np.meshgrid(X, Y, Z)
obj = mlab.contour3d(X, Y, Z, matrix3D, contours=4, transparent=True)
where n_x, n_y, n_z are the dimension of the 3 axes. How can I actually see and/or save the image now?
If you need to plot the whole thing I think you're best taking a look at mayavi. This will let you plot a volume and you should be able to get the results you need.
I know you said you need to plot the whole thing at once, but this might still be of some use. You can use countourf to plot like this:
import numpy as np
import matplotlib.pyplot as plt
matrix3D = np.empty((10, 10, 10))
x = np.arange(10)
y = np.arange(10)
z = np.arange(10)
matrix3D[x][y][z] = np.exp(-(x**2+y**2+z**2))
fig = plt.figure()
ax = fig.add_subplot(plt.subplot(1, 1, 1))
ax.contourf(x, y, matrix3D[:, :, 3])
plt.show()
This gives you a slice of the 3D matrix (in this example the 4th slice).

creating surface data for axes3d

Okay, apologies for this question but I'm pulling my hair out here.
I have a data structure loaded in python in the form:
[(1,0,#),(1,1,#),(1,2,#),(1,3,#),(2,0,#),(2,1,#) ... (26,3,#)]
with # being a different number each time that I wish to represent on the z-axis. You can see that x and y are always integers.
Plotting a scatter graph is simple:
x,y,z = zip(*data)
fig = plt.figure()
ax = fig.gca(projection = '3d')
surface = ax.scatter(x, y, z)
plt.show()
But when it comes to surfaces, I can see two methods:
1) Call ax.plot_trisurf(), which should work with 1D arrays similar to ax.scatter() and apparently works here, but for me gives me an error:
"AttributeError: Axes3D subplot object has not attribute 'plot_trisurf'"
This error also appears if I use the example source code at:
http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#tri-surface-plots, suggesting it's something wrong with my installation - my Matplotlib version is 1.1.1rc,. This error does not appear if, for example, ax.plot_surface() is called, nor ax.scatter().
2) Use meshgrid() or griddata() in combination with ax.plot_surface() - in either case, after two days' of pouring over the documentation and examples, I still don't understand how to correctly use these in my case, particularly when it comes to generating the values for Z.
Any help would be much appreciated.
To address your first question (1) I believe you need to import Axes3D from the mplot3d library, even if you're not directly calling it. Maybe try adding
from mpl_toolkits.mplot3d import Axes3D
before your main code (this line triggered a memory while reading the tutorial).
As for (2), X, Y and Z need to be matrix (2d array) type objects. This can get confusing, but you may consider an example:
# two arrays - one for each axis
x = np.arange(-5, 5, 0.25)
y = np.arange(-5, 5, 0.25)
# create a mesh / matrix like object from the arrays
X, Y = np.meshgrid(x, y)
# create Z values - also in a mesh like shape
Z = np.sin(np.sqrt(X**2 + Y**2))
# plot!
surface = ax.plot_surface(X, Y, Z)
Here is an example of how could you extract your z-values from data
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
data = [(j,i,i**2 + j) for j in range(1,27) for i in range(4)]
print data
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 4, 1)
Y = np.arange(1, 27, 1)
X, Y = np.meshgrid(X, Y)
print X.shape
print Y.shape
Z = np.array([z for _,_,z in data]).reshape(26,4)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=True)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

Streamplot error: Cannot convert float NaN to integer

I'm trying to use the streamplot function to plot a velocity field but for some reason it is failing. Here is an original SO post about the function with an example: how to plot streamlines , when i know u and v components of velocity(numpy 2d arrays), using a plotting program in python?. The example works fine for me; however, I tried to modify the values to simplify the function and imitate initial conditions and now it no longer works.
Here's my "simplified" code:
import matplotlib.pyplot as plt
import numpy as np
from streamplot import streamplot
x = np.linspace(0, 1, 10)
y = np.linspace(0, 2, 10)
u = np.zeros((len(x), len(y)))
v = np.zeros((len(x), len(y)))
u[:,len(y)-1]=1
speed = np.sqrt(u*u + v*v)
plt.figure()
plt.subplot(121)
streamplot(x, y, u, v,density=1, INTEGRATOR='RK4', color='b')
plt.subplot(122)
streamplot(x, y, u, v, density=(1,1), INTEGRATOR='RK4', color=u,
linewidth=5*speed/speed.max())
plt.show()
Any recommendations or help is appreciated.
I think the problem is that the density of your (x,y) grid (you've switched x and y in your initialization of u and v, by the way) is less than the density of the streamplot grid. When you set density=1 or (1,1) (they should be equivalent) then "the domain is divided into a 25x25 grid". I think that means that there is some smoothing going on if your data is nonzero in a slim enough region compared to the density of either the streamplot or your x-y grid. I couldn't get it to work by increasing those densities (density or the linspace spacing). but if you make two columns nonzero at the edge, it seems to work fine.
Seems like the streamplot function is not very robust for these cases and perhaps you should submit a bug.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 1, 10)
y = np.linspace(0, 2, 10)
u = np.zeros((y.size, x.size))
v = np.zeros((y.size, x.size))
u[:,-2:] = 1
speed = np.sqrt(u*u + v*v)
plt.figure()
plt.subplot(121)
plt.streamplot(x, y, u, v,density=1, color='b')
plt.subplot(122)
plt.streamplot(x, y, u, v, density=(1,1), color=u, linewidth=5*speed/speed.max())
plt.show()

Categories