Matplotlib video zooming and quality - matplotlib

I already figured out how to save a video animation and zooming a picture with matplotlib.
I want now to merge the two things and understand how to introduce zoom in an animation: reading some documentation I noticed it's not straightforward for just a picture, I expect to be the same or worse for a video.
In the following I write a simple working code, relating to that you can find on the first link
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from matplotlib import cm
#Define x,y vectors and meshgrid with function u on it
x = np.arange(0,10,.1)
y = np.arange(0,10,.1)
X,Y = np.meshgrid(x,y)
#Create a figure and an axis object for the surface
fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
#Animation without axes and with colormap
def animate(n):
ax.cla()
u = np.sin(X+Y+(n/10))
plt.axis('off')
plt.grid('off')
ax.plot_surface(X,Y,u,cmap=cm.inferno)
return fig,
anim = animation.FuncAnimation(fig,animate,frames=63)
anim.save('A.mp4',fps=20)
Here the output
As you can see is not bad zoomed, but it's not enough, I want it more!
In the actual code I'm using, video animations are very very small, and I don't know why because it's very similar to this. I hope also that this way I can increase video quality, that is quite poor.
Thanks for any help.

I finally got to a raw, but effective solution. The code almost doesn't change
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from matplotlib import cm
#Define x,y vectors and meshgrid with function u on it
x = np.arange(0,10,.1)
y = np.arange(0,10,.1)
X,Y = np.meshgrid(x,y)
#Create a figure and an axis object for the surface
fig = plt.figure(frameon=False)
ax = fig.add_subplot(111,projection='3d')
#Definition? and zooming
fig.set_size_inches(10,10)
fig.subplots_adjust(left=0,right=1,bottom=0,top=1,wspace=None,hspace=None)
#Animation without axes and with colormap
def animate(n):
ax.cla()
u = np.sin(X+Y+(n/10))
plt.axis('off')
plt.grid('off')
ax.plot_surface(X,Y,u,cmap=cm.inferno)
print(n)
return fig,
anim = animation.FuncAnimation(fig,animate,frames=63)
anim.save('A.gif',fps=20)
As you can see the zooming is good.
The bad quality is due to compression I did in a second moment, because the actual output gif is quite heavy with those parameters, in fact the quality is very good.

Related

How to draw graphics dynamically on jupyterlab notebook

I found an example that can run normally on my laptop, but there is a problem. When the drawing is finished, a repeated result graph will be drawn again. I want to know how to not display the last repeated image.
import numpy as np
import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
# set up matplotlib
is_ipython = 'inline' in matplotlib.get_backend()
if is_ipython:
from IPython import display
plt.ion()
def plot_durations(y):
plt.figure(2)
plt.clf()
plt.subplot(211)
plt.plot(y[:,0])
plt.subplot(212)
plt.plot(y[:,1])
if is_ipython:
display.clear_output(wait=True)
display.display(plt.gcf())
x = np.linspace(-10,10,10)
y = []
for i in range(len(x)):
y1 = np.cos(i/(3*3.14))
y2 = np.sin(i/(3*3.14))
y.append(np.array([y1,y2]))
plot_durations(np.array(y))
plt.ioff()
plt.show()
Replacing plt.show() with plt.close() at the end of your code will prevent jupyter notebook from displaying the final plot twice. An explanation is included here.

how to draw non-uniform colorbar in matplotlib

Tried the midnorm and listed colormap, but do not know how to turn the front 0-20 into uniform multi-color (the picture shows the abbreviation, please do not mind), do not know how to draw the effect of the picture! ! Help
In this case it makes sense to use a BoundaryNorm and a ListedColormap. Define the N+1 bounaries for the N colors of the colormap and use spacing="proportional" in the colorbar call.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import BoundaryNorm, ListedColormap
cmap = ListedColormap(["black", "darkred", "crimson", "salmon", "navy", "violet", "yellow"])
bounds = [0,1,2,3,4,10,20,30]
norm = BoundaryNorm(bounds, cmap.N)
fig, ax = plt.subplots()
sc = ax.scatter(*np.random.rand(2,100), c=np.random.rand(100)*30, cmap=cmap, norm=norm)
fig.colorbar(sc, orientation="horizontal", spacing="proportional")
plt.show()
I think you should use
MyCmap=colors.LinearSegmentedColormap.from_list("", list(zip(lvals,cvals)))
as in this solution (even if it is for a logarithmic scale there). Look at the end of the accepted answer.

Plotting a rasterio raster on a Cartopy GeoAxes

