How to refresh images of axes of Matplotlib figure - python

I want to draw a 3D volume using Matplotlib, slice by slice.
Mouse scroll to change the index. My program is given below:
#Mouse scroll event.
def mouse_scroll(event):
fig = event.canvas.figure
ax = fig.axes[0]
if event.button == 'down':
next_slice(ax)
fig.canvas.draw()
#Next slice func.
def previous_slice(ax):
volume = ax.volume
ax.index = (ax.index - 1) % volume.shape[0]
#ax.imshow(volume[ax.index])
ax.images[0].set_array(volume[ax.index])
Figure is initialized in the main function. like:
fig, ax = plt.subplots()
ax.volume = volume # volume is a 3D data, a 3d np array.
ax.index = 1
ax.imshow(volume[ax.index])
fig.canvas.mpl_connect('scroll_event', mouse_scroll)
Everything worked pretty well even I don't understand what is the ax.images. However, problem occurred when I replace the ax.volume with a new volume data. It suddenly stop to render! Debug into the code, the ax.image[0] is correctly set at each event callback.
But, if change the image set_array method to ax.show(). Figure begins to render again. But axes imshow function is really slow comparing to the ax.images[0].set_array() method.
How can I fix this problem? really want to use set_array() method. Thank you very much.
A simple executable script is attached.
plot.py#googledrive

You need to work on the same image all the time. Best give this a name
img = ax.imshow(volume[ax.index])
You can then set the data for it using set_data.
import numpy as np
import matplotlib.pyplot as plt
#Mouse scroll event.
def mouse_scroll(event):
fig = event.canvas.figure
ax = fig.axes[0]
if event.button == 'down':
next_slice(ax)
fig.canvas.draw()
#Next slice func.
def next_slice(ax):
volume = ax.volume
ax.index = (ax.index - 1) % volume.shape[0]
img.set_array(volume[ax.index])
def mouse_click(event):
fig = event.canvas.figure
ax = fig.axes[0]
volume = np.random.rand(10, 10, 10)
ax.volume = volume
ax.index = (ax.index - 1) % volume.shape[0]
img.set_array(volume[ax.index])
fig.canvas.draw_idle()
if __name__ == '__main__':
fig, ax = plt.subplots()
volume = np.random.rand(40, 40, 40)
ax.volume = volume # volume is a 3D data, a 3d np array.
ax.index = 1
img = ax.imshow(volume[ax.index])
fig.canvas.mpl_connect('scroll_event', mouse_scroll)
fig.canvas.mpl_connect('button_press_event', mouse_click)
plt.show()

Related

How to draw animation by taking snapshot with matplotlib?

