Fast Live Plotting in Matplotlib / PyPlot - matplotlib

For years, I've been struggling to get efficient live plotting in matplotlib, and to this day I remain unsatisfied.
I want a redraw_figure function that updates the figure "live" (as the code runs), and will display the latest plots if I stop at a breakpoint.
Here is some demo code:
import time
from matplotlib import pyplot as plt
import numpy as np
def live_update_demo():
plt.subplot(2, 1, 1)
h1 = plt.imshow(np.random.randn(30, 30))
redraw_figure()
plt.subplot(2, 1, 2)
h2, = plt.plot(np.random.randn(50))
redraw_figure()
t_start = time.time()
for i in xrange(1000):
h1.set_data(np.random.randn(30, 30))
redraw_figure()
h2.set_ydata(np.random.randn(50))
redraw_figure()
print 'Mean Frame Rate: %.3gFPS' % ((i+1) / (time.time() - t_start))
def redraw_figure():
plt.draw()
plt.pause(0.00001)
live_update_demo()
Plots should update live when the code is run, and we should see the latest data when stopping at any breakpoint after redraw_figure(). The question is how to best implement redraw_figure()
In the implementation above (plt.draw(); plt.pause(0.00001)), it works, but is very slow (~3.7FPS)
I can implement it as:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
And it runs faster (~11FPS), but plots are not up-to date when you stop at breakpoints (eg if I put a breakpoint on the t_start = ... line, the second plot does not appear).
Strangely enough, what does actually work is calling the show twice:
def redraw_figure():
plt.gcf().canvas.flush_events()
plt.show(block=False)
plt.show(block=False)
Which gives ~11FPS and does keep plots up-to-data if your break on any line.
Now I've heard it said that the "block" keyword is deprecated. And calling the same function twice seems like a weird, probably-non-portable hack anyway.
So what can I put in this function that will plot at a reasonable frame rate, isn't a giant kludge, and preferably will work across backends and systems?
Some notes:
I'm on OSX, and using TkAgg backend, but solutions on any backend/system are welcome
Interactive mode "On" will not work, because it does not update live. It just updates when in the Python console when the interpreter waits for user input.
A blog suggested the implementation:
def redraw_figure():
fig = plt.gcf()
fig.canvas.draw()
fig.canvas.flush_events()
But at least on my system, that does not redraw the plots at all.
So, if anybody has an answer, you would directly make me and thousands of others very happy. Their happiness would probably trickle through to their friends and relatives, and their friends and relatives, and so on, so that you could potentially improve the lives of billions.
Conclusions
ImportanceOfBeingErnest shows how you can use blit for faster plotting, but it's not as simple as putting something different in the redraw_figure function (you need to keep track of what things to redraw).

