Plotting patches of random shapes with matplotlib - python

This is my target to plot:
Several ellipses which are not regular shape.
(source: clouddn.com)
I was thinking about generating some random number as vertices location.
But it can only build a polygon. So, how to plot several arcs and make them close up?

To create any arbitrary shape, you will need to use the matplotlib.patches.Polygon class and just provide enough x,y samples to make it appear as smooth of a path as necessary (at the end of the day it's still straight line segments when you zoom in close enough).
If you only have a few points, you can use one of many interpolation methods (such as scipy.interpolate.spline) to create a smooth interpolant of the data that you can then feed to the Polygon constructor.
Here is a simple example creating a circle using the Polygon class by supplying x,y points around the circle.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
# Circle coordinates (100 points around the circle)
t = np.linspace(0, 2 * np.pi, 100).reshape(100,1)
coords = np.concatenate((np.cos(t), np.sin(t)), axis=1)
ax = plt.axes()
polygons = [];
polygons.append(Polygon(coords))
p = PatchCollection(polygons, alpha=0.4)
ax.add_collection(p)
ax.axis('equal')

Sonds just like the example in the official documentation:
http://matplotlib.org/examples/pylab_examples/ellipse_demo.html
the main part, they just construct a list of the ellipses:
ells = [Ellipse(xy=rnd.rand(2)*10, width=rnd.rand(), height=rnd.rand(), angle=rnd.rand()*360) for i in range(250)]
...or did I miss your point? :)

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()

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.

Best fitting rectangular mesh to a smooth 3D surface

G'day, I'm struggling to find a way to create a rectangular mesh that best fits a smooth 3D surface. Particularly I have a model of an earthquake fault shown in this plot.
These are the depth contours to the fault. I want to find a rectangular mesh of defined dimension (say 10x10km) that best fits the surface. It doesn't have to (and it can't) be exactly on the surface, just the closest possible and it HAS to be a rectangle, not just a quadrangle. I have the nodes that define the surface and I can easily interpolate them.
Python solutions are welcome or suggestions on open-source code that my tackle this. I've tried commercial meshers (ABAQUS) but they always return quadrangles. I haven't been able to figure this out so any hints are appreciated.
If you have the nodes that define the surface, that means you have an irregular grid of coordinates and corresponding values. So you can generate a triangulation from this (most likely the tool you're using to show these filled contours uses the same behind the screens).
Matplotlib has two very useful classes that can convert a triangulation to a rectilinear grid (the more generic form of a rectangular grid): LinearTriInterpolator and CubicTriInterpolator. They are being used in this matplotlib example.
These are the basic steps from that same example, annotated by me, but credit goes to the matplotlib contributors:
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
import numpy as np
# Create triangulation.
coords, earthquake_fault = get_coordinate_data() # to be filled in by you
x = coords['x']
y = coords['y']
triang = mtri.Triangulation(x, y)
# Interpolate to regularly-spaced quad grid.
z = earthquake_fault # the "height" data
xi, yi = np.meshgrid(np.linspace(x.min(), x.max() 20), np.linspace(y.min(), y.max(), 20))
interp_lin = mtri.LinearTriInterpolator(triang, z)
zi_lin = interp_lin(xi, yi)
# Plot the triangulation.
plt.subplot(121)
plt.tricontourf(triang, z)
plt.triplot(triang, 'ko-')
plt.title('Triangular grid')
# Plot linear interpolation to quad grid.
plt.subplot(122)
plt.contourf(xi, yi, zi_lin)
plt.title('Rectangular grid')

Matplotlib tripcolor bug?

I want to use tripcolor from matplotlib.pyplot to view the colored contours of some of my data.
The data is extracted from an XY plane at z=cst using Paraview. I directly export the data in csv from Paraview which triangulates the plane for me.
The problem is that depending on the plane position (ie the mesh) tripcolor gives me sometimes good or bad results.
Here is a simple example code and results to illustrate it:
Code
import matplotlib.pyplot as plt
import numpy as np
p,u,v,w,x,y,z = np.loadtxt('./bad.csv',delimiter=',',skiprows=1,usecols=(0,1,2,3,4,5,6),unpack=True)
NbLevels = 256
plt.figure()
plt.gca().set_aspect('equal')
plt.tripcolor(x,y,w,NbLevels,cmap=plt.cm.hot_r,edgecolor='black')
cbar = plt.colorbar()
cbar.set_label('Velocity magnitude',labelpad=10)
plt.show()
Results with tripcolor
Here is the file that causes the problem.
I've heard that matplotlib's tripcolor is sometimes buggy, so is it a bug or not ?
As highlighted by #Hooked this is the normal behaviour for a Delaunay triangulation.
To remove unwanted triangles you should provide your own Triangulation by passing explicitly the triangles.
This is quite easy in your case as your data is almost structured: I suggest performing a Delaunay triangulation in the plane (r, theta) then passing these triangles to the initial (x, y) arrays. You can make use of the the built-in TriAnalyzer class to remove very flat triangles from the (r, theta) triangulation (they might exists due to round-off errors).
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.tri as mtri
p,u,v,w,x,y,z = np.loadtxt('./bad.csv',delimiter=',',skiprows=1,usecols=(0,1,2,3,4,5,6),unpack=True)
r = np.sqrt(y**2 + x**2)
tan = (y / x)
aux_tri = mtri.Triangulation(r/np.max(r), tan/np.max(tan))
triang = mtri.Triangulation(x, y, aux_tri.triangles)
triang.set_mask(mtri.TriAnalyzer(aux_tri).get_flat_tri_mask())
NbLevels = 256
plt.figure()
plt.gca().set_aspect('equal')
plt.tripcolor(triang, w, NbLevels, cmap=plt.cm.jet, edgecolor='black')
cbar = plt.colorbar()
cbar.set_label('Velocity magnitude',labelpad=10)
plt.show()
It's probably because the Delaunay triangulation called by Paraview created a convex hull of the points (as it should). To test this, I used matplotlib.tri.Triangulation and plotted the resulting mesh from the x-y values:
import matplotlib.tri as tri
plt.scatter(x,y)
w[:] = 1
triang = tri.Triangulation(x, y)
plt.tripcolor(triang,w,alpha=.2)
which shows the same effect. It may be possible to remove the unwanted triangles from the mesh, either by hand, or using a non-convex boundary finder.

Categories