Hinweis
Zum Ende springen, um den vollständigen Beispielcode herunterzuladen.
Schnellere Darstellung durch Blitting#
Blitting ist eine Standardtechnik in der Rastergrafik, die im Kontext von Matplotlib verwendet werden kann, um die Leistung interaktiver Grafiken (drastisch) zu verbessern. Zum Beispiel verwenden die Module animation und widgets Blitting intern. Hier zeigen wir, wie Sie Ihr eigenes Blitting außerhalb dieser Klassen implementieren können.
Blitting beschleunigt wiederholtes Zeichnen, indem alle sich nicht ändernden grafischen Elemente einmal in ein Hintergrundbild gerendert werden. Dann müssen für jeden Zeichenvorgang nur die sich ändernden Elemente auf diesen Hintergrund gezeichnet werden. Wenn sich beispielsweise die Grenzen eines Achsenobjekts nicht geändert haben, können wir die leeren Achsen einschließlich aller Ticks und Beschriftungen einmal rendern und nur die sich ändernden Daten später zeichnen.
Die Strategie ist
Vorbereitung des konstanten Hintergrunds
Zeichnen Sie die Grafik, schließen Sie jedoch alle Künstler aus, die Sie animieren möchten, indem Sie sie als animiert markieren (siehe
Artist.set_animated).Sichern Sie eine Kopie des RGBA-Puffers.
Rendern der einzelnen Bilder
Stellen Sie die Kopie des RGBA-Puffers wieder her.
Zeichnen Sie die animierten Künstler mit
Axes.draw_artist/Figure.draw_artistneu.Zeigen Sie das resultierende Bild auf dem Bildschirm an.
Eine Konsequenz dieses Verfahrens ist, dass Ihre animierten Künstler immer über den statischen Künstlern gezeichnet werden.
Nicht alle Backends unterstützen Blitting. Sie können überprüfen, ob ein bestimmtes Canvas dies unterstützt, über die Eigenschaft FigureCanvasBase.supports_blit.
Warnung
Dieser Code funktioniert nicht mit dem macosx-Backend (funktioniert aber mit anderen GUI-Backends auf Mac).
Minimalbeispiel#
Wir können die Methoden copy_from_bbox und restore_region der FigureCanvasAgg in Verbindung mit dem Setzen von animated=True auf unserem Künstler verwenden, um ein minimales Beispiel zu implementieren, das Blitting zur Beschleunigung des Renderings verwendet.
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)

Dieses Beispiel funktioniert und zeigt eine einfache Animation. Da wir den Hintergrund jedoch nur einmal aufnehmen, wird der Hintergrund ungültig und führt zu falschen (aber manchmal cool aussehenden!) Bildern, wenn sich die Größe der Grafik in Pixeln ändert (entweder durch Änderung der Größe oder DPI der Grafik). Es gibt auch eine globale Variable und viel Boilerplate, was darauf hindeutet, dass wir dies in eine Klasse verpacken sollten.
Klassenbasierte Beispiel#
Wir können eine Klasse verwenden, um die Boilerplate-Logik und den Zustand des Wiederherstellens des Hintergrunds, des Zeichnens der Künstler und des anschließenden Blittings des Ergebnisses auf dem Bildschirm zu kapseln. Zusätzlich können wir den Rückruf 'draw_event' verwenden, um einen neuen Hintergrund zu erfassen, wann immer eine vollständige Neuberechnung stattfindet, um Größenänderungen korrekt zu behandeln.
class BlitManager:
def __init__(self, canvas, animated_artists=()):
"""
Parameters
----------
canvas : FigureCanvasAgg
The canvas to work with, this only works for subclasses 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()
Hier ist, wie wir unsere Klasse verwenden würden. Dies ist ein etwas komplizierteres Beispiel als der erste Fall, da wir zusätzlich einen Textrahmenzähler hinzufügen.
# 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(f"frame: {j}")
# tell the blitting manager to do its thing
bm.update()

Diese Klasse ist nicht von pyplot abhängig und eignet sich zum Einbetten in größere GUI-Anwendungen.
Gesamtlaufzeit des Skripts: (0 Minuten 1.025 Sekunden)