First of all, the code that is posted in the question runs with 7 fps on my machine, with QT4Agg as backend.
Now, as has been suggested in many posts, like here or here, using blit might be an option. Although this article mentions that blit causes strong memory leakage, I could not observe that.
I have modified your code a bit and compared the frame rate with and without the use of blit. The code below gives
28 fps when run without blit
175 fps with blit
Code:
import time
from matplotlib import pyplot as plt
import numpy as np
def live_update_demo(blit = False):
x = np.linspace(0,50., num=100)
X,Y = np.meshgrid(x,x)
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)
img = ax1.imshow(X, vmin=-1, vmax=1, interpolation="None", cmap="RdBu")
line, = ax2.plot([], lw=3)
text = ax2.text(0.8,0.5, "")
ax2.set_xlim(x.min(), x.max())
ax2.set_ylim([-1.1, 1.1])
fig.canvas.draw() # note that the first draw comes before setting data
if blit:
# cache the background
axbackground = fig.canvas.copy_from_bbox(ax1.bbox)
ax2background = fig.canvas.copy_from_bbox(ax2.bbox)
plt.show(block=False)
t_start = time.time()
k=0.
for i in np.arange(1000):
img.set_data(np.sin(X/3.+k)*np.cos(Y/3.+k))
line.set_data(x, np.sin(x/3.+k))
tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= ((i+1) / (time.time() - t_start)) )
text.set_text(tx)
#print tx
k+=0.11
if blit:
# restore background
fig.canvas.restore_region(axbackground)
fig.canvas.restore_region(ax2background)
# redraw just the points
ax1.draw_artist(img)
ax2.draw_artist(line)
ax2.draw_artist(text)
# fill in the axes rectangle
fig.canvas.blit(ax1.bbox)
fig.canvas.blit(ax2.bbox)
# in this post http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
# it is mentionned that blit causes strong memory leakage.
# however, I did not observe that.
else:
# redraw everything
fig.canvas.draw()
fig.canvas.flush_events()
#alternatively you could use
#plt.pause(0.000000000001)
# however plt.pause calls canvas.draw(), as can be read here:
#http://bastibe.de/2013-05-30-speeding-up-matplotlib.html
live_update_demo(True) # 175 fps
#live_update_demo(False) # 28 fps
Update:
For faster plotting, one may consider using pyqtgraph.
As the pyqtgraph documentation puts it: "For plotting, pyqtgraph is not nearly as complete/mature as matplotlib, but runs much faster."
I ported the above example to pyqtgraph. And although it looks kind of ugly, it runs with 250 fps on my machine.
Summing that up,
matplotlib (without blitting): 28 fps
matplotlib (with blitting): 175 fps
pyqtgraph : 250 fps
pyqtgraph code:
import sys
import time
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#### Create Gui Elements ###########
self.mainbox = QtGui.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtGui.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.label = QtGui.QLabel()
self.mainbox.layout().addWidget(self.label)
self.view = self.canvas.addViewBox()
self.view.setAspectLocked(True)
self.view.setRange(QtCore.QRectF(0,0, 100, 100))
# image plot
self.img = pg.ImageItem(border='w')
self.view.addItem(self.img)
self.canvas.nextRow()
# line plot
self.otherplot = self.canvas.addPlot()
self.h2 = self.otherplot.plot(pen='y')
#### Set Data #####################
self.x = np.linspace(0,50., num=100)
self.X,self.Y = np.meshgrid(self.x,self.x)
self.counter = 0
self.fps = 0.
self.lastupdate = time.time()
#### Start #####################
self._update()
def _update(self):
self.data = np.sin(self.X/3.+self.counter/9.)*np.cos(self.Y/3.+self.counter/9.)
self.ydata = np.sin(self.x/3.+ self.counter/9.)
self.img.setImage(self.data)
self.h2.setData(self.ydata)
now = time.time()
dt = (now-self.lastupdate)
if dt <= 0:
dt = 0.000000000001
fps2 = 1.0 / dt
self.lastupdate = now
self.fps = self.fps * 0.9 + fps2 * 0.1
tx = 'Mean Frame Rate: {fps:.3f} FPS'.format(fps=self.fps )
self.label.setText(tx)
QtCore.QTimer.singleShot(1, self._update)
self.counter += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())

Here's one way to do live plotting: get the plot as an image array then draw the image to a multithreaded screen.
Example using a pyformulas screen (~30 FPS):
import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time
fig = plt.figure()
screen = pf.screen(title='Plot')
start = time.time()
for i in range(10000):
t = time.time() - start
x = np.linspace(t-3, t, 100)
y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
plt.xlim(t-3,t)
plt.ylim(-3,3)
plt.plot(x, y, c='black')
# If we haven't already shown or saved the plot, then we need to draw the figure first...
fig.canvas.draw()
image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))
screen.update(image)
#screen.close()
Disclaimer: I'm the maintainer of pyformulas

Related

How much faster is PyQtGraph than Matplotlib?