I've seen a few other questions on this topic, but the library has changed enough that the answers to those no longer seem to apply.
Rasterio used to include an example for plotting a rasterio raster on a Cartopy GeoAxes. The example went roughly like this:
import matplotlib.pyplot as plt
import rasterio
from rasterio import plot
import cartopy
import cartopy.crs as ccrs
world = rasterio.open(r"../tests/data/world.rgb.tif")
fig = plt.figure(figsize=(20, 12))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
ax.set_global()
plot.show(world, origin='upper', transform=ccrs.PlateCarree(), interpolation=None, ax=ax)
ax.coastlines()
ax.add_feature(cartopy.feature.BORDERS)
However, this code no longer draws the raster. Instead, I get something like this:
It should look like this:
When I asked about this in the rasterio issues tracker, they told me the example was deprecated (and deleted the example). Still, I wonder if there's some way to do what I'm trying to do. Can anyone point me in the right direction?
I think you may want to read the data to a numpy.ndarray and plot it using ax.imshow, where ax is your cartopy.GeoAxes (as you have it already). I offer an example of what I mean, below.
I clipped a small chunk of Landsat surface temperature and some agricultural fields for this example. Get them on this drive link.
Note fields are in WGS 84 (epsg 4326), Landsat image is in UTM Zone 12 (epsg 32612), and I want my map in Lambert Conformal Conic. Cartopy makes this easy.
import numpy as np
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
from cartopy.feature import ShapelyFeature
import rasterio
import matplotlib.pyplot as plt
def cartopy_example(raster, shapefile):
with rasterio.open(raster, 'r') as src:
raster_crs = src.crs
left, bottom, right, top = src.bounds
landsat = src.read()[0, :, :]
landsat = np.ma.masked_where(landsat <= 0,
landsat,
copy=True)
landsat = (landsat - np.min(landsat)) / (np.max(landsat) - np.min(landsat))
proj = ccrs.LambertConformal(central_latitude=40,
central_longitude=-110)
fig = plt.figure(figsize=(20, 16))
ax = plt.axes(projection=proj)
ax.set_extent([-110.8, -110.4, 45.3, 45.6], crs=ccrs.PlateCarree())
shape_feature = ShapelyFeature(Reader(shapefile).geometries(),
ccrs.PlateCarree(), edgecolor='blue')
ax.add_feature(shape_feature, facecolor='none')
ax.imshow(landsat, transform=ccrs.UTM(raster_crs['zone']),
cmap='inferno',
extent=(left, right, bottom, top))
plt.savefig('surface_temp.png')
feature_source = 'fields.shp'
raster_source = 'surface_temperature_32612.tif'
cartopy_example(raster_source, feature_source)
The trick with Cartopy is to remember to use the projection keyword for your axes object, as this renders the map in a nice projection of your choice (LCC in my case). Use transform keyword to indicate what projection system your data is in, so Cartopy knows how to render it.
No need of rasterio. Get a bluemarble image, then plot it.
Here is the working code:
import cartopy
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure(figsize=(10, 5))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
# source of the image:
# https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73909/world.topo.bathy.200412.3x5400x2700.jpg
fname = "./world.topo.bathy.200412.3x5400x2700.jpg"
img_origin = 'lower'
img = plt.imread(fname)
img = img[::-1]
ax.imshow(img, origin=img_origin, transform=ccrs.PlateCarree(), extent=[-180, 180, -90, 90])
ax.coastlines()
ax.add_feature(cartopy.feature.BORDERS)
ax.set_global()
plt.show()
The output plot:

Matplotlib colorbar and WCS projection

