Schnelleres Rendern durch Blittern #

Blitting ist eine Standardtechnik in Rastergrafiken, die im Kontext von Matplotlib verwendet werden kann, um die Leistung interaktiver Figuren (drastisch) zu verbessern. Beispielsweise verwenden die Module animationund widgetsintern Blitting. Hier demonstrieren wir, wie Sie Ihr eigenes Blitting außerhalb dieser Klassen implementieren können.

Blitting beschleunigt das wiederholte Zeichnen, indem es alle sich nicht ändernden grafischen Elemente einmal in ein Hintergrundbild rendert. Auf diesen Hintergrund müssen dann bei jeder Ziehung nur noch die wechselnden Elemente gezeichnet werden. Wenn sich beispielsweise die Grenzen einer Achse nicht geändert haben, können wir die leeren Achsen einschließlich aller Ticks und Beschriftungen einmal rendern und später nur die sich ändernden Daten zeichnen.

Die Strategie ist

  • Bereiten Sie den konstanten Hintergrund vor:

    • Zeichnen Sie die Figur, aber schließen Sie alle Künstler aus, die Sie animieren möchten, indem Sie sie als animiert markieren (siehe Artist.set_animated).

    • Speichern Sie eine Kopie des RBGA-Puffers.

  • Rendern Sie die einzelnen Bilder:

    • Stellen Sie die Kopie des RGBA-Puffers wieder her.

    • Zeichnen Sie die animierten Künstler mit Axes.draw_artist/ neu Figure.draw_artist.

    • Zeigen Sie das resultierende Bild auf dem Bildschirm an.

Eine Folge 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 eine bestimmte Leinwand über die FigureCanvasBase.supports_blitEigenschaft funktioniert.

Warnung

Dieser Code funktioniert nicht mit dem OSX-Backend (funktioniert aber mit anderen GUI-Backends auf dem Mac).

Minimalbeispiel #

Wir können die FigureCanvasAggMethoden copy_from_bboxund restore_regionin Verbindung mit der Einstellung animated=Trueunseres Künstlers verwenden, um ein minimales Beispiel zu implementieren, das Blitting verwendet, um das Rendern zu beschleunigen

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)
blitzen

Dieses Beispiel funktioniert und zeigt eine einfache Animation, aber da wir den Hintergrund nur einmal erfassen, wird der Hintergrund ungültig und führt zu, wenn sich die Größe der Figur in Pixeln ändert (aufgrund entweder der Größe oder der DPI-Änderung der Figur). falsche (aber manchmal cool aussehende!) Bilder. Es gibt auch eine globale Variable und eine ganze Menge Boiler Plate, was darauf hindeutet, dass wir dies in eine Klasse packen sollten.

Klassenbasiertes Beispiel #

Wir können eine Klasse verwenden, um die Boilerplate-Logik und den Zustand der Wiederherstellung des Hintergrunds zu kapseln, die Künstler zu zeichnen und dann das Ergebnis auf den Bildschirm zu übertragen. Darüber hinaus können wir den 'draw_event' Rückruf verwenden, um einen neuen Hintergrund zu erfassen, wenn eine vollständige Neuzeichnung passiert, um Größenänderungen korrekt zu handhaben.

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()

So würden wir unsere Klasse verwenden. Dies ist ein etwas komplizierteres Beispiel als der erste Fall, da wir auch 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("frame: {j}".format(j=j))
    # tell the blitting manager to do its thing
    bm.update()
blitzen

Diese Klasse hängt nicht von größeren GUI-Anwendungen ab pyplotund eignet sich zur Einbettung in größere GUI-Anwendungen.

Gesamtlaufzeit des Skripts: ( 0 Minuten 1.185 Sekunden)

Galerie generiert von Sphinx-Gallery