In my project, I have many polygons to draw for each time step.
At each step, the number of polygons varies, thus it is difficult to keep Axes.patchs and translate them to make the animation.
I want to create animation with final figures (show after calling matplotlib.pyplot.show()), how to do this?
We take the sin curve as example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ims = []
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
z = np.cos(x)
for i in range(1,100):
tmpx = x[:i]
tmpy = y[:i]
tmpz = z[:i]
plt.plot(tmpx, tmpz)
im = plt.plot(tmpx, tmpy)
ims.append(im)
ani = animation.ArtistAnimation(fig, ims, interval=200)
ani.save('/home/test.gif', writer='imagemagick')
plt.show()
There are two curves: animated-sin-curve and static-cos-curve.
the sin-curve is kept as Line2D objects for each step
the cos-curve stay static for each step.
In this way, we show different Artist object for each step.
But I want to keep the rasterized Line2D figure for each step.
I find classes of AxesImage/FigureImage, but I don't know how to save the rasterized figure and make them work.
I tried to convert figure.canvas to AxesImage with following code :
def fig2AxesImage(fig):
import PIL.Image as Image
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
buf = numpy.fromstring(fig.canvas.tostring_argb(), dtype=numpy.uint8)
buf.shape = (w, h, 4)
# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = numpy.roll(buf, 3, axis=2)
image = Image.frombytes("RGBA", (w, h), buf.tostring())
image = numpy.asarray(image)
return plt.imshow(image, animated=True)
but with this way, I have to clear canvas at start of next frame, which make the final animation a blank video. (but the .jpg figures I output for each step get the right content)
Does anyone have done this before that save rasterized canvas-figures of matplotlib.pyplot.figure() as a animation Vedio?
celluloid for python 2.7
''' copy from celluloid'''
# from typing import Dict, List # not supported by python 2.7. So comment it
from collections import defaultdict
from matplotlib.figure import Figure
from matplotlib.artist import Artist
from matplotlib.animation import ArtistAnimation
__version__ = '0.2.0'
class Camera:
def __init__(self, figure):
self.figure_ = figure
self.offsets_ = { k:defaultdict(int) \
for k in ['collections', 'patches', 'lines', 'texts', 'artists', 'images']
}
self.photos_ = []
def snap(self):
frame_artists = []
for i, axis in enumerate(self.figure_.axes):
if axis.legend_ is not None:
axis.add_artist(axis.legend_)
for name in self.offsets_:
new_artists = getattr(axis, name)[self.offsets_[name][i]:]
frame_artists += new_artists
self.offsets_[name][i] += len(new_artists)
self.photos_.append(frame_artists)
def animate(self):
return ArtistAnimation(self.figure_, self.photos_)

Cannot save animated gif with transparent background (matplotlib.animation)

I am trying to save an animation with a completely transparent background. Setting:
fig1 = (...,facecolor=(1,1,1,0))
Does not seem to work. Also, just as a side note, if you do that and view the plot then you get these weird transparency effects and lagging animation. Curious why that happens too, but mostly I just want the background to save as transparent.
If I then try:
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"facecolor": (1,1,1,0)})
Then I get an output which does not have a transparent background and makes the lines thick. Same curiosity as above as why making the figure alpha to 0 would give this effect.
Another attempt:
fig1 = (...,facecolor=(1,1,1,0))
line_ani.save(...,savefig_kwargs={"transparent": None})
Also doesn't produce a transparent background.
If I just include the facecolor in the dictionary, then it gives the undesired line thickening bug.
line_ani.save(...,savefig_kwargs={"transparent": None,"facecolor":(1,1,1,0)})
The code is below.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
from matplotlib.pyplot import figure
def update_line(num, data, line):
line.set_data(data[..., :num])
return line,
def plots():
plt.xlim(-1, 1)
plt.ylim(-1, 1)
plt.xticks([])
plt.yticks([])
plt.box()
# Since I'm calling things twice, it's convenient to define these
fs = (3,3)
inter = 100
frames = 219
lw = 0.25
alph = 0
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth =lw)
# Generate placeholder for data and set initial conditions
DAT =np.zeros((2,300))
DAT[0][0] = 0
DAT[1][0] = 1
theta=2*np.pi*(1/np.e +0.01)
# 2D Rotation Matrix
def R(x):
return [[np.cos(x),-np.sin(x)],[np.sin(x),np.cos(x)]]
# Generate the data
for i in range(len(DAT[0])):
if i < len(DAT[0])-1:
DAT[0][i+1]=DAT[0][i]*R(theta)[0][0] + DAT[1][i]*R(theta)[0][1]
DAT[1][i+1]=DAT[0][i]*R(theta)[1][0] + DAT[1][i]*R(theta)[1][1]
# Animate the data
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
plt.show()
# Save the animation
matplotlib.use("Agg")
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth = lw)
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
print("Saving animation...")
now=time.time()
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": None})
later = time.time()
print("Saved in time: ", int(later-now),"seconds")
If you run the code it should show you the animation and then save it. It also will calculate the runtime.
Setting transparent = True does the trick...
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": True})

matplotlib ArtistAnimation shows only one frame

