Matplotlib эквивалент пигментного флип
У меня есть программа с быстрой анимацией, которая отлично работает под pygame, и по техническим причинам мне нужно сделать то же самое, используя только matplotlib или другой широко распространенный модуль.
Структура программы примерно:
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()
У меня нет опыта работы с matplotlib на низком уровне, но я думаю, что с matplotlib можно делать эквивалентные вещи. Другими словами:
Как поделиться растровым изображением фигуры, изменить некоторые пиксели и обновить экран?
Вот минимальный рабочий пример, который переворачивает 250 кадров в секунду (больше, чем экран...) на моем компьютере:
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))
РЕДАКТИРОВАТЬ
Что я пытаюсь с указанием в ответах:
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()
Но это только обеспечивает 20 кадров в секунду....
4 ответа
Следует отметить, что человеческий мозг способен "видеть" до частоты кадров ~25 кадров в секунду. Более быстрые обновления на самом деле не решаются.
Matplotlib
С матплотлибом и его animation
Модуль Пример из вопроса работает на моем компьютере с 84 кадров в секунду.
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
В pyqtgraph получается более высокая частота кадров, она будет работать на моем компьютере с 295 кадрами в секунду.
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_())
Если вы хотите анимировать сюжет, вы можете взглянуть на функциональность анимации в matplotlib под matplotlib.animation.Animation
, Вот отличный учебник - https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial.
Если вы просто хотите периодически обновлять растровое изображение adhoc, я не уверен, что matplotlib предназначен для того, чего вы пытаетесь достичь. Из документов matplotlib:
Matplotlib - это двухмерная библиотека черчения Python, которая генерирует показатели качества публикаций в различных печатных форматах и интерактивных средах на разных платформах.
Если вы хотите периодически обновлять изображение adhoc на экране, вы можете заглянуть в библиотеки GUI для python. Вот краткое резюме доступных опций - https://docs.python.org/3/faq/gui.html. Tkinter является довольно стандартным и поставляется с Python. Вы можете использовать ImageTk
модуль в pillow
создавать / изменять изображения для отображения через Tkinter - http://pillow.readthedocs.io/en/4.2.x/reference/ImageTk.html.
Если вам просто нужно оживить matplotlib
холст рамки анимации является ответом. Здесь есть простой пример, который делает в основном то, что вы просите.
Если это будет частью более сложного приложения, возможно, вам нужен более точный контроль над конкретным бэкэндом.
Вот быстрая попытка с помощью Qt
свободно на основе этого примера matplotlib.
Это использует QTimer
для обновлений, вероятно, есть также несколько обратных вызовов в Qt
Вы можете прикрепить к.
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_())
Вы должны быть осторожны с тем, что imshow
вычисляет нормализацию изображения на первом кадре. В последующих кадрах это вызывает set_data
поэтому нормализация остается прежней. Если вы хотите обновить его, вы можете позвонить imshow
вместо этого (вероятно, медленнее). Или вы можете просто исправить это вручную vmin
а также vmax
во-первых imshow
позвоните и предоставьте правильно нормированные кадры.
Учитывая, что вы говорили об использовании распространенных модулей, вот доказательство концепции использования OpenCV
, Здесь он работает довольно быстро, до 250-300 сгенерированных кадров в секунду. Ничего особенного, просто чтобы показать, что, может быть, если вы не используете какую-либо функцию печати matplotlib
не должно быть вашим первым выбором.
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()