Nota
Haga clic aquí para descargar el código de ejemplo completo
Renderizado más rápido usando blitting #
Blitting es una técnica estándar en gráficos de trama que, en el contexto de Matplotlib, se puede utilizar para mejorar (drásticamente) el rendimiento de las figuras interactivas. Por ejemplo, los
módulos animation
y widgets
usan blitting internamente. Aquí, demostramos cómo implementar su propio blitting, fuera de estas clases.
Blit acelera el dibujo repetitivo al convertir todos los elementos gráficos que no cambian en una imagen de fondo una vez. Luego, para cada dibujo, solo los elementos cambiantes deben dibujarse en este fondo. Por ejemplo, si los límites de un eje no han cambiado, podemos representar los ejes vacíos, incluidas todas las marcas y etiquetas, una vez, y solo dibujar los datos cambiantes más tarde.
la estrategia es
Prepare el fondo constante:
Dibuja la figura, pero excluye a todos los artistas que quieras animar marcándolos como animados (ver
Artist.set_animated
).Guarde una copia del búfer RBGA.
Renderice las imágenes individuales:
Restaure la copia del búfer RGBA.
Vuelva a dibujar los artistas animados usando
Axes.draw_artist
/Figure.draw_artist
.Muestre la imagen resultante en la pantalla.
Una consecuencia de este procedimiento es que sus artistas animados siempre se dibujan encima de los artistas estáticos.
No todos los backends admiten blitting. Puede verificar si un lienzo determinado lo hace a través de la FigureCanvasBase.supports_blit
propiedad.
Advertencia
Este código no funciona con el backend de OSX (pero sí funciona con otros backends de GUI en Mac).
Ejemplo mínimo #
Podemos usar los FigureCanvasAgg
métodos
copy_from_bbox
y
restore_region
junto con la configuración
animated=True
de nuestro artista para implementar un ejemplo mínimo que use blitting para acelerar el renderizado.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2 * np.pi, 100)
fig, ax = plt.subplots()
# animated=True tells matplotlib to only draw the artist when we
# explicitly request it
(ln,) = ax.plot(x, np.sin(x), animated=True)
# make sure the window is raised, but the script keeps going
plt.show(block=False)
# stop to admire our empty window axes and ensure it is rendered at
# least once.
#
# We need to fully draw the figure at its final size on the screen
# before we continue on so that :
# a) we have the correctly sized and drawn background to grab
# b) we have a cached renderer so that ``ax.draw_artist`` works
# so we spin the event loop to let the backend process any pending operations
plt.pause(0.1)
# get copy of entire figure (everything inside fig.bbox) sans animated artist
bg = fig.canvas.copy_from_bbox(fig.bbox)
# draw the animated artist, this uses a cached renderer
ax.draw_artist(ln)
# show the result to the screen, this pushes the updated RGBA buffer from the
# renderer to the GUI framework so you can see it
fig.canvas.blit(fig.bbox)
for j in range(100):
# reset the background back in the canvas state, screen unchanged
fig.canvas.restore_region(bg)
# update the artist, neither the canvas state nor the screen have changed
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
# re-render the artist, updating the canvas state, but not the screen
ax.draw_artist(ln)
# copy the image to the GUI state, but screen might not be changed yet
fig.canvas.blit(fig.bbox)
# flush any pending GUI events, re-painting the screen if needed
fig.canvas.flush_events()
# you can put a pause in if you want to slow things down
# plt.pause(.1)
Este ejemplo funciona y muestra una animación simple, sin embargo, debido a que solo tomamos el fondo una vez, si el tamaño de la figura en píxeles cambia (ya sea porque cambia el tamaño o los dpi de la figura), el fondo no será válido y dará como resultado imágenes incorrectas (¡pero a veces geniales!). También hay una variable global y una buena cantidad de placa de caldera que sugiere que deberíamos envolver esto en una clase.
Ejemplo basado en clases #
Podemos usar una clase para encapsular la lógica repetitiva y el estado de restaurar el fondo, dibujar a los artistas y luego mostrar el resultado en la pantalla. Además, podemos usar la 'draw_event'
devolución de llamada para capturar un nuevo fondo siempre que se redibuje por completo para manejar los cambios de tamaño correctamente.
class BlitManager:
def __init__(self, canvas, animated_artists=()):
"""
Parameters
----------
canvas : FigureCanvasAgg
The canvas to work with, this only works for sub-classes of the Agg
canvas which have the `~FigureCanvasAgg.copy_from_bbox` and
`~FigureCanvasAgg.restore_region` methods.
animated_artists : Iterable[Artist]
List of the artists to manage
"""
self.canvas = canvas
self._bg = None
self._artists = []
for a in animated_artists:
self.add_artist(a)
# grab the background on every draw
self.cid = canvas.mpl_connect("draw_event", self.on_draw)
def on_draw(self, event):
"""Callback to register with 'draw_event'."""
cv = self.canvas
if event is not None:
if event.canvas != cv:
raise RuntimeError
self._bg = cv.copy_from_bbox(cv.figure.bbox)
self._draw_animated()
def add_artist(self, art):
"""
Add an artist to be managed.
Parameters
----------
art : Artist
The artist to be added. Will be set to 'animated' (just
to be safe). *art* must be in the figure associated with
the canvas this class is managing.
"""
if art.figure != self.canvas.figure:
raise RuntimeError
art.set_animated(True)
self._artists.append(art)
def _draw_animated(self):
"""Draw all of the animated artists."""
fig = self.canvas.figure
for a in self._artists:
fig.draw_artist(a)
def update(self):
"""Update the screen with animated artists."""
cv = self.canvas
fig = cv.figure
# paranoia in case we missed the draw event,
if self._bg is None:
self.on_draw(None)
else:
# restore the background
cv.restore_region(self._bg)
# draw all of the animated artists
self._draw_animated()
# update the GUI state
cv.blit(fig.bbox)
# let the GUI event loop process anything it has to do
cv.flush_events()
Así es como usaríamos nuestra clase. Este es un ejemplo un poco más complicado que el primer caso, ya que también agregamos un contador de marco de texto.
# make a new figure
fig, ax = plt.subplots()
# add a line
(ln,) = ax.plot(x, np.sin(x), animated=True)
# add a frame number
fr_number = ax.annotate(
"0",
(0, 1),
xycoords="axes fraction",
xytext=(10, -10),
textcoords="offset points",
ha="left",
va="top",
animated=True,
)
bm = BlitManager(fig.canvas, [ln, fr_number])
# make sure our window is on the screen and drawn
plt.show(block=False)
plt.pause(.1)
for j in range(100):
# update the artists
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
fr_number.set_text("frame: {j}".format(j=j))
# tell the blitting manager to do its thing
bm.update()
Esta clase no depende pyplot
y es adecuada para integrarse en una aplicación GUI más grande.
Tiempo total de ejecución del script: (0 minutos 1,185 segundos)