Struggles with matplotlib geoprojections - python

This is my first question here so I'd appreciate if you go easy on me.
I'm a total newbie in python and in programming in general to be honest. My main passion is astrophotography and my working horse is a very old program "Iris" written by Cristian Buil.
Recently I took spherical panorama and I wanted to represent it in Hammer-Aitoff projection. Iris is able to do that but it struggles with very large images. Therefore I thought that I could try to use a Python for this task.
During my research of this question I found that Matplotlib library seems to be designed for this. In particular Basemap tool directly offers the features necessary such as projection selection, image warping e.t.c.
I've tried to cope with the manuals but the longer I've played with the examples the more my frustration grew. It seems that only half of the examples work for me! Although I've replicated al the steps and installed the libraries necessary.
For example if I try to replicate these two examples from here, the first one returns an empty circle and the second is working properly:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib import image
import matplotlib.image as mpimg
import numpy as np
#empty circle
plt.figure(figsize=(8, 8))
m = Basemap(projection='ortho', resolution=None, lat_0=50, lon_0=-100)
m.bluemarble(scale=0.5);
#proper map
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None,
width=8E6, height=8E6,
lat_0=45, lon_0=-100,)
m.etopo(scale=0.5, alpha=0.5)
#it works even if I change "m.etopo(scale=0.5, alpha=0.5)" by "m.bluemarble(scale=0.5);"
#so it seems that the problem in the projection and not something else
# Map (long, lat) to (x, y) for plotting
x, y = m(-122.3, 47.6)
plt.plot(x, y, 'ok', markersize=5)
plt.text(x, y, ' Seattle', fontsize=12);
with the image warping I also have the same inconsistent behavior. Lambert projection works fine but the hammer or mollweide projections return empty ellipses.
#works just fine
m = Basemap(width=20000000,height=10000000,projection='lcc', resolution=None,lat_1=-55.,lat_2=-55,lat_0=-0,lon_0=30.)
m.warpimage(image="DJI_0114.png")
plt.show()
#empty shells in the output
m = Basemap(projection='hammer', resolution=None, lat_0=0,lon_0=0.)
m = Basemap(projection='mall', resolution=None, lat_0=0,lon_0=0.)
#There are no width and height fields here since python shows this message:
#"warning: width and height keywords ignored for Hammer projection"
m.warpimage(image="DJI_0114.png")
Manuals say that the image has to cover the whole sky/ground i.e. be in equirectangular projection with the aspect ratio of 2:1. So it is.
Is it something wrong with me/my_code or is there some mistake in the library?.. I bet that I'm doing something wrong but I can't see what. So any help would be very welcomed!

Related

Graphical errors plotting 3d polygons in python

