Rotate 3d plot to look like 2d plot (no perspective) - python

I have created a 3d plot and want it to rotate so that the observer looks straight onto the yz-plane. I used ax.view_init(0,360) for this rotation but there is still some perspective, as you can see in the second picture. In a 2D plot the maroon and orange colored plots would meet exactly in the middle of the red plot as can be seen in the third picture. I intend to animate this rotation, and want to seemlessly continue with a 2D plot after the rotation, so ideally it would be possible to get rid of the perspective in this 3d environment, because I'm having a hard time matching the style of the 3d plot with a 2d plot.

I'd like to post a complete example but you didn't help very much (no MVE), however you can specify the projection type when you instantiate the axes:
In [6]: import matplotlib.pyplot as plt
...: from mpl_toolkits.mplot3d import Axes3D
...: %matplotlib
...:
...: ax = plt.axes(projection='3d', proj_type='ortho')
...: ax.view_init(0,360)

Related

Matplotlib plot_surface: How to convert 1D arrays to required 2D input?

Maybe this question is a duplicate because I can imagine that many people face this problem. Forgive me if so.
I want to plot a sphere in Matplotlib 3D. For that, I have a bunch of xyz coordinates. When I plot it with plot_trisurf, I get this:
So I wanted to try plot_surface, but then I get the error ValueError: Argument Z must be 2-dimensional.
This post explains why the input for plot_surface is 2D.
My question ist: How can I convert my regular xyz coordinates into the format plot_surface needs?
Edit:
Okay, I understood that 3-tuples can be differently interpreted. Is there a way then to use plot_trisurf with some kind of polar coordinates, so that it doesn't interpolate "through the xy plane" but from the coordinate origin, spherically?
If your points are created in a mesh-like way, it is best to create mesh at the same time, such as in this post.
It seems plot_trisurf creates a mesh for an open surface (like a rectangular table cloth) but not for a closed surface.
If the points aren't nicely organized, but you know all points lie on a convex 3D surface (e.g. a sphere), you can calculate the 3D convex hull and draw that.
The code below does just that. Note that some triangles look darker and some lighter. This is because the triangles returned by ConvexHull aren't nicely oriented (so that e.g. a clockwise orientation would indicate the outside face of the polygon). For that you'd need to calculate the surface normal for each triangle and reverse the triangle in case the dot product of that normal with the center of the triangle would be negative (supposing 0,0,0 lies inside the sphere).
If you need more 3D plotting power, the Mayawi library would be more appropriate.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import ConvexHull
import numpy as np
xyz = np.random.randn(3, 50) # random 3D points
xyz /= np.linalg.norm(xyz, axis=0) # project each point on a unit sphere
fig = plt.figure()
ax = fig.gca(projection='3d')
hull = ConvexHull(xyz.T)
ax.plot_trisurf(*xyz, triangles=hull.simplices, linewidth=0.2, antialiased=True)
plt.show()

RGB polar plot in Python

I am trying to produce RGB polar plots in Python and I was expecting matplotlib.pyplot.imshow to be able to do it. However, whenever I try plotting data using this method I obtain a blank output.
import matplotlib.pyplot as plt
import numpy as np
data = np.array([[[0,0,1],[0,1,0],[1,0,0]],[[0,0,0.5],[0,0.5,0],[0.5,0,0]]])
# Sample, any N,M,3 data should work
ax = plt.subplot(111,polar=True)
ax.imshow(data,extent=[0,2*np.pi,0,1]) # Produces a white circle
Is there a good way to accomplish this using the aforementioned method or another ?
Thanks.
EDIT: I managed to make a single quadrant by using extent=[0,np.pi/2,0,1] but its use is clearly bugged for polar plots. since anything but a full quadrant doesn't produce the expected outcome.
Using imshow on a polar plot is unfortunately not possible, because the imshow grid is necessarily quadratic in its pixels. You may however use pcolormesh and apply a trick (similar to this one), namely to provide the colors as color argument to pcolormesh, as it would usually just take 2D input.
import matplotlib.pyplot as plt
import numpy as np
data = np.array([[[0,0,1],[0,1,0],[1,0,0]],
[[0,0,0.5],[0,0.5,0],[0.5,0,0]]])
ax = plt.subplot(111, polar=True)
#get coordinates:
phi = np.linspace(0,2*np.pi,data.shape[1]+1)
r = np.linspace(0,1,data.shape[0]+1)
Phi,R = np.meshgrid(phi, r)
# get color
color = data.reshape((data.shape[0]*data.shape[1],data.shape[2]))
# plot colormesh with Phi, R as coordinates,
# and some 2D array of the same shape as the image, except the last dimension
# provide colors as `color` argument
m = plt.pcolormesh(Phi,R,data[:,:,0], color=color, linewidth=0)
# This is necessary to let the `color` argument determine the color
m.set_array(None)
plt.show()
The result is not a circle because you do not have enough points. Repeating the data, data = np.repeat(data, 25, axis=1) would then allow to get a circle.

matplotlib: remove 3D plot's white spaces in mixed 2D/3D subplots