I'm trying to write a function to display astronomical images with a colorbar on the top (automaticly with the same length of the x-axis).
I'm having problem because when I try to put the tick on the top it doesn't do anything...it keeps the tick on the bottom of the colorbar (and also the tick on the y-axis of the colobar).
I think that could be a problem with the WCS coordinate of the x-axis, because when i try to do it without the projection it work well!
import numpy as np
import matplotlib.pyplot as plt
from astropy import wcs
from matplotlib.colors import PowerNorm
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib import cm
#WCS coordinate system
w = wcs.WCS(naxis=2)
w.wcs.crpix = [23.5, 23.5]
w.wcs.cdelt = np.array([-0.0035, 0.0035])
w.wcs.crval = [266.8451, -28.151658]
w.wcs.ctype = ["RA---TAN", "DEC--TAN"]
w.wcs.set_pv([(2, 1, 45.0)])
#generate an array as image test
data = (np.arange(10000).reshape((100,100)))
#display image
fig = plt.figure()
ax = plt.gca(projection=w)
graf = ax.imshow(data, origin='lower', cmap=cm.viridis, norm=PowerNorm(1))
#colorbar
divider = make_axes_locatable(ax)
cax = divider.append_axes("top", size="5%")
cbar = fig.colorbar(graf, cax=cax, orientation='horizontal')
cax.xaxis.set_ticks_position('top')
fig.show()
Thanks!
You can fix this issue using matplotlib's axes class.
...
import matplotlib.axes as maxes
cax = divider.append_axes("top", size="5%", axes_class=maxes.Axes)
...
You need to use the internal machinery of the WCSAxes to handle the ticks in the WCS projection. It looks like WCSAxes handles the colorbar ticks through a coordinate map container (you can find it in cbar.ax.coords) instead of the xaxis/yaxis attributes (that don't seem to be used much).
So, after running your code, the following trick worked for me and the xticks moved up:
c_x = cbar.ax.coords['x']
c_x.set_ticklabel_position('t')
cbar.update_normal(cax)
To get something like this to work, I needed a few additional parameters:
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cax.coords[0].grid(False)
cax.coords[1].grid(False)
cax.tick_params(direction='in')
cax.coords[0].set_ticks(alpha=0, color='w', size=0, values=[]*u.dimensionless_unscaled)
cax.coords[1].set_ticklabel_position('r')
cax.coords[1].set_axislabel_position('r')
because the default axis gad the grid on, the labels to the left, and x-axis labels enabled. I'm not sure why the original post didn't have issues with this.

Matplotlib animation not working in IPython Notebook (blank plot)

I've tried multiple animation sample codes and cannot get any of them working. Here's a basic one I've tried from the Matplotlib documentation:
"""
A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01) # x-array
line, = ax.plot(x, np.sin(x))
def animate(i):
line.set_ydata(np.sin(x+i/10.0)) # update the data
return line,
#Init only required for blitting to give a clean slate.
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
interval=25, blit=True)
plt.show()
When I execute the above in an IPython Notebook, I just see a blank plot generated. I've tried running this from multiple servers (including Wakari), on multiple machines, using multiple browsers (Chrome, FF, IE).
I can save the animation to an mp4 file just fine and it looks good when played.
Any help is appreciated!
To summarize the options you have:
Using display in a loop Use IPython.display.display(fig) to display a figure in the output. Using a loop you would want to clear the output before a new figure is shown. Note that this technique gives in general not so smooth resluts. I would hence advice to use any of the below.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from IPython.display import display, clear_output
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
for i in range(len(x)):
animate(i)
clear_output(wait=True)
display(fig)
plt.show()
%matplotlib notebook Use IPython magic %matplotlib notebook to set the backend to the notebook backend. This will keep the figure alive instead of displaying a static png file and can hence also show animations.
Complete example:
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
plt.show()
%matplotlib tk Use IPython magic %matplotlib tk to set the backend to the tk backend. This will open the figure in a new plotting window, which is interactive and can thus also show animations.
Complete example:
%matplotlib tk
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
plt.show()
Convert animation to mp4 video:
from IPython.display import HTML
HTML(ani.to_html5_video())
or use plt.rcParams["animation.html"] = "html5" at the beginning of the notebook.
This will require to have ffmpeg video codecs available to convert to HTML5 video. The video is then shown inline. This is therefore compatible with %matplotlib inline backend. Complete example:
%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "html5"
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
ani
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
from IPython.display import HTML
HTML(ani.to_html5_video())
Convert animation to JavaScript:
from IPython.display import HTML
HTML(ani.to_jshtml())
or use plt.rcParams["animation.html"] = "jshtml" at the beginning of the notebook.
This will display the animation as HTML with JavaScript. This highly compatible with most new browsers and also with the %matplotlib inline backend. It is available in matplotlib 2.1 or higher.
Complete example:
%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "jshtml"
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
ani
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])
animate = lambda i: l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
from IPython.display import HTML
HTML(ani.to_jshtml())
According to this answer, you can get animation (and full interactivity support) working in an IPython notebook enabling the nbagg backend with %matplotlib nbagg.
I was having the exact same problem as you until a moment ago. I am a complete novice, so tcaswell's answer was a bit cryptic to me. Perhaps you figured out what he meant or found your own solution. In case you have not, I will put this here.
I googled "matplotlib inline figures" and found this site, which mentions that you have to enable matplotlib mode. Unfortunately, just using %maplotlib didn't help at all.
Then I typed %matplotlib qt into the IPython console on a lark and it works just fine now, although the plot appears in a separate window.
I ran into this issue as well and found I needed to understand the concept of matplotlib backends, how to enable a specific backend, and which backends work with FuncAnimation. I put together an ipython notebook that explains the details and summarizes which backends work with FuncAnimation on Mac, Windows, and wakari.io. The notebook also summarizes which backends work with the ipython interact() widget, and where plots appear (inline or secondary window) for basic matplotlib plotting. Code and instructions are included so you can reproduce any of the results.
The bottom line is that you can't get an animation created with FuncAnimation to display inline in an ipython notebook. However, you can get it to display in a separate window. It turns out that I needed this to create visualizations for an undergraduate class I am teaching this semester, and while I would much prefer the animations to be inline, at least I was able to create some useful visualizations to show during class.
No inline video in Jupyter at the end of an animation also happens when
HTML(ani.to_html5_video())
is not at the very end of a notebook cell, as the output is then suppressed.
You may use it then as follows
out = HTML(ani.to_html5_video())
and just type out` in a new cell to get the video online.