i have a problem plotting polygons using matplotlib in 3D. Under some circumstances there always seems to occur some kind of graphical error where polygons are shown that are covered by other polygons. This results in some really weird locking plots. But i can't figure out, where i make an error in the code. Maybe yome of you have had the problem befor and already a solution for it. My example code is as follows:
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt
import numpy as np
z = np.zeros((10,10))
z[5,5] = 5
z[4,5] = 2
z[4,4] = 2.8
nx,ny = (10,10)
xv = np.linspace(0,9,nx)
yv = np.linspace(0,9,ny)
x,y = np.meshgrid(xv,yv)
y = np.flipud(y)
fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlim3d(np.min(np.array(x)),np.max(np.array(x)))
ax.set_ylim3d(np.min(np.array(y)),np.max(np.array(y)))
ax.set_zlim3d(np.min(np.array(z)),np.max(np.array(z)))
ax.view_init(elev=45,azim=0)
ax.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
ax.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
ax.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
ax.xaxis._axinfo["grid"]['color'] = (1,1,1,0)
ax.yaxis._axinfo["grid"]['color'] = (1,1,1,0)
ax.zaxis._axinfo["grid"]['color'] = (1,1,1,0)
ax.set_axis_off()
for d1 in range(ny-1):
for d2 in range(nx-1):
xp = [x[d1,d2],x[d1+1,d2],x[d1,d2+1]]
yp = [y[d1,d2],y[d1+1,d2],y[d1,d2+1]]
zp = [z[d1,d2],z[d1+1,d2],z[d1,d2+1]]
verts = [list(zip(xp,yp,zp))]
ax.add_collection3d(Poly3DCollection(verts,facecolor='mediumblue',
linewidths=1,edgecolor='black'))
xp = [x[d1+1,d2],x[d1+1,d2+1],x[d1,d2+1]]
yp = [y[d1+1,d2],y[d1+1,d2+1],y[d1,d2+1]]
zp = [z[d1+1,d2],z[d1+1,d2+1],z[d1,d2+1]]
verts = [list(zip(xp,yp,zp))]
tri = ax.add_collection3d(Poly3DCollection(verts,facecolor='goldenrod',
linewidths=1,edgecolor='black'))
plt.savefig('out.png')
A figure that shows the problem can be seen here, have a look at the 6th column from the left, close to the middle of the plot. These error seems to be realted to the angle, in ohter azimut angles this error does not occur. But it is not a solution to change the azimuth angle because the such errors can occur in other position. Does someone know what i have done wrong and how to do it right?
If you rotate, you'll see that inconsistent depth rendering is the issue:
This is unfortunately a known issue that is even addressed in the Matplotlib FAQ:
My 3D plot doesn’t look right at certain viewing angles This is
probably the most commonly reported issue with mplot3d. The problem is
that – from some viewing angles – a 3D object would appear in front of
another object, even though it is physically behind it. This can
result in plots that do not look “physically correct.”
Unfortunately, while some work is being done to reduce the occurrence
of this artifact, it is currently an intractable problem, and can not
be fully solved until matplotlib supports 3D graphics rendering at its
core.
If you read on, their official recommendation is to use Mayavi for the time being. It's probably best to follow this recommendation if you require a larger amount of flexibility. Otherwise, you will probably have to stick to certain angles that work.

Zoom an inline 3D matplotlib figure *without* using the mouse?

This question explains how to change the "camera position" of a 3D plot in matplotlib by specifying the elevation and azimuth angles. ax.view_init(elev=10,azim=20), for example.
Is there a similar way to specify the zoom of the figure numerically -- i.e. without using the mouse?
The only relevant question I could find is this one, but the accepted answer to that involves installing another library, which then also requires using the mouse to zoom.
EDIT:
Just to be clear, I'm not talking about changing the figure size (using fig.set_size_inches() or similar). The figure size is fine; the problem is that the plotted stuff only takes up a small part of the figure:
The closest solution to view_init is setting ax.dist directly. According to the docs for get_proj "dist is the distance of the eye viewing point from the object point". The initial value is currently hardcoded with dist = 10. Lower values (above 0!) will result in a zoomed in plot.
Note: This behavior is not really documented and may change. Changing the limits of the axes to plot only the relevant parts is probably a better solution in most cases. You could use ax.autoscale(tight=True) to do this conveniently.
Working IPython/Jupyter example:
%matplotlib inline
from IPython.display import display
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Grab some test data.
X, Y, Z = axes3d.get_test_data(0.05)
# Plot a basic wireframe.
ax.view_init(90, 0)
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
plt.close()
from ipywidgets import interact
#interact(dist=(1, 20, 1))
def update(dist=10):
ax.dist = dist
display(fig)
Output
dist = 10
dist = 5

Matplotlib plot_surface transparency artefact