I am having trouble removing the excessive white spaces when mixing 2D and 3D subplots. For pure 3D subplots, I can adjust the region being plotted with fig.subplots_adjust() to remove the white spaces, see here.
However, the same trick doesn't work if this 3D image is inside a 2D subplots.
I created the mixed subplots like the following:
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import axes3d
fig,axes = plt.subplots(2,2)
ax = axes.flat
for a in range(3):
ax[a].plot(range(10),range(10))
ax[3].remove()
ax[3] = fig.add_subplot(224,projection='3d')
X, Y, Z = axes3d.get_test_data(0.03)
ax[3].plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.8,cmap=cm.coolwarm)
ax[3].set_xticklabels('')
ax[3].set_yticklabels('')
ax[3].set_zticklabels('')
fig.subplots_adjust(hspace=0,wspace=0)
Now the trick eg. fig.subplots_adjust(left=-0.01) will act on the 2D subplot's left edge, and the 3D subplots is not modified. Is there a way to completely remove the white spaces surrounding the 3D subplot? I also tried smaller ax.dist and it is not good if the 3D plot is longer in say z-direction.
There is no whitespace around the axes, it even overlaps the other subplots (their spines are hidden by the 3D axes).
What you want is to adjust the size of gray cube inside the axes. This can be done by changing the viewing distance to that cube.
E.g. ax[3].dist = 7
ax[3].dist = 9
The optimal distance depends of course on the viewing angle.

imagesc like feature with non-rectangular grids [MATLAB]

If i want to color a square grid with different color in each grid cells, then it is possible in MATLAB with a simple call to imagesc command like here.
What if i want to color different cells in a grid like this:
Is this functionality available by default in either python or Matlab? I tried discretizing this grid with very small square cells. And then color each cell. That works. But it seems ordinary. Is there a smarter way to get his done?
In python, there is the builtin polar projection for the axes. This projection allows you to automatically use almost every plotting method in polar coordinates. In particular, you need to you pcolor or pcolormesh as follows
import numpy as np
from matplotlib import pyplot as plt
r = np.linspace(0,4,5)
theta = np.linspace(0,2*np.pi,10)
theta,r = np.meshgrid(theta,r)
values = np.random.rand(*(theta.shape))
ax = plt.subplot(111,polar=True)
ax.pcolor(theta,r,values)
plt.show()
Note that this will produce a plot like this
which is almost what you want. The obvious problem is that the patch vertices are joined by straight lines and not lines that follow the circle arc. You can solve this by making the angles array denser. Here is a posible way to do it.
import numpy as np
from matplotlib import pyplot as plt
r = np.linspace(0,4,5)
theta = np.linspace(0,2*np.pi,10)
values = np.random.rand(r.size,theta.size)
dense_theta = np.linspace(0,2*np.pi,100)
v_indeces = np.zeros_like(dense_theta,dtype=np.int)
i = -1
for j,dt in enumerate(dense_theta):
if dt>=theta[i+1]:
i+=1
v_indeces[j] = i
T,R = np.meshgrid(dense_theta,r)
dense_values = np.zeros_like(T)
for i,v in enumerate(values):
for j,ind in enumerate(v_indeces):
dense_values[i,j] = v[ind]
ax = plt.subplot(111,polar=True)
ax.pcolor(T,R,dense_values)
plt.show()
Which would produce
I am not aware of a way to do this in matlab but I googled around and found this that says it can produce pcolor plots in polar coordinates. You should check it out.

Creating a rotatable 3D earth

I know we can create simple 3-Dimensional spheres using matplotlib, an example of such a sphere is included in the documentation.
Now, we also have a warp method as part of the matplotlib module, an example of it's usage is here .
To warp a cylindrical image to the sphere. Is it possible to combine these methods to create a 3D rotatable earth? Unless my way of thinking about this problem is way off it seems that to be able to do this you would have to take the pixel data of the image and then plot every pixel using the sin and cosine expressions along the surface of the 3D sphere being created in the first example. Some examples of these cylindrical maps can be found here
I know alternative ways to do this are through maya and blender, but I am attempting to stay within matplotlib to do this, as I want to create this plot and then be able to plot geospatial data to the surface using an array of data.
Interesting question. I tried to basically follow the thinking outlined by #Skeletor, and map the image so that it can be shown with plot_surface:
import PIL
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
# load bluemarble with PIL
bm = PIL.Image.open('bluemarble.jpg')
# it's big, so I'll rescale it, convert to array, and divide by 256 to get RGB values that matplotlib accept
bm = np.array(bm.resize([d/5 for d in bm.size]))/256.
# coordinates of the image - don't know if this is entirely accurate, but probably close
lons = np.linspace(-180, 180, bm.shape[1]) * np.pi/180
lats = np.linspace(-90, 90, bm.shape[0])[::-1] * np.pi/180
# repeat code from one of the examples linked to in the question, except for specifying facecolors:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.outer(np.cos(lons), np.cos(lats)).T
y = np.outer(np.sin(lons), np.cos(lats)).T
z = np.outer(np.ones(np.size(lons)), np.sin(lats)).T
ax.plot_surface(x, y, z, rstride=4, cstride=4, facecolors = bm)
plt.show()
Result:
Here what I made some hours ago:
First we import the needed libraries:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import imageio
Secondly, we make the figures and stored them as png in our directory:
Note that I wrote range(0,330,20)
for i in range(0,330,20):
my_map = Basemap(projection='ortho', lat_0=0, lon_0=i, resolution='l', area_thresh=1000.0)
my_map.bluemarble()
my_map.etopo()
name=str(i)
path='/path/to/your/directory/'+name
plt.savefig(path+'.png')
plt.show()
plt.clf()
plt.cla()
plt.close()
And finally we can join all the images in an animated GIF:
images = []
for f in range(0,330,20):
images.append(imageio.imread("/path/to/your/directory/"+str(f)+".png"))
imageio.mimsave('movie.gif', images, duration=0.5)
and then enjoy the result:
I could imagine the following solution:
Using numpy.roll you could shift your array by one column (ore more) with each call. So you could load your image of the earth surface into a numpy array as a template and export the rotated image into a jpg. This you plot as shown in the warp example.

Categories