I am trying to implement a GUI on a Raspberry Pi which plots real time data coming in from a TCP client with the functionality of panning and zooming as well. I have heard all the rage about PyQtGraph being faster than Matplotlib and would like to reimplement the Gui I'd previously made using Matplotlib and Gtk using PyQtGraph and PyQt5. I have benchmarked the code but would like clarification on the results seeing as I can blit the Matplotlib plot to dramatically reduce the plot time. Below is the code I used for benchmarking:
# PyQtGraph
import pyqtgraph as pg
import numpy as np
app = pg.mkQApp()
x = []
y = []
for i in range(1000000):
x.append(i)
y.append(1)
view = pg.GraphicsLayoutWidget()
view.show()
w1 = view.addPlot()
w1.disableAutoRange()
start = pg.ptime.time()
w1.plot(x, y)
w1.autoRange() # only after plots are added
end = pg.ptime.time()
print("Plot time: %0.2f sec" % (end-start))
app.exec_()
and
# Matplotlib
import matplotlib.pyplot as plt
import time
x = []
y = []
for i in range(1000000):
x.append(i)
y.append(1)
fig, ax = plt.subplots(nrows=1)
(line,) = ax.plot([], [], "b")
plt.show(block=False)
start = time.time()
line.set_data(x, y)
ax.relim()
ax.autoscale_view(True,True,True)
fig.canvas.draw()
fig.canvas.flush_events()
end = time.time()
print(end-start)
# In order to see if it has plotted
time.sleep(10)
For 1 000 000 points, I got 2.54 s to plot using PyQtGraph and 6.97 s to plot using Matplotlib on my Raspberry Pi. Is this the expected speed up or is there something else I'm not doing? Thank you.

Why isn't my histogram showing on Jupyter notebook?

So I'm relatively new to coding and have recently taken the monstrous task of building a few climate models for my MSc thesis. Using this code I have adapted it and it now shows no error messages except now it doesn't show any figure as an output. Any solutions?
I input
%matplotlib notebook at the top of the code, and also put plt.show(); at the bottom of the script (as per some recommendations through some similar queries)... but still doesn't work. Prior to this it was showing <Figure Ssize 432x288 with 0 Axes> which i presumed may be the problem but i can't figure out why there are 0 axes?
Any recommendations/solutions?
Thanks!
As requested - my code:
import iris.quickplot as qplt
import iris.analysis.cartography
import matplotlib.dates as mdates
def main():
Current45 = '....X.nc'
Current45 = iris.load_cube(Current45)
lats = iris.coords.DimCoord(Current45.coords()[1].points[:,0], \
standard_name='latitude', units='degrees')
lons = Current45.coords()[2].points[0]
for i in range(len(lons)):
if lons[i]>100.:
lons[i] = lons[i]-360.
lons = iris.coords.DimCoord(lons, \
standard_name='longitude', units='degrees')
Current45.remove_coord('latitude')
Current45.remove_coord('longitude')
Current45.add_dim_coord(lats, 1)
Current45.add_dim_coord(lons, 2)
Current45.convert_units('Celsius')
Colombia = iris.Constraint(longitude=lambda v: -74.73 <= v <= -76.20, \
latitude=lambda v: 5.30 <= v <= 4.43)
Current45 = Current45.extract(Colombia)
iriscc.add_day_of_year(Current45, 'time')
Current45.coord('latitude').guess_bounds()
Current45.coord('longitude').guess_bounds()
Current45_grid_areas = iris.analysis.cartography.area_weights(Current45)
Current45 = Current45.collapsed(['latitude', 'longitude'],
iris.analysis.MEAN,
weights=Current45_grid_areas)
Histogram = Current45.data
#frq, bins, patches = plt.hist(Histogram, bins=np.arange(20,37,2))
frq, bins, patches = plt.hist(Histogram, bins=np.arange(16,45,2), color='blue')
print (frq)
thresh = 32
plt.axvline(x=thresh, color='green', linestyle='dashed', linewidth=2)
plt.xlabel("Daily Max Temperature / Celsius")
plt.ylabel("Number of days")
fig = plt.gcf()
plt.show();
My code with blank figure at the bottom
In the code, you are never calling the main function, so the figure you are showing is empty.
You should call main() at some point in your code before the plt.gcf() or plt.show.
Edit
In more detail:
You are writing your main() function in this snippet of code, and then, without indent, you are calling pyplot to get the current figure, where pyplot just gives you en empty figure back (the gcf()-call is not necessary anyways in your code) and plt.show() shows no an empty figure.
You can or cannot move the plt.show() into you main() function, but at one point you must definitely call that function otherwise none of it is executed.
Edit 2:
# function definition
def main():
...
# function call
main()
# show figure
plt.show()

