可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a program with rapid animations which works perfectly under pygame, and for technical reasons, I need to do the same using only matplotlib or an other widespread module.
The program structure is roughly:
pygame.init()
SURF = pygame.display.set_mode((500, 500))
arr = pygame.surfarray.pixels2d(SURF) # a view for numpy, as a 2D array
while ok:
# modify some pixels of arr
pygame.display.flip()
pygame.quit()
I have no low level matplotlib experience, but I think it is possible to do equivalent things with matplotlib. In other words :
How to share the bitmap of a figure, modify some pixels and refresh the screen ?
Here is a minimal working exemple, which flips 250 frames per second (more than the screen ...) on my computer :
import pygame,numpy,time
pygame.init()
size=(400,400)
SURF = pygame.display.set_mode(size)
arr = pygame.surfarray.pixels2d(SURF) # buffer pour numpy
t0=time.clock()
for counter in range(1000):
arr[:]=numpy.random.randint(0,0xfffff,size)
pygame.display.flip()
pygame.quit()
print(counter/(time.clock()-t0))
EDIT
What I try with indications in answers :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
def f(x, y):
return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)
im = plt.imshow(f(x, y), animated=True)
count=0
t0=time.clock()+1
def updatefig(*args):
global x, y,count,t0
x += np.pi / 15.
y += np.pi / 20.
im.set_array(f(x, y))
if time.clock()<t0:
count+=1
else:
print (count)
count=0
t0=time.clock()+1
return im,
ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
plt.show()
But this only provides 20 fps....
回答1:
It should be noted that the human brain is capable of "seeing" up to a framerate of ~25 fps. Faster updates are not actually resolved.
Matplotlib
With matplotlib and its animation
module the example from the question runs with 84 fps on my computer.
import time
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
def f(x, y):
return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)
im = ax.imshow(f(x, y), animated=True)
text = ax.text(200,200, "")
class FPS():
def __init__(self, avg=10):
self.fps = np.empty(avg)
self.t0 = time.clock()
def tick(self):
t = time.clock()
self.fps[1:] = self.fps[:-1]
self.fps[0] = 1./(t-self.t0)
self.t0 = t
return self.fps.mean()
fps = FPS(100)
def updatefig(i):
global x, y
x += np.pi / 15.
y += np.pi / 20.
im.set_array(f(x, y))
tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= fps.tick() )
text.set_text(tx)
return im, text,
ani = animation.FuncAnimation(fig, updatefig, interval=1, blit=True)
plt.show()
PyQtGraph
In pyqtgraph a higher framerate is obtained, it would run with 295 fps on my computer.
import sys
import time
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pyqtgraph as pg
class FPS():
def __init__(self, avg=10):
self.fps = np.empty(avg)
self.t0 = time.clock()
def tick(self):
t = time.clock()
self.fps[1:] = self.fps[:-1]
self.fps[0] = 1./(t-self.t0)
self.t0 = t
return self.fps.mean()
fps = FPS(100)
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)
#### Set Data #####################
self.x = np.linspace(0, 2 * np.pi, 400)
self.y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)
#### Start #####################
self._update()
def f(self, x, y):
return np.sin(x) + np.cos(y)
def _update(self):
self.x += np.pi / 15.
self.y += np.pi / 20.
self.img.setImage(self.f(self.x, self.y))
tx = 'Mean Frame Rate:\n {fps:.3f}FPS'.format(fps= fps.tick() )
self.label.setText(tx)
QtCore.QTimer.singleShot(1, self._update)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
回答2:
If you want to animate a plot, then you can take a look at the animation functionality in matplotlib under matplotlib.animation.Animation
. Here's a great tutorial - https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial.
If you just want to periodically update an adhoc bitmap, I am not sure matplotlib is meant for what you are trying to achieve. From matplotlib docs:
Matplotlib is a Python 2D plotting library which produces publication quality figures in a variety of hardcopy formats and interactive environments across platforms.
If you would like to periodically update an adhoc image on the screen, you may want to look into GUI libraries for python. Here is a short summary of available options - https://docs.python.org/3/faq/gui.html. Tkinter is a pretty standard one and is shipped with python. You can use the ImageTk
module in pillow
to create/modify images for displaying via Tkinter - http://pillow.readthedocs.io/en/4.2.x/reference/ImageTk.html.
回答3:
If you just need to animate a matplotlib
canvas the animation framework is the answer. There's a simple example here that does basically what you ask.
If this is going to be part of a more complex application you probably want finer control over a specific backend.
Here's a quick attempt using Qt
loosely based on this matplotlib example.
It's using a QTimer
for the updates, probably there's also some idle callback in Qt
you could attach to.
import sys
import numpy as np
import matplotlib as mpl
mpl.use('qt5agg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5 import QtWidgets, QtCore
size = (400, 400)
class GameCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.gca()
self.init_figure()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_figure)
timer.start(10)
def gen_frame(self):
return np.random.randint(0,0xfffff,size)
def init_figure(self):
self.img = self.axes.imshow(self.gen_frame())
def update_figure(self):
self.img.set_data(self.gen_frame())
self.draw()
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.main_widget = QtWidgets.QWidget(self)
dc = GameCanvas(self.main_widget, width=5, height=4, dpi=100)
self.setCentralWidget(dc)
def fileQuit(self):
self.close()
def closeEvent(self, ce):
self.fileQuit()
app = QtWidgets.QApplication(sys.argv)
appw = ApplicationWindow()
appw.show()
sys.exit(app.exec_())
One thing you should be careful with is that imshow
computes the image normalization on the first frame. In the subsequent frames it's calling set_data
so the normalization stays the same. If you want to update it you can call imshow
instead (probably slower). Or you could just fix it manually with vmin
and vmax
in the first imshow
call and provide properly normalized frames.
回答4:
Given you talked about using widespread modules, here's a proof of concept using OpenCV
. It runs pretty fast here, up to 250-300 generated frames per second. It's nothing too fancy, just to show that maybe if you're not using any plotting feature matplotlib
shouldn't really be your first choice.
import sys
import time
import numpy as np
import cv2
if sys.version_info >= (3, 3):
timer = time.perf_counter
else:
timer = time.time
def f(x, y):
return np.sin(x) + np.cos(y)
# ESC, q or Q to quit
quitkeys = 27, 81, 113
# delay between frames
delay = 1
# framerate debug init
counter = 0
overflow = 1
start = timer()
x = np.linspace(0, 2 * np.pi, 400)
y = np.linspace(0, 2 * np.pi, 400).reshape(-1, 1)
while True:
x += np.pi / 15.
y += np.pi / 20.
cv2.imshow("animation", f(x, y))
if cv2.waitKey(delay) & 0xFF in quitkeys:
cv2.destroyAllWindows()
break
counter += 1
elapsed = timer() - start
if elapsed > overflow:
print("FPS: {:.01f}".format(counter / elapsed))
counter = 0
start = timer()