create stereoscopic 3d plot with matplotlib - python

I would like to be able to make 3d plots in matplotlib that can be displayed in stereoscopic 3d, like this:
The camera of the left image is slightly translated with respect to the right. If you practice for a bit, you can trick your brain into having your left eye look at the left image and your right eye at the right image and see 3D on any screen!
Anyway, I would like to be able to make any matplotlib 3d plot show up twice, with one camera slightly translated. I've managed to get just the plot itself in stereo, simply by translating the data:
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,5))
axl = fig.add_subplot(1,2,1,projection='3d')
axr = fig.add_subplot(1,2,2,projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
axr.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
axl.plot_surface(X, Y-5, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
for ax in [axr,axl]:
ax.set_zlim(-1.01, 1.01)
ax.set_ylim(-10,10)
ax.view_init(azim=10)
ax.set_axis_off()
This gives the desired effect, but I would like to be able to also show the axes themselves in stereoscopic 3d. I can only find how to set the elevation and azimuth of the camera, using the view_init method of the Axes object. There doesn't seem to be a 'nice' way of translating the camera.
Does anyone know how to do it?

Related

Matplotlib bar chart or similar with bars located at a specific x,y,angle

Is there a way to create a bar chart using matplotlib such that the bars are located at a specific x,y and at a specific angle? In the screenshot below, I just drew thick lines (to represent thin bars) in PowerPoint on top of the scatterplot.
It doesn't have to be a barchart necessarily, I just don't know the name of a plot that is like this. I thought about trying to mimic this with a quiver plot but wasn't sure how. Reason for wanting this is densely spaced points that have variable values (not monotonically increasing like in this example), and just coloring the scatter plot isn't visually elucidating trends of interest, even with different colormaps.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(25)
y = -x
z = x
plt.scatter(x, y, c=z, cmap='viridis')
I don't know of a canned way to do this, but you could, in a pinch, create your own function that draws rectangles to create this plot. For example:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
x = np.arange(25)
y = -x
z = x
plt.scatter(x, y, c=z, cmap='viridis')
def slanted_bars(x, y, z, angle, ax):
for xi, yi, zi in zip(x, y, z):
ax.add_patch(Rectangle((xi, yi), 1, zi, angle))
fig, ax = plt.subplots(1, 1)
ax.scatter(x, y, c=z, cmap='viridis')
slanted_bars(x, y, z, -45, ax)
You'd have to play with the color and shape of the rectangles to get something appealing, but it can do what you want.

Smooth surface plot in Python

I would like to create a smooth plot in Python. Generally, you can make a plot that looks like the one below:
Source
While this is a nice image, it looks as though it's made out of a mesh of polygons, making it look "coarse." In my own plots I have tried increasing the resolution of my function to no avail. I am trying to achieve the following "smooth" look:
Source
How do I achieve this?
Maybe you were missing rcount and ccount?
# This import registers the 3D projection, but is otherwise unused.
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
# Make data.
X = np.arange(-5, 5, 0.05)
Y = np.arange(-5, 5, 0.05)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False, rcount=200, ccount=200)
# Customize the z axis.
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()

Relocate colorbar

Is it possible to put the color diagram (which is now on the right side of the original figure) on the top of the figure?
My code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
import numpy as np
# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))
#===============
# First subplot
#===============
# set up the axes for the first plot
ax = fig.add_subplot(1, 2, 1, projection='3d')
# plot a 3D surface like in the example mplot3d/surface3d_demo
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
fig.colorbar(surf, shrink=0.5, aspect=10)
fig.savefig('64bit.png')
You have to add additional axes (add_axes) to put your colorbar at the desired position:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))
#===============
# First subplot
#===============
# set up the axes for the first plot
ax = fig.add_subplot(1, 2, 1, projection='3d')
# plot a 3D surface like in the example mplot3d/surface3d_demo
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
# position of colorbar
# where arg is [left, bottom, width, height]
cax = fig.add_axes([0.15, .87, 0.35, 0.03])
fig.colorbar(surf, orientation='horizontal', cax=cax)
plt.show()
Yes it is, there are multiple answers here in the site showing you how to move the colorbar around like this one: positioning the colorbar
In your case, you want to combine that with the orientation argument. As far as I know, there is no easy way of just placing the colorbar to the top of your figure automatically, you will have to place it manually. Here is my code that replaces your fig.colorbar(surf, shrink=0.5, aspect=10):
cbax = fig.add_axes([0.1, 0.89, 0.5, 0.05])
fig.colorbar(surf, orientation="horizontal", cax=cbax)
The numbers in the list describe some characteristics of the colorbar which are [left, bottom, width, height] as mentioned in the other answer that I have attached.
These numbers came out nicely for your plot, feel free to change them to your liking.
In order to get the colorbar on top of the plot you need to create some axes, designated to host the colorbar.
This can either be done manually by placing a new axes at some given position in figure coordinates,
cax = fig.add_axes([0.2,0.8,0.3,.05])
fig.colorbar(surf, cax=cax, orientation="horizontal")
or, by using a subplot grid (gridspec), which is shown in the following:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
import matplotlib.gridspec as gridspec
import numpy as np
x = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(x,x)
Z = np.sin(np.sqrt(X**2 + Y**2))
gs = gridspec.GridSpec(2, 2, height_ratios=[0.05,1])
fig = plt.figure()
ax = fig.add_subplot(gs[1,0], projection='3d')
cax = fig.add_subplot(gs[0,0])
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap="coolwarm",
linewidth=0, antialiased=False, vmin=-1, vmax=1)
fig.colorbar(surf, cax=cax, orientation="horizontal", ticks=[-1,0,1])
plt.show()
For a method which avoids having to manually create new axes and instead allows us to keep the colorbar linked to an existing plot axis, we can use the location keyword (method adapted initially from here).
The location argument is meant to be used on colorbars which reference multiple axes in a list (and will throw an error if colorbar is given only one axis), but if you simply put your one axis in a list, it will allow you to use the argument. You can use the following code as an example:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
axp = ax.imshow(np.random.randint(0, 100, (100, 100)))
cb = plt.colorbar(axp,ax=[ax],location='top')
plt.show()
which yields this plot. From here, you can edit the colorbar using the typical methods (pad, shrink, etc.) to further tune the appearance of your plot.
Fair warning, I haven't seen this method used many other places and it could be less robust than going through the extra steps of creating a new axis for your colorbar.

