How to draw intersecting planes? - python

I want to use matplotlib to draw more or less the figure I attached below, which includes the two intersecting planes with the right amount of transparency indicating their relative orientations, and the circles and vectors in the two planes projected in 2D.
I'm not sure if there is an existing package for doing this, any hints?

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')
dim = 10
X, Y = np.meshgrid([-dim, dim], [-dim, dim])
Z = np.zeros((2, 2))
angle = .5
X2, Y2 = np.meshgrid([-dim, dim], [0, dim])
Z2 = Y2 * angle
X3, Y3 = np.meshgrid([-dim, dim], [-dim, 0])
Z3 = Y3 * angle
r = 7
M = 1000
th = np.linspace(0, 2 * np.pi, M)
x, y, z = r * np.cos(th), r * np.sin(th), angle * r * np.sin(th)
ax.plot_surface(X2, Y3, Z3, color='blue', alpha=.5, linewidth=0, zorder=-1)
ax.plot(x[y < 0], y[y < 0], z[y < 0], lw=5, linestyle='--', color='green',
zorder=0)
ax.plot_surface(X, Y, Z, color='red', alpha=.5, linewidth=0, zorder=1)
ax.plot(r * np.sin(th), r * np.cos(th), np.zeros(M), lw=5, linestyle='--',
color='k', zorder=2)
ax.plot_surface(X2, Y2, Z2, color='blue', alpha=.5, linewidth=0, zorder=3)
ax.plot(x[y > 0], y[y > 0], z[y > 0], lw=5, linestyle='--', color='green',
zorder=4)
plt.axis('off')
plt.show()
caveats:
I am running a version very close to the current master, so I am not
sure what will work in older versions
The reason for splitting up the plotting is that 'above' and 'below' are determined in a some what arcane way (I am not strictly sure the zorder actually does anything), and is really dependent on the order the artists are drawn in. Thus surfaces can not intersect (one will be above the other every where), so you need to plot the sections on either side of the intersection separately. (You can see this in the black line which I didn't split at looks like it in 'on top of' the upper blue plane).
The 'proper' ordering of the surfaces also seems to be dependent on the view angle.

Matplotlib does have 3d projection capability, but the dashed lines are drawn with constant width in the final 2D image view, not looking as if laid flat on the tilted planes. If the geometry is simple, and the "orbits" circular, it might work, but if you're wanting to draw ellipses seen at an angle, the viewer may desire more visual clues on the whole 3D arrangement.
If I had to make one nice fancy illustration like that, but even nicer and fancier, and it didn't have to be automated, I'd start by creating the graphics - at least the dashed line circles - for each of the planes as a simple flat 2D image using whatever seems handy at the moment - a vector drawing program like Illustrator or Inkscape, or in matplotlib if there is data to be followed.
Then, I'd use POV-Ray or Blender to model the planes at whatever angles, spheres for the round things (planets?). The 2D graphics already generated would become textures to be mapped to the planes. POV-Ray uses a scripting language allowing a record to be kept, modified, and copied for future projets. If it were really one-time and I didn't mind doing it all by hand, Blender is good. Whichever tool I use, the result is an image showing the desired projection of the 3D geometric elements into 2D.
Are the round things, what I'm calling "planets" supposed to be flat circles in the final work, like in the examples? Then I'd draw them with a vector drawing app over the rendered 3D image. But I suspect you'd prefer spheres in 3D.
The samples shown have no lighting or shadows. Shadows would help with clarifying the geometry in 3D, although the first of those two illustrations isn't too bad. The short green line showing the inclined plane's planet over the red line seems clear enough but a shadow would help. The second illustration does look a bit more confusing as to the shape, location an intersections of the various entities. Here, shadows would help more. POV-Ray or Blender will happily create these with little effort. Even more, inter-reflections, known as radiosity, help with seeing 3D relations in 2D images. This advanced effect is easy to do these days, not needing expertise in optics or graphics, just knowing that it exists.
Of course, this advice is no good unless one is already familiar with 3D graphics and tools such s POV-Ray.
For an automated solution, using OpenGL in some quick and dirty program may be best. Shadows may take some work, though.

Related

Wrong orientation of a contour plot in Python using Matplotlib's contourf()

I am a beginner in Python and I'll try to visualize a function depending on x and y. With the contour plot the maximum should be seen easier.
That is my code and the output. The question comes below.
# Generate synthetic plot
x_fine=np.linspace(-4,10,100).reshape(-1,1)
y_fine=np.linspace(-3,3,100)
f_xy=1.5*np.sin(x_fine) - 0.1*(x_fine-3)**2 +10 - 0.5*(y_fine**2-2) + np.sin(y_fine)*2
#f_xy= -(x_fine-2)**2 - (y_fine)**2 +20
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(x_fine, y_fine, f_xy, cmap=cm.coolwarm, linewidth=0, antialiased=False, alpha=0.6)
ax.contourf(x_fine.flatten(),y_fine, f_xy, zdir='z', offset=-0, cmap=cm.coolwarm,alpha=0.7)
ax.set_xlabel("$x$")
ax.set_ylabel("$y$")
ax.set_zlabel("Objective")
plt.show()
So, if you might see, the plot contour plot reveals the contours, but somehow rotated by 90 degrees. If I just change the x and y input in the contour plot method, the graphs deforms. If I change the x and y input both in the contour plot and in the plot_surface command, the graph is shown correctly. But then, I need declare my x-axis as "y" and vice versa, which I would like to avoid.
I hope I made my problem clear. I am interested in reading your answers what I might have done wrong or why the code behaves as is behaves :D
In numpy, the axes are ordered row-column, e.g.
a = np.zeros((3, 2))
a
>>> [[0, 0],
[0, 0],
[0, 0]]
When you reshape x_fine to (-1, 1) you give it a width of 1 and a height of N. Something like.
x_fine
>> [[-4],
[-3.96],
...
[10]]
Following the convention of x across and y up/down, this is the wrong orientation.
Put the reshap(-1, 1) on y_fine instead. Then when you draw the contours call flatten() on y_fine instead of x_fine.
Now the two plots are orientated the same. If you want to verify that they are rotated correctly, and not BOTH off by 90 degrees, set f_xy to something simple like f_xy = x_fine + np.zeros_like(y_fine) and you'll see both increasing in the x direction.

Hatch area using pcolormesh in Basemap

I try to hatch only the regions where I have statistically significant results. How can I do this using Basemap and pcolormesh?
plt.figure(figsize=(12,12))
lons = iris_cube.coord('longitude').points
lats = iris_cube.coord('latitude').points
m = Basemap(llcrnrlon=lons[0], llcrnrlat=lats[0], urcrnrlon=lons[-1], urcrnrlat=lats[-1], resolution='l')
lon, lat = np.meshgrid(lons, lats)
plt.subplot(111)
cs = m.pcolormesh(lon, lat, significant_data, cmap=cmap, norm=norm, hatch='/')
It seems pcolormesh does not support hatching (see https://github.com/matplotlib/matplotlib/issues/3058). Instead, the advice is to use pcolor, which starting from this example would look like,
import matplotlib.pyplot as plt
import numpy as np
dx, dy = 0.15, 0.05
y, x = np.mgrid[slice(-3, 3 + dy, dy),
slice(-3, 3 + dx, dx)]
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
z = z[:-1, :-1]
zm = np.ma.masked_less(z, 0.3)
cm = plt.pcolormesh(x, y, z)
plt.pcolor(x, y, zm, hatch='/', alpha=0.)
plt.colorbar(cm)
plt.show()
where a mask array is used to get the values of z greater than 0.3 and these are hatched using pcolor.
To avoid plotting another colour over the top (so you get only hatching) I've set alpha to 0. in pcolor which feels a bit like a hack. The alternative is to use patch and assign to the areas you want. See this example Python: Leave Numpy NaN values from matplotlib heatmap and its legend. This may be more tricky for basemaps, etc than just choosing areas with pcolor.
I have a simple solution for this problem, using only pcolormesh and not pcolor: Plot the color mesh, then hatch the entire plot, and then plot the original mesh again, this time by masking statistically significant cells, so that the only hatching visible is those on significant cells. Alternatively, you can put a marker on every cell (looks good too), instead of hatching the entire figure.
(I use cartopy instead of basemap, but this shouldn't matter.)
Step 1: Plot your field (z) normally, using pcolormesh.
mesh = plt.pcolormesh(x,y,z)
where x/y can be lons/lats.
Step 2: Hatch the entire plot. For this, use fill_between:
hatch = plt.fill_between([xmin,xmax],y1,y2,hatch='///////',color="none",edgecolor='black')
Check details of fill_between to set xmin, xmax, y1 and y2. You simply define two horizontal lines beyond the bounds of your plot, and hatch the area in between. Use more, or less /s to set hatch density.
To adjust hatch thickness, use below lines:
import matplotlib as mpl
mpl.rcParams['hatch.linewidth'] = 0.3
As an alternative to hatching everything, you can plot all your x-y points (or, lon-lat couples) as markers. A simple solution is putting a dot (x also looks good).
hatch = plt.plot(x,y,'.',color='black',markersize=1.5)
One of the above will be the basis of your 'hatch'. This is how it should look after Step 2:
Step 3: On top of these two, plot your color mesh once again with pcolormesh, this time masking cells containing statistically significant values. This way, the markers on your 'insignificant' cells become invisible again, while significant markers stay visible.
Assuming you have an identically sized array containing the t statistic for each cell (t_z), you can mask significant values using numpy's ma module.
z_masked = numpy.ma.masked_where(t_z >= your_threshold, z)
Then, plot the color mesh, using the masked array.
mesh_masked = plt.pcolormesh(x,y,z_masked)
Use zorder to make sure the layers are in correct order. This is how it should look after Step 3:

Drawing a cuboid to image

I'm in the process of moving a significant application to Python from PHP. All is well, except one particular issue that I can't seem to get to the bottom of.
The old application uses a PHP library Image_3D to draw some cuboids to an image (SVG, though that's not important) and display them on the web page.
I've migrated almost everything to Flask(w/SQLAlchemy) except fo the imaging. I've tried matplotlib, vpython and almost got desperate enough to reach for OpenGL bindings.
This is how PHP currently renders a few examples:
You can see that it remains readable enough even in extreme situations.
My best attempt at Python does fairly poorly:
(source: offblast.si)
(source: offblast.si)
The image is entirely unreadable. I didn't fill in the content, because, frankly, there's no point to it - you can't make anything out. Any attempt at fixing the view with ax.init_view was even worse, no matter the elevation or azimuth.
The code is just a bunch of points, rendered with
fig = plt.figure(frameon=False)
ax = fig.gca(projection='3d')
ax.set_axis_off()
ax.set_aspect('equal')
ax.plot_wireframe(points[:, 0], points[:, 1], points[:, 2], color="b")
plt.savefig(filename, bbox_inches='tight', pad_inches=0)
I'm sure I'm doing something wrong, but I can't figure out how to make readable renders work in Python / matplotlib or if there's a different library better suited for the task.
Update 1
It occurs to me I haven't described the problem correctly - it's a business-restricted subset of the knapsack problem. The container and cuboids within are thus arbitrarily positioned. The visualization module recieves dimensions and positions and needs to render them. The regularity of the previous examples is just a first-order optimal solution and is found often, but not in the majority of situations.
Some more examples to illustrate:
Update 2
A better solution with matplotlib, folowing the answer below. It doesn't seem easy to get MPL to rotate the entire plot as to show a useful overview.
ax.view_init should allow for changing the height, but it seems the FOV is too wide for such a narrow plot and all it does is transform the edges slightly.
(source: offblast.si)
You could use either VPython of matplotlib for this. In matplotlib I'd use a 3d box for your bars, and than another for the frame, and turn off the axes. Here's an examples:
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")
ax.autoscale(enable=False)
xpos = np.arange(5)
ypos = np.zeros(5)
zpos = np.zeros(5)
dx = np.ones(5)
dy = 5*np.ones(5)
dz = 5 + 5*np.random.random(5)
colors = ['r', 'c', 'g', 'y', 'm']
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, alpha=1)
ax.bar3d(0, 0, 0, 5, 5, 25, color=(1, 1, 1, 0))
ax.set_axis_off()
plt.show()

Creating intersecting images in matplotlib with imshow or other function

I have two 3-D arrays of ground penetrating radar data. Each array is basically a collection of time-lapse 2-D images, where time is increasing along the third dimension. I want to create a 3-D plot which intersects a 2-D image from each array.
I'm essentially trying to create a fence plot. Some examples of this type of plot are found on these sites:
http://www.geogiga.com/images/products/seismapper_3d_seismic_color.gif
http://www.usna.edu/Users/oceano/pguth/website/so461web/seismic_refl/fence.png
I typically use imshow to individually display the 2-D images for analysis. However, my research into the functionality of imshow suggests it doesn't work with the 3D axes. Is there some way around this? Or is there another plotting function which could replicate imshow functionality but can be combined with 3D axes?
There might be better ways, but at least you can always make a planar mesh and color it:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
# create a 21 x 21 vertex mesh
xx, yy = np.meshgrid(np.linspace(0,1,21), np.linspace(0,1,21))
# create some dummy data (20 x 20) for the image
data = np.random.random((20, 20))
# create vertices for a rotated mesh (3D rotation matrix)
X = np.sqrt(1./3) * xx + np.sqrt(1./3) * yy
Y = -np.sqrt(1./3) * xx + np.sqrt(1./3) * yy
Z = np.sqrt(1./3) * xx - np.sqrt(1./3) * yy
# create the figure
fig = plt.figure()
# show the reference image
ax1 = fig.add_subplot(121)
ax1.imshow(data, cmap=plt.cm.BrBG, interpolation='nearest', origin='lower', extent=[0,1,0,1])
# show the 3D rotated projection
ax2 = fig.add_subplot(122, projection='3d')
ax2.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=plt.cm.BrBG(data), shade=False)
This creates:
(Please note, I was not very careful with the rotation matrix, you will have to create your own projection. It might really be a good idea to use a real rotation matrix.)
Just note that there is a slight problem with the fence poles and fences, i.e. the grid has one more vertex compared to the number of patches.
The approach above is not very efficient if you have high-resolution images. It may not even be useful with them. Then the other possibility is to use a backend which supports affine image transforms. Unfortunately, you will then have to calculate the transforms yourself. It is not hideously difficult, but still a bit clumsy, and then you do not get a real 3D image which could be rotated around, etc.
For this approach, see http://matplotlib.org/examples/api/demo_affine_image.html
Alternateively, you can use OpenCV and its cv2.warpAffine function to warp your image before showing it with imshow. If you fill the surroundings with transparent color, you can then layer images to get a result which looks like your example iamge.
Just to give you an idea of the possibilities of plot_surface, I tried to warp Lena around a semi-cylinder:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
# create a 513 x 513 vertex mesh
xx, yy = np.meshgrid(np.linspace(0,1,513), np.linspace(0,1,513))
# create vertices for a rotated mesh (3D rotation matrix)
theta = np.pi*xx
X = np.cos(theta)
Y = np.sin(theta)
Z = yy
# create the figure
fig = plt.figure()
# show the 3D rotated projection
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=plt.imread('/tmp/lena.jpg')/255., shade=False)
She indeed bends well, but all operations on the image are quite slow:
If you're happy to contemplate using a different plotting library (ie not matplotlib) then it might be worth considering mayavi / tvtk (although the learning curve is a little steep). The closest I've seen to what you want is the scalar cut planes in
http://wiki.scipy.org/Cookbook/MayaVi/Examples
The bulk of the documentation is at:
http://docs.enthought.com/mayavi/mayavi/index.html
There is no way of doing this with matplotlib. #DrV's answer is an approximation. Matplotlib does not actually show each individual pixel of the original image but some rescaled image. rstride and cstride allow you to help specify how the image gets scaled, however, the output will not be the exact image.

matplotlib: filling under line in 3d polar plot

I'd like to plot a sine wave on a circle: that is, the circle is in the x,y-plane and the sine wave wraps around it perpendicular to that plane (sticking up the z-axis). I can do this, but when I try to fill the areas between the circle and the sine wave with a polygon (ie paint on the surface of the imaginary cylinder on which my sine wave lives), I can't get it quite right - matplotlib seems to XOR the regions that overlap in a view of the plot instead of giving me a view in which the ones in front occlude those behind.
Here's the relevant bit of my code:
fig = plt.figure()
ax = fig.gca(projection='3d')
ax._axis3don = False
theta = np.linspace(0., 2 * np.pi, 1000)
r = 1.
x = r * np.sin(theta)
y = r * np.cos(theta)
sinez = N * np.sin(theta * m)
ax.plot(x, y, sinez, color='r')
xv = np.append(x, x[::-1])
yv = np.append(y, y[::-1])
zv = np.append(sinez, np.zeros(n))
verts = [zip(xv,yv,zrev),]
poly = Poly3DCollection(verts, facecolors = [cc('r'), cc('b')],
edgecolor='None')
poly.set_alpha(0.7)
ax.add_collection3d(poly)
Here's what it looks like:
matplotlib's main reason for existence is 2D plotting, the 3D stuff is just some clever transforms and can be buggy/hacky. One of the inherent limitations is that matplotlib draws in layers, so it has no notion of 'in front' or 'behind', it only knows the order in which in draws the curves to the canvas (which is confusingly called z-order).
If you want to get this to look right with out re-writing the 3D code, split the sine wave up into pieces and make sure you set the z-order right by hand (see How to draw intersecting planes? for a simpler version of this), but you won't be able to rotate the image.
If you need real 3D, I would suggest looking into mayavi from enthought which is OpenGL based.
In the docs, the devs claim that Poly3DCollection
does a bit of magic with the _facecolors and _edgecolors properties.
which I believe is the XOR effect that you can see here, and looking at the code it's the function do_3d_projection that seems to be doing the magic.
As I see it, you could either subclass Poly3DCollection and rewrite do_3d_projection to get what you want, or maybe think of another way to plot this (perhaps treating the sinusoid and circle as separate objects somehow).

Categories

Resources