블리팅을 사용하여 더 빠른 렌더링 #

Blitting 은 Matplotlib의 맥락에서 대화형 그림의 성능을 (급격하게) 개선하는 데 사용할 수 있는 래스터 그래픽 의 표준 기술 입니다. 예를 들어 animationwidgets모듈은 내부적으로 블리팅을 사용합니다. 여기서는 이러한 클래스 외부에서 고유한 블리팅을 구현하는 방법을 보여줍니다.

Blitting은 변경되지 않는 모든 그래픽 요소를 배경 이미지로 한 번 렌더링하여 반복적인 그리기 속도를 높입니다. 그런 다음 모든 그리기에 대해 변경 요소만 이 배경에 그려야 합니다. 예를 들어 Axes의 한계가 변경되지 않은 경우 모든 눈금과 레이블을 포함하는 빈 Axes를 한 번 렌더링하고 나중에 변경되는 데이터만 그릴 수 있습니다.

전략은

  • 일정한 배경 준비:

    • 그림을 그리되 애니메이션으로 표시하여 애니메이션 하려는 모든 아티스트를 제외합니다 ( 참조 Artist.set_animated).

    • RBGA 버퍼의 복사본을 저장합니다.

  • 개별 이미지 렌더링:

    • RGBA 버퍼의 복사본을 복원합니다.

    • Axes.draw_artist/ 를 사용하여 애니메이션 아티스트를 다시 그립니다 Figure.draw_artist.

    • 결과 이미지를 화면에 표시합니다.

이 절차의 한 가지 결과는 애니메이션 아티스트가 항상 정적 아티스트 위에 그려지는 것입니다.

모든 백엔드가 블리팅을 지원하는 것은 아닙니다. 주어진 캔버스가 속성을 통해 수행하는지 확인할 수 있습니다 FigureCanvasBase.supports_blit.

경고

이 코드는 OSX 백엔드에서 작동하지 않습니다(그러나 Mac의 다른 GUI 백엔드에서는 작동합니다).

최소 예시 #

렌더링을 가속화하기 위해 블리팅을 사용하는 최소한의 예제를 구현하기 위해 아티스트에 대한 설정과 함께 FigureCanvasAgg메서드 copy_from_bbox및를 사용할 수 있습니다.restore_regionanimated=True

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)
블리팅

이 예제는 작동하고 간단한 애니메이션을 보여줍니다. 그러나 우리는 배경을 한 번만 잡기 때문에 그림의 크기가 픽셀 단위로 변경되면(그림의 크기 또는 dpi 변경으로 인해) 배경이 유효하지 않게 되고 결과는 부정확한(때때로 멋져 보이는!) 이미지. 또한 전역 변수와 상당한 양의 상용구가 있어 이를 클래스로 래핑해야 합니다.

클래스 기반 예제 #

클래스를 사용하여 상용구 논리와 배경 복원 상태를 캡슐화하고 아티스트를 그린 다음 결과를 화면에 출력할 수 있습니다. 또한 'draw_event' 크기 조정을 올바르게 처리하기 위해 전체 다시 그리기가 발생할 때마다 콜백을 사용하여 새 배경을 캡처할 수 있습니다.

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

다음은 클래스를 사용하는 방법입니다. 텍스트 프레임 카운터도 추가하므로 첫 번째 경우보다 약간 더 복잡한 예입니다.

# 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()
블리팅

pyplot이 클래스는 더 큰 GUI 응용 프로그램에 의존하지 않으며 포함하기에 적합합니다.

스크립트의 총 실행 시간: (0분 1.185초)

Sphinx-Gallery에서 생성한 갤러리