matplotlib update figure in loop [duplicate]

I'm having issues with redrawing the figure here. I allow the user to specify the units in the time scale (x-axis) and then I recalculate and call this function plots(). I want the plot to simply update, not append another plot to the figure.
def plots():
global vlgaBuffSorted
cntr()
result = collections.defaultdict(list)
for d in vlgaBuffSorted:
result[d['event']].append(d)
result_list = result.values()
f = Figure()
graph1 = f.add_subplot(211)
graph2 = f.add_subplot(212,sharex=graph1)
for item in result_list:
tL = []
vgsL = []
vdsL = []
isubL = []
for dict in item:
tL.append(dict['time'])
vgsL.append(dict['vgs'])
vdsL.append(dict['vds'])
isubL.append(dict['isub'])
graph1.plot(tL,vdsL,'bo',label='a')
graph1.plot(tL,vgsL,'rp',label='b')
graph2.plot(tL,isubL,'b-',label='c')
plotCanvas = FigureCanvasTkAgg(f, pltFrame)
toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
toolbar.pack(side=BOTTOM)
plotCanvas.get_tk_widget().pack(side=TOP)
You essentially have two options:
Do exactly what you're currently doing, but call graph1.clear() and graph2.clear() before replotting the data. This is the slowest, but most simplest and most robust option.
Instead of replotting, you can just update the data of the plot objects. You'll need to make some changes in your code, but this should be much, much faster than replotting things every time. However, the shape of the data that you're plotting can't change, and if the range of your data is changing, you'll need to manually reset the x and y axis limits.
To give an example of the second option:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
fig.canvas.draw()
fig.canvas.flush_events()
You can also do like the following:
This will draw a 10x1 random matrix data on the plot for 50 cycles of the for loop.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
for i in range(50):
y = np.random.random([10,1])
plt.plot(y)
plt.draw()
plt.pause(0.0001)
plt.clf()
This worked for me. Repeatedly calls a function updating the graph every time.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
def plot_cont(fun, xmax):
y = []
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
def update(i):
yi = fun()
y.append(yi)
x = range(len(y))
ax.clear()
ax.plot(x, y)
print i, ': ', yi
a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
plt.show()
"fun" is a function that returns an integer.
FuncAnimation will repeatedly call "update", it will do that "xmax" times.
This worked for me:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
clear_output(wait=True)
y = np.random.random([10,1])
plt.plot(y)
plt.show()
I have released a package called python-drawnow that provides functionality to let a figure update, typically called within a for loop, similar to Matlab's drawnow.
An example usage:
from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
# can be arbitrarily complex; just to draw a figure
#figure() # don't call!
plot(t, x)
#show() # don't call!
N = 1e3
figure() # call here instead!
ion() # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
x = sin(2 * pi * i**2 * t / 100.0)
drawnow(draw_fig)
This package works with any matplotlib figure and provides options to wait after each figure update or drop into the debugger.
In case anyone comes across this article looking for what I was looking for, I found examples at
How to visualize scalar 2D data with Matplotlib?
and
http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop
(on web.archive.org)
then modified them to use imshow with an input stack of frames, instead of generating and using contours on the fly.
Starting with a 3D array of images of shape (nBins, nBins, nBins), called frames.
def animate_frames(frames):
nBins = frames.shape[0]
frame = frames[0]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
for k in range(nBins):
frame = frames[k]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
del tempCS1
fig.canvas.draw()
#time.sleep(1e-2) #unnecessary, but useful
fig.clf()
fig = plt.figure()
ax = fig.add_subplot(111)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)
I also found a much simpler way to go about this whole process, albeit less robust:
fig = plt.figure()
for k in range(nBins):
plt.clf()
plt.imshow(frames[k],cmap=plt.cm.gray)
fig.canvas.draw()
time.sleep(1e-6) #unnecessary, but useful
Note that both of these only seem to work with ipython --pylab=tk, a.k.a.backend = TkAgg
Thank you for the help with everything.
All of the above might be true, however for me "online-updating" of figures only works with some backends, specifically wx. You just might try to change to this, e.g. by starting ipython/pylab by ipython --pylab=wx! Good luck!
Based on the other answers, I wrapped the figure's update in a python decorator to separate the plot's update mechanism from the actual plot. This way, it is much easier to update any plot.
def plotlive(func):
plt.ion()
#functools.wraps(func)
def new_func(*args, **kwargs):
# Clear all axes in the current figure.
axes = plt.gcf().get_axes()
for axis in axes:
axis.cla()
# Call func to plot something
result = func(*args, **kwargs)
# Draw the plot
plt.draw()
plt.pause(0.01)
return result
return new_func
Usage example
And then you can use it like any other decorator.
#plotlive
def plot_something_live(ax, x, y):
ax.plot(x, y)
ax.set_ylim([0, 100])
The only constraint is that you have to create the figure before the loop:
fig, ax = plt.subplots()
for i in range(100):
x = np.arange(100)
y = np.full([100], fill_value=i)
plot_something_live(ax, x, y)