I'm trying to plot a surface in 3D from a set of data which specifies the z-values. I get some weird transparency artefact though, where I can see through the surface, even though I set alpha=1.0.
The artefact is present both when plotting and when saved to file (both as png and pdf):
I have tried changing the line width, and changing the number of strides from 1 to 10 (in the latter case, the surface is not visible though due to too rough resolution).
Q: How can I get rid of this transparency?
Here is my code:
import sys
import numpy as np
import numpy.ma as ma
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
y_label = r'x'
x_label = r'y'
z_label = r'z'
x_scale = 2.0*np.pi
y_scale = 2.0*np.pi
y_numPoints = 250
x_numPoints = 250
def quasiCrystal(x, y):
z = 0
for i in range(0,5):
z += np.sin(x * np.cos(float(i)*np.pi/5.0) +
y * np.sin(float(i)*np.pi/5.0))
return z
x = np.linspace(-x_scale, x_scale, x_numPoints)
y = np.linspace(-y_scale, y_scale, y_numPoints)
X,Y = np.meshgrid(x,y)
Z = quasiCrystal(X, Y)
f = plt.figure()
ax = f.gca(projection='3d')
surf = ax.plot_surface( X, Y, Z,
rstride=5, cstride=5,
cmap='seismic',
alpha=1,
linewidth=0,
antialiased=True,
vmin=np.min(Z),
vmax=np.max(Z)
)
ax.set_zlim3d(np.min(Z), np.max(Z))
f.colorbar(surf, label=z_label)
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
ax.set_zlabel(z_label)
plt.show()
Here is another picture of my actual data where it is easier to see the artefact:
Matplotlib is not a "real" 3D engine. This is a very well known problem and once in a while a similar question to yours appears appears (see this and this). The problem is that the same artefact can originate problems that seem to be different. I believe such is the case for you.
Before going on with my recommendations let me just quote this information from the maplotlib website:
My 3D plot doesn’t look right at certain viewing angles
This is probably the most commonly reported issue with mplot3d. The problem is
that – from some viewing angles – a 3D object would appear in front of
another object, even though it is physically behind it. This can
result in plots that do not look “physically correct.”
Unfortunately, while some work is being done to reduce the occurance
of this artifact, it is currently an intractable problem, and can not
be fully solved until matplotlib supports 3D graphics rendering at its
core.
The problem occurs due to the reduction of 3D data down to 2D +
z-order scalar. A single value represents the 3rd dimension for all
parts of 3D objects in a collection. Therefore, when the bounding
boxes of two collections intersect, it becomes possible for this
artifact to occur. Furthermore, the intersection of two 3D objects
(such as polygons or patches) can not be rendered properly in
matplotlib’s 2D rendering engine.
This problem will likely not be solved until OpenGL support is added
to all of the backends (patches are greatly welcomed). Until then, if
you need complex 3D scenes, we recommend using MayaVi.
It seems that Mayavi has finally moved on to Python 3, so its certainly a possibility. If you want to stick with matplotlib for this kind of plot my advice is that you work with rstride and cstride values to see which ones produce a plot satisfactory to you.
surf = ax.plot_surface( X, Y, Z,
rstride=5, cstride=5,
cmap='jet',
alpha=1,
linewidth=0,
antialiased=True,
vmin=0,
rstride=10,
cstride=10,
vmax=z_scale
)
Other possibility is to try to see if other kinds of 3D plots do better. Check plot_trisurf, contour or contourf. I know its not ideal but in the past I also managed to circumvent other type of artefacts using 3D polygons.
Sorry for not having a more satisfactory answer. Perhaps other SO users have better solutions for this. Best of luck.
I ran into some similar issues and found that they were antialiasing artifacts and could be fixed by setting antialiased=False in plot_surface.

Python Matplotlib 3D reduce scale of z axis

I try to render the classic rice.png image for an image processing class but I cannot get the result I want.
import matplotlib.pyplot as plt
import numpy as np
from scipy import misc
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
# rice.png is the image one can find on the internet
rice = misc.imread('rice.png')
height, width = rice.shape
fig = plt.figure(2)
ax = fig.gca(projection='3d')
X = np.arange(0, height, 1)
Y = np.arange(0, width, 1)
X, Y = np.meshgrid(X, Y)
surf = ax.plot_surface(X, Y, rice, cmap=cm.coolwarm, linewidth=0)
plt.title('3D representation of image')
plt.show()
But that gives me this :
I tried using set_zticks but the ticks overflow as in the image above, I tried also the solutions one can see here but it overflows also and/or give poor result.
My goal is to have something like what can be seen in the paragraph how can I do so ?
[edit] I have already seen this question that gives a less complete answer that what can be found in the other link I gave earlier (idea of overriding the proj fonction).
However, I am not happy with the results. First because it means I have to change a functions in the matplotlib library (if I follow the SO solution) and as I will share my code with other students from my class, I do not want to do so. Then because it does not give me the same result (see later) it does not center the image, it just change the scale then cut the above part.
[edit2] update of the code

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