Observação
Clique aqui para baixar o código de exemplo completo
Renderização mais rápida usando blitting #
Blitting é uma técnica padrão em gráficos raster que, no contexto do Matplotlib, pode ser usado para (drasticamente) melhorar o desempenho de figuras interativas. Por exemplo, os
módulos animation
e widgets
usam blitting internamente. Aqui, demonstramos como implementar seu próprio blitting, fora dessas classes.
O blitting acelera o desenho repetitivo renderizando todos os elementos gráficos inalteráveis em uma imagem de fundo uma vez. Então, para cada desenho, apenas os elementos que mudam precisam ser desenhados neste fundo. Por exemplo, se os limites de um Axes não foram alterados, podemos renderizar os Axes vazios, incluindo todos os ticks e rótulos uma vez, e apenas desenhar os dados alterados mais tarde.
A estratégia é
Prepare o fundo constante:
Desenhe a figura, mas exclua todos os artistas que deseja animar marcando-os como animados (consulte Recursos
Artist.set_animated
).Salve uma cópia do buffer RBGA.
Renderize as imagens individuais:
Restaure a cópia do buffer RGBA.
Redesenhe os artistas animados usando
Axes.draw_artist
/Figure.draw_artist
.Mostre a imagem resultante na tela.
Uma consequência desse procedimento é que seus artistas animados são sempre desenhados em cima dos artistas estáticos.
Nem todos os back-ends suportam blitting. Você pode verificar se uma determinada tela funciona por meio da FigureCanvasBase.supports_blit
propriedade.
Aviso
Este código não funciona com o back-end do OSX (mas funciona com outros back-ends da GUI no mac).
Exemplo mínimo #
Podemos usar os FigureCanvasAgg
métodos
copy_from_bbox
e
restore_region
em conjunto com a configuração
animated=True
em nosso artista para implementar um exemplo mínimo que usa blitting para acelerar a renderização
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 exemplo funciona e mostra uma animação simples, no entanto, como estamos capturando o plano de fundo apenas uma vez, se o tamanho da figura em pixels mudar (devido à mudança de tamanho ou dpi da figura), o plano de fundo será inválido e resultará em imagens incorretas (mas às vezes legais!). Há também uma variável global e uma boa quantidade de clichê que sugere que devemos agrupar isso em uma classe.
Exemplo baseado em classe #
Podemos usar uma classe para encapsular a lógica padronizada e o estado de restauração do plano de fundo, desenhando os artistas e, em seguida, exibindo o resultado na tela. Além disso, podemos usar o 'draw_event'
retorno de chamada para capturar um novo plano de fundo sempre que um redesenho completo acontecer para lidar com os redimensionamentos corretamente.
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()
Aqui está como usaríamos nossa classe. Este é um exemplo um pouco mais complicado do que o primeiro caso, pois também adicionamos um contador de quadro 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 classe não depende pyplot
e é adequada para incorporar em um aplicativo GUI maior.
Tempo total de execução do script: ( 0 minutos 1,185 segundos)