I am trying to make an animation with matplotlib.animation, using the ArtistAnimation function. I have a large matrix and I want to show a different part of this matrix in a colormap using plt.imshow() in each frame. However, only one frame is shown in the animation and I don't know why. I tried to mimic the code in An animated image using a list of images. I have looked at this question and this question, but I could not find a solution there.
This is my code:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
def make_animation(pwv_matrix, length_side):
pwv_shape = pwv_matrix.shape
fig = plt.figure()
num_frames = min(pwv_shape[0], pwv_shape[1])-length_side
y_min = int(np.round(pwv_shape[0]/2) - np.round(length_side/2))
y_max = int(np.round(pwv_shape[0]/2) + np.round(length_side/2))
x_min = 0
x_max = length_side
ims = []
for i in range(0, num_frames):
pwv_frame = pwv_matrix[x_min:x_max, y_min:y_max]
im = plt.imshow(pwv_frame, animated=True, cmap = 'viridis')
ims.append([im])
x_min += 1
x_max += 1
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
plt.show()
x = np.linspace(0, 1, 200)
y = np.linspace(0, 1, 200)
pwv_matrix, yv = np.meshgrid(x, y)
length_side = 20 #m
make_animation(pwv_matrix, length_side)
where self.pwv_matrix is a large matrix obtained from a .dat file. Does anyone see the problem?
Thanks very much in advance!
Try adding a very small delay after the show() function with
matplotlib.pyplot.pause(interval)[source]
function or:
from time import sleep
sleep(0.05)
The actual drawing of the image occurs one you give it some processing time (in a very simplified way of explaining it)
If it does not work there are some other things you might try, let me know if it doesn't work

Problem animating matplotlib pcolor plot with polar projection using object

I am trying to create a plotting object that produces an animated matplotlib pcolor plot with a polar projection. Currently the object can either create a set of polar plots or try to create an animation of those plots.
When creating the set of polar plots (but not the animation) the object works as planned.
The animation portion of the object is based on this example, which works on my system. Unfortunately the animation as implemented in my object is not working. There is a figure and an MP4 file produced for the animation but both the figure and the too-short animation both show just some mis-shaped axes.
Does anyone have a suggestion of how to capture this figure series in an animation when embedded in an object?
I am using python 3.7, matplotlib 3.03 on a windows 10 machine
The code for the object and the code to run its instantiation are given below.
class Polar_smudge(object):
# object for creating polar contour plots
def __init__(self, azimuth_grid, range_grid):
import numpy as np
self.azimuth_grid = np.deg2rad(azimuth_grid)
self.range_grid = range_grid
self.fig = None
self.ax = None
self.images = []
#------------------------------------------------------------------
def add_data(self, value_grid):
import numpy as np
self.value_grid = value_grid
self.value_grid[self.value_grid<=0] = np.nan
#------------------------------------------------------------------
def add_figure(self, value_grid):
import matplotlib.pyplot as plt
# make and set-up figure
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_rlim([0,10])
# make plot
cax = ax.pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r)
ax.grid()
plt.show()
#------------------------------------------------------------------
def start_figure(self):
import matplotlib.pyplot as plt
# make and set-up figure
if self.fig is None :
self.fig, self.ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
self.ax[0].set_theta_zero_location("N")
self.ax[0].set_theta_direction(-1)
def update_figure(self, value_grid):
import matplotlib.pyplot as plt
# make figure and add to image list
self.images.append((self.ax[0].pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r),))
def end_figure(self):
import matplotlib.animation as animation
# animate the figure list
im_ani = animation.ArtistAnimation(self.fig, self.images, interval=50, repeat_delay=3000,blit=True)
im_ani.save('smudge.mp4')
#============This runs the object ====================================
import numpy as np
azimuth_bins = np.linspace(0, 360, 360)
range_bins = np.linspace(0, 10, 30)
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
# this works but isnt what I want
good_smudge = Polar_smudge(azimuth_grid,range_grid)
for ix in range(3):
val_grid = np.random.randn(360,30)
good_smudge.add_figure(val_grid)
# this doesnt work
bad_smudge = Polar_smudge(azimuth_grid,range_grid)
bad_smudge.start_figure()
for ix in range(3):
val_grid = np.random.randn(360,30)
bad_smudge.update_figure(val_grid)
bad_smudge.end_figure()
In response to the comment from Earnest, I did some further refinement and it appears that the problem is not linked to being embedded in an object, and also that increasing the number of frames (to eg. 30) does not solve the problem. The code snippet below provides a more concise demonstration of the problem (but lacks the correctly produced figure output option).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
azimuth_bins = np.linspace(0, 360, 60)
range_bins = np.linspace(0, 10, 30)
images = []
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
fig,ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
ax[0].set_theta_zero_location("N")
ax[0].set_theta_direction(-1)
for ix in range(30):
val_grid = np.random.randn(60,30)
images.append((ax[0].pcolor(azimuth_grid, range_grid, val_grid, cmap=plt.cm.viridis_r),))
# animate the figure list
im_ani = animation.ArtistAnimation(fig, images, interval=50, repeat_delay=3000,blit=False)
im_ani.save('smudge2.mp4')