Legend not working for live data and while loop configuration

My code takes a continuously updating input from raspberry pi, which is then plotted onto a graph. I'm trying to use the legend to display the current frequency (most recent output of y_data) however I can't seem to get it to display. Placing plt.legend() just before plt.show() results in a display, however freezing of the graph. Any help would be greatly appreciated.
import matplotlib
matplotlib.use('qt5agg')
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import RPi.GPIO as GPIO
import time
import numpy as np
x_data = []
y_data = []
GPIO.setmode(GPIO.BCM)
INPUT_PIN = 26
GPIO.setup(INPUT_PIN, GPIO.IN)
fig, ax = plt.subplots()
line, = plt.plot([],[], 'k-',label = 'data', drawstyle = 'steps')
avr, = plt.plot([],[], 'g--',label = 'mean')
plt.show(block = False)
def update(x_data, y_data, average):
line.set_ydata(y_data)
line.set_xdata(x_data)
avr.set_xdata(x_data)
avr.set_ydata([average]*len(x_data))
fig.canvas.draw()
ax.draw_artist(ax.patch)
ax.draw_artist(line)
ax.draw_artist(avr)
ax.relim()
ax.autoscale_view()
data = round(y_data[-1], 1)
ax.legend((line, avr), (data, 'mean'))
fig.canvas.update()
fig.canvas.flush_events()
while True: #Begin continuous loop
NUM_CYCLES = 10 #Loops to be averaged over
start = time.time()
for impulse_count in range(NUM_CYCLES):
GPIO.wait_for_edge(INPUT_PIN, GPIO.FALLING)
duration = time.time() - start #seconds to run for loop
frequency = NUM_CYCLES / duration #Frequency in Hz
bpm = (frequency/1000)*60 #Frequency / no. of cogs per breath * min
x_data.append(time.time()) #add new data to data lists
y_data.append(bpm)
average = sum(y_data)/float(len(y_data))
update(x_data,y_data, average) #call function to update graph contents
I think you should call fig.canvas.draw() at the end of the update function, not in the middle of it. I'm not sure why you add all the artists again in the update function, so you may leave that out. Concerning the legend, It's probably best to create it once at the beginning and inside the update function only update the relevant text.
Commenting out all the GPIO stuff, this is a version which works fine for me:
import matplotlib
#matplotlib.use('qt5agg')
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
#import RPi.GPIO as GPIO
import time
import numpy as np
x_data = []
y_data = []
#GPIO.setmode(GPIO.BCM)
#INPUT_PIN = 26
#GPIO.setup(INPUT_PIN, GPIO.IN)
fig, ax = plt.subplots()
line, = plt.plot([],[], 'k-',label = 'data', drawstyle = 'steps')
avr, = plt.plot([],[], 'g--',label = 'mean')
# add legend already at the beginning
legend = ax.legend((line, avr), (0.0, 'mean'))
plt.show(block = False)
def update(x_data, y_data, average):
line.set_ydata(y_data)
line.set_xdata(x_data)
avr.set_xdata(x_data)
avr.set_ydata([average]*len(x_data))
#fig.canvas.draw() <- use this at the end
#ax.draw_artist(ax.patch) # useless?
#ax.draw_artist(line) # useless?
#ax.draw_artist(avr) # useless?
ax.relim()
ax.autoscale_view()
data = round(y_data[-1], 1)
# only update legend here
legend.get_texts()[0].set_text(str(data))
#fig.canvas.update() # <- what is this one needed for?
fig.canvas.draw()
fig.canvas.flush_events()
while True: #Begin continuous loop
NUM_CYCLES = 10 #Loops to be averaged over
start = time.time()
#for impulse_count in range(NUM_CYCLES):
# GPIO.wait_for_edge(INPUT_PIN, GPIO.FALLING)
a = np.random.rand(700,800) # <- just something that takes a little time
duration = time.time() - start #seconds to run for loop
frequency = NUM_CYCLES / duration #Frequency in Hz
bpm = (frequency/1000)*60 #Frequency / no. of cogs per breath * min
x_data.append(time.time()) #add new data to data lists
y_data.append(bpm)
average = sum(y_data)/float(len(y_data))
update(x_data,y_data, average) #call function to update graph contents
Add plt.draw() (or fig.canvas.draw_idle() for a more OO approach) at the end of update.