How to rotate a 3d plot in python? (or as a animation) Rotate 3-D view using mouse

I have this code which contains a 3D plot. I run the code in Spyder; I want to know if it is possible to make this plot a rotating one (360 degrees) and save it.
Thanks!
P.s. Sorry if it is a silly question, but I am a newby in Python.
import matplotlib.pyplot as plt
import numpy as np
from scipy import array
jet = plt.get_cmap('jet')
from matplotlib import animation
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.linspace(70,40,4)
Y = np.linspace(5,2,4)
X,Y= np.meshgrid(X, Y)
Z = array ([
[1223.539555, 1428.075086,1714.479425, 2144.053223],
[1567.26647,1829.056119,2990.416079,2745.320067],
[2135.163957,2491.534201, 2990.416079,3738.761638],
[3257.280827, 3800.655101, 4561.372117, 5702.458776],
])
surf = ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1, cmap = jet,linewidth = 0,alpha= 1)
ax.set_zlim3d(0, Z.max())
fig.colorbar(surf, shrink=0.8, aspect=5)
ax.set_xlabel('Axial Length [mm]')
ax.set_ylabel('nbTurns')
ax.set_zlabel('RPM')
plt.show()
You need to define a function in order to get a specific animation. In your case it is a simple rotation:
def rotate(angle):
ax.view_init(azim=angle)
Then use the matplotlib animation:
rot_animation = animation.FuncAnimation(fig, rotate, frames=np.arange(0,362,2),interval=100)
This will call the rotate function with the frames argument as angles and with an interval of 100ms, so this will result in a rotation over 360° with a 2° step each 100ms. To save the animation as a gif file:
rot_animation.save('path/rotation.gif', dpi=80, writer='imagemagick')

How to disable perspective in mplot3d?

Is it possible to disable the perspective when plotting in mplot3d, i.e. to use the orthogonal projection?
This is now official included since matplot version 2.2.2 Whats new | github
So for plotting a perspective orthogonal plot you have to add proj_type = 'ortho' then you should have something like that:
fig.add_subplot(121, projection='3d', proj_type = 'ortho')
Example Picture
]2
Example is taken from the official example script and edited
'''
======================
3D surface (color map)
======================
Demonstrates plotting a 3D surface colored with the coolwarm color map.
The surface is made opaque by using antialiased=False.
Also demonstrates using the LinearLocator and custom formatting for the
z axis tick labels.
'''
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np
# Make data.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
# Plot the surface.
fig = plt.figure(figsize=(16,4))
ax.view_init(40, 60)
ax = fig.add_subplot(121, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
ax = fig.add_subplot(122, projection='3d', proj_type = 'ortho')
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.viridis, linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
plt.show()
NOTE: This has been updated see this answer instead.
Sort of, you can run this snippet of code before you plot:
import numpy
from mpl_toolkits.mplot3d import proj3d
def orthogonal_proj(zfront, zback):
a = (zfront+zback)/(zfront-zback)
b = -2*(zfront*zback)/(zfront-zback)
return numpy.array([[1,0,0,0],
[0,1,0,0],
[0,0,a,b],
[0,0,0,zback]])
proj3d.persp_transformation = orthogonal_proj
It is currently an open issue found here.

Categories

Resources