matplotlib display only one graph of a set of 10 like a slideshow

I have a set of 10 graphs. (based on X/Y-pairs) (In this example only 3)
Displaying one graph is easy, same to all graphs in the same window.(See picture)
But I haven't found a solution for what I want :
The 10 graphs are Data from a spectrum analyzer and are showing a signal.
I want to display the first graph, delete or remove it and display the 2nd graph in the same window.
Then next, the second graph will be removed and the 3rd shall be seen (and so on)
Thats my Code :
from matplotlib import pyplot as plt
import numpy as np
datei = ['./2450ATT0.csv','./2450ATT0-1.csv','./2450ATT0-2.csv']
for file in datei:
x = np.genfromtxt(file, usecols =(0), delimiter=';', unpack=True)
y = np.genfromtxt(file, usecols =(1), delimiter=';', unpack=True, dtype=float)
plt.xlim(2435,2465)
plt.ylim(-120,-20)
plt.xlabel('Frequenz')
plt.ylabel('Leistung')
plt.plot(x/1000000,y, label=file)
plt.show()
Do you have any idea ?
I also have had a look at plt.animate. but I haven't found a solution with that suggestions.
Thank you.
Andi
Showing the data one after the other seems a bit unergonomic to me. Also using an animation might not be the best solution. What if after inspection of the second dataset you want to go back to the first?
I would therefore implement a solution which allows to go back and forth between the spectra.
The following sandbox example is based on a solution I have provided to a similar problem with images. It uses a discrete Slider to walk through the pages. Although it may seem a bit complicated on first sight, you do not really have to understand the PageSlider class in order to use it. Just look at the code down in the __main__ part.
import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1
class PageSlider(matplotlib.widgets.Slider):
def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d',
closedmin=True, closedmax=True,
dragging=True, **kwargs):
self.facecolor=kwargs.get('facecolor',"w")
self.activecolor = kwargs.pop('activecolor',"b")
self.fontsize = kwargs.pop('fontsize', 10)
self.numpages = numpages
super(PageSlider, self).__init__(ax, label, 0, numpages,
valinit=valinit, valfmt=valfmt, **kwargs)
self.poly.set_visible(False)
self.vline.set_visible(False)
self.pageRects = []
for i in range(numpages):
facecolor = self.activecolor if i==valinit else self.facecolor
r = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1,
transform=ax.transAxes, facecolor=facecolor)
ax.add_artist(r)
self.pageRects.append(r)
ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),
ha="center", va="center", transform=ax.transAxes,
fontsize=self.fontsize)
self.valtext.set_visible(False)
divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
bax = divider.append_axes("right", size="5%", pad=0.05)
fax = divider.append_axes("right", size="5%", pad=0.05)
self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_back.label.set_fontsize(self.fontsize)
self.button_forward.label.set_fontsize(self.fontsize)
self.button_back.on_clicked(self.backward)
self.button_forward.on_clicked(self.forward)
def _update(self, event):
super(PageSlider, self)._update(event)
i = int(self.val)
if i >=self.valmax:
return
self._colorize(i)
def _colorize(self, i):
for j in range(self.numpages):
self.pageRects[j].set_facecolor(self.facecolor)
self.pageRects[i].set_facecolor(self.activecolor)
def forward(self, event):
current_i = int(self.val)
i = current_i+1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
def backward(self, event):
current_i = int(self.val)
i = current_i-1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
if __name__ == "__main__":
import numpy as np
from matplotlib import pyplot as plt
num_pages = 10
data = np.random.rand(700, num_pages)
spec = np.linspace(-10,10, 700)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
ax.set_ylim([0.,1.6])
line, = ax.plot(spec,data[:,0], color="b")
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")
def update(val):
i = int(slider.val)
line.set_ydata(data[:,i])
slider.on_changed(update)
plt.show()
The code above is working and shows how this would look like. In your specific case, you would need to change it a bit.
I tried to adapt your code accordingly, but of course I cannot guarantee that it works. This code has to be put below the __main__ part, the PageSlider must stay unchanged.
import numpy as np
from matplotlib import pyplot as plt
dateien = ['./2450ATT0.csv','./2450ATT0-1.csv','./2450ATT0-2.csv']
data_x = []
data_y = []
for datei in dateien: #do not call a variable "file" in python as this is protected
x = np.genfromtxt(datei, usecols =(0), delimiter=';', unpack=True)
x = x/1000000.
y = np.genfromtxt(datei, usecols =(1), delimiter=';', unpack=True, dtype=float)
data_x.append(x)
data_y.append(y)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
ax.set_xlim([2435,2465])
ax.set_xlim([-120,20])
ax.set_xlabel('Frequenz')
ax.set_ylabel('Leistung')
text = ax.text(0.98,0.98, dateien[0], ha="right", va="top")
line, = ax.plot(data_x[0],data_y[0], color="b")
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', len(dateien), activecolor="orange")
def update(val):
i = int(slider.val)
line.set_data(data_x[i],data_y[i])
text.set_text(dateien[i])
slider.on_changed(update)
plt.show()
Edit:
For a simple animation, you would rather use matplotlib.animation.FuncAnimation and the code would look something along those lines
import numpy as np
from matplotlib import pyplot as plt
dateien = ['./2450ATT0.csv','./2450ATT0-1.csv','./2450ATT0-2.csv']
data_x = []
data_y = []
for datei in dateien: # do not call a variable "file" in python, this is a protected word
x = np.genfromtxt(datei, usecols =(0), delimiter=';', unpack=True)
x = x/1000000.
y = np.genfromtxt(datei, usecols =(1), delimiter=';', unpack=True, dtype=float)
data_x.append(x)
data_y.append(y)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
ax.set_xlim([2435,2465])
ax.set_xlim([-120,20])
ax.set_xlabel('Frequenz')
ax.set_ylabel('Leistung')
line, = ax.plot(data_x[0],data_y[0], color="b")
def update(i):
line.set_data(data_x[i],data_y[i])
ani = matplotlib.animation.FuncAnimation(fig, update,
frames= len(dateien), interval = 200, blit = False, repeat= True)
plt.show()
I like to suggest using multiple subplots in a 2D matrix layout and animating them. Examples (w/o animation) can be seen from http://matplotlib.org/examples/pylab_examples/subplots_demo.html and https://www.dataquest.io/blog/images/python_r/python_pairs.png.
In this way your students get to see changes in all the data simultaneously. The implementation details for the subplots are given in the 1st example. Furas has directed you to the plot animation example.

Categories

Resources