matplotlib.pyplot Event Handling: Linear Increase in Key-Press Dwell Time?

I am writing an image processing module in Python using matplotlib.pyplot and numpy backend.
The images will largely be in tiff format, so the code below uses tifffile to convert a 3D image file to a 4D array in numpy. The below code aims to move through the z-plane of the 3D image, one image at a time, using z and x as hotkeys. My problem is quite interesting and I can't figure it out: the time between event and action (pressing x and displaying z+1 image) gets twice as long with each event.
I timed it, results below:
1st z-press: 0.124 s
2nd z-prss: 0.250 s
3rd z-press: 0.4875 s
It is a bonafide linear increase, but I can't find where in my code the bug could be.
import matplotlib.pyplot as plt
import numpy as np
import tifffile as tiff
class Image:
def __init__ (self, fname):
self.fname = fname
self.fig = plt.figure()
self.z = 0
self.ax = self.fig.add_subplot(111)
self.npimg = tiff.imread(self.fname)
self.plotimg()
self.connect()
def plotimg(self):
plt.imshow(self.npimg[self.z][0])
plt.show()
def connect(self):
self.cidkeypress = self.fig.canvas.mpl_connect('key_press_event',self.keypress)
def disconnect(self):
self.fig.canvas.mpl_disconnect(self.cidkeypress)
def keypress(self, event):
if event.key == 'x':
self.z += 1
self.plotimg()
elif event.key == 'z':
self.z -= 1
self.plotimg()
Since you didn't provide an example file, I can't test your code. But I think the problem is, that you call plt.imshow() repeatedly. Each time a new AxesImage object is added to the Figure. This is why the timings increase linearly.
So the solution is to only have one AxesImage object and only update the data. Adapt __init__ and plotimg as follows:
def __init__ (self, fname):
self.fname = fname
self.fig = plt.figure()
self.z = 0
self.ax = self.fig.add_subplot(111)
self.npimg = tiff.imread(self.fname)
self.pltim = plt.imshow(self.npimg[self.z][0])
self.connect()
plt.show()
def plotimg(self):
self.pltim.set_data(self.npimg[self.z][0])
plt.draw()
Beware: Untested code!
EDIT:
Difference between plt.imshow and plt.show:
Despite their similar name, they do very different things: plt.imshow is a plotting function, which draws an image to the current axes (similar to plt.plot, plt.scatter, etc.) plt.show however, dispays the figure on the screen and enters the backends mainloop (i.e. blocks your code). This answer explains it in more detail