Python ln_Python 可视化——绘制动图

在进行动力学模拟的时候遇到了状态过程可视化问题,因此mark一下python动图的绘制,希望能有所帮助。这里主要介绍 python中 matplotlib 的 animation 模块,可以快速绘制Mp4, gif动画

class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)

Matplotlib 的 animation 包含动图生成的几乎所有操作,包含:

Animation This class wraps the creation of an animation using matplotlib. 创建一个动画生成对象

FuncAnimation Makes an animation by repeatedly calling a function func. 使用特定的函数生成动画

ArtistAnimation Animation using a fixed set of Artist objects. 使用固定的(我理解为以及画好的图像)生成动画

为了保存画出来的动画,animation 提供了一些writer,这些需要借助安装第三方软件例如ffmpeg 和 ImageMagick.

FFMpegWriter Pipe-based ffmpeg writer.

ImageMagickWriter Pipe-based animated gif.

AVConvWriter Pipe-based avconv writer.

更多信息matplotlib.animation 官方文档都有说明。

安装ffmpeg 和 ImageMagick过程很简单,而且安装imagemagick的时候还可以选择安装ffmpeg,下面给出了安装链接。

a2a5293ebcc7a4b4bbef249f8e3e3947.png
ImageMagick下载链接​imagemagick.org matplotlib.animation 官方文档​matplotlib.org

接下来是绘图代码了,目前看到的最清楚的框架是下面这个(修改自官方文档的例子,保存图片上传不了,可以自行运行试试):

import matplotlib.pyplot as plt
# plt.rcParams['animation.convert_path'] = 'E:Program FilesImageMagick-7.0.8-Q16magick.exe'

from matplotlib import animation 
# writer = ImageMagickFileWriter() #如果安装了ImageMahick
writer = animation.FFMpegWriter() #如果安装了ffmpeg
# 导入相关模块
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import Image
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
# 注意这边的 "," 不能省
ln, = ax.plot([], [], 'r-', animated=False)

# 初始化图像(譬如 坐标范围)
def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1.1, 1.1)
    # 注意这边的 "," 也不能省
    return ln,

# 将最新数据添加到图像中
def update(frame):
    xdata.append(frame)
    ydata.append(np.sin(frame))
    ln.set_data(xdata, ydata)
    # 注意这边的 "," 也不能省
    return ln,

# 核心方法入口
ani = FuncAnimation(fig, 
                    update, 
                    frames=np.linspace(0, 2*np.pi, 50),
                    interval=5,
                    init_func=init, 
                    blit=True)

# 保存动态图
ani.save('ming.mp4',writer=writer)

# 将图展示在页面上
Image(url='./ming.mp4')

下面我用可视化代码运行了Kuramoto模型的变化过程:

# 导入相关模块
import matplotlib.pyplot as plt
# 可以自行设置writer的路径,不过一般添加了环境变量可以不考虑
# plt.rcParams['animation.convert_path'] = 'E:Program FilesImageMagick-7.0.8-Q16magick.exe'
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import Image
from matplotlib import animation 
from matplotlib.animation import FuncAnimation

#writer = animation.ImageMagickFileWriter() #如果安装了ImageMahick
writer = animation.FFMpegWriter() #如果安装了ffmpeg

data = pd.read_csv('phase_sin.csv',index_col=0) # 导入数据
datax = np.cos(data)
datay = np.sin(data)
n = data.shape[0]
s = data.shape[1]
print(s)

fig, ax = plt.subplots(figsize = (10,10))
x = np.linspace(-1, 1, 100) 
ax.plot(x, np.sqrt(1-x**2),'b')
ax.plot(x, -np.sqrt(1-x**2),'b') # 画出单位圆

def get_data(frame=0,sensor_id=0):
    #print(frame,sensor_id)
    x = datax.iloc[sensor_id, frame]
    y = datay.iloc[sensor_id, frame]
    return x,y

# initialization function 
def init(): 
    ax.set_xlim(-1.1, 1.1)
    ax.set_ylim(-1.1, 1.1)
    # plot the first day (day=0) here:
    for j in range(n):
        x,y=get_data(0,j)
        scat = ax.scatter(x,y,c='r')
    return scat,

# animation function 
def update(i): 
    for j in range(n):
        x,y=get_data(i,j)
        # print(i,col)## use this to understand "where" we are
        scat = ax.scatter(x,y,c='r')

    # return plot object 
    return scat,

# 核心方法入口
ani = FuncAnimation(fig, 
                    update, 
                    frames=200,
                    interval=100,
                    init_func=init, 
                    blit=True)

# 保存 动态图
ani.save('phase.mp4',writer=writer)
c298c4afc8cb89fce431b8edbe36913b.png
Kuramoto模型同步过程https://www.zhihu.com/video/1228651616762142720

附录:FuncAnimation源码,供感兴趣的阅读者使用

class FuncAnimation(TimedAnimation):
    """
    Makes an animation by repeatedly calling a function *func*.

    Parameters
    ----------
    fig : `~matplotlib.figure.Figure`
       The figure object that is used to get draw, resize, and any
       other needed events.

    func : callable
       The function to call at each frame.  The first argument will
       be the next value in *frames*.   Any additional positional
       arguments can be supplied via the *fargs* parameter.

       The required signature is::

          def func(frame, *fargs) -> iterable_of_artists

       If ``blit == True``, *func* must return an iterable of all artists
       that were modified or created. This information is used by the blitting
       algorithm to determine which parts of the figure have to be updated.
       The return value is unused if ``blit == False`` and may be omitted in
       that case.

    frames : iterable, int, generator function, or None, optional
        Source of data to pass *func* and each frame of the animation

        - If an iterable, then simply use the values provided.  If the
          iterable has a length, it will override the *save_count* kwarg.

        - If an integer, then equivalent to passing ``range(frames)``

        - If a generator function, then must have the signature::

             def gen_function() -> obj

        - If *None*, then equivalent to passing ``itertools.count``.

        In all of these cases, the values in *frames* is simply passed through
        to the user-supplied *func* and thus can be of any type.

    init_func : callable, optional
       A function used to draw a clear frame. If not given, the
       results of drawing from the first item in the frames sequence
       will be used. This function will be called once before the
       first frame.

       The required signature is::

          def init_func() -> iterable_of_artists

       If ``blit == True``, *init_func* must return an iterable of artists
       to be re-drawn. This information is used by the blitting
       algorithm to determine which parts of the figure have to be updated.
       The return value is unused if ``blit == False`` and may be omitted in
       that case.

    fargs : tuple or None, optional
       Additional arguments to pass to each call to *func*.

    save_count : int, default: 100
        Fallback for the number of values from *frames* to cache. This is
        only used if the number of frames cannot be inferred from *frames*,
        i.e. when it's an iterator without length or a generator.

    interval : number, optional
       Delay between frames in milliseconds.  Defaults to 200.

    repeat_delay : number, optional
       If the animation in repeated, adds a delay in milliseconds
       before repeating the animation.  Defaults to *None*.

    repeat : bool, optional
       Controls whether the animation should repeat when the sequence
       of frames is completed.  Defaults to *True*.

    blit : bool, optional
       Controls whether blitting is used to optimize drawing. Note: when using
       blitting any animated artists will be drawn according to their zorder.
       However, they will be drawn on top of any previous artists, regardless
       of their zorder.  Defaults to *False*.

    cache_frame_data : bool, optional
       Controls whether frame data is cached. Defaults to *True*.
       Disabling cache might be helpful when frames contain large objects.
    """

[docs]    def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
                 save_count=None, *, cache_frame_data=True, **kwargs):
        if fargs:
            self._args = fargs
        else:
            self._args = ()
        self._func = func
        self._init_func = init_func

        # Amount of framedata to keep around for saving movies. This is only
        # used if we don't know how many frames there will be: in the case
        # of no generator or in the case of a callable.
        self.save_count = save_count
        # Set up a function that creates a new iterable when needed. If nothing
        # is passed in for frames, just use itertools.count, which will just
        # keep counting from 0. A callable passed in for frames is assumed to
        # be a generator. An iterable will be used as is, and anything else
        # will be treated as a number of frames.
        if frames is None:
            self._iter_gen = itertools.count
        elif callable(frames):
            self._iter_gen = frames
        elif np.iterable(frames):
            if kwargs.get('repeat', True):
                def iter_frames(frames=frames):
                    while True:
                        this, frames = itertools.tee(frames, 2)
                        yield from this
                self._iter_gen = iter_frames
            else:
                self._iter_gen = lambda: iter(frames)
            if hasattr(frames, '__len__'):
                self.save_count = len(frames)
        else:
            self._iter_gen = lambda: iter(range(frames))
            self.save_count = frames

        if self.save_count is None:
            # If we're passed in and using the default, set save_count to 100.
            self.save_count = 100
        else:
            # itertools.islice returns an error when passed a numpy int instead
            # of a native python int (http://bugs.python.org/issue30537).
            # As a workaround, convert save_count to a native python int.
            self.save_count = int(self.save_count)

        self._cache_frame_data = cache_frame_data

        # Needs to be initialized so the draw functions work without checking
        self._save_seq = []

        TimedAnimation.__init__(self, fig, **kwargs)

        # Need to reset the saved seq, since right now it will contain data
        # for a single frame from init, which is not what we want.
        self._save_seq = []


[docs]    def new_frame_seq(self):
        # Use the generating function to generate a new frame sequence
        return self._iter_gen()


[docs]    def new_saved_frame_seq(self):
        # Generate an iterator for the sequence of saved data. If there are
        # no saved frames, generate a new frame sequence and take the first
        # save_count entries in it.
        if self._save_seq:
            # While iterating we are going to update _save_seq
            # so make a copy to safely iterate over
            self._old_saved_seq = list(self._save_seq)
            return iter(self._old_saved_seq)
        else:
            if self.save_count is not None:
                return itertools.islice(self.new_frame_seq(), self.save_count)

            else:
                frame_seq = self.new_frame_seq()

                def gen():
                    try:
                        for _ in range(100):
                            yield next(frame_seq)
                    except StopIteration:
                        pass
                    else:
                        cbook.warn_deprecated(
                            "2.2", message="FuncAnimation.save has truncated "
                            "your animation to 100 frames.  In the future, no "
                            "such truncation will occur; please pass "
                            "'save_count' accordingly.")

                return gen()


    def _init_draw(self):
        # Initialize the drawing either using the given init_func or by
        # calling the draw function with the first item of the frame sequence.
        # For blitting, the init_func should return a sequence of modified
        # artists.
        if self._init_func is None:
            self._draw_frame(next(self.new_frame_seq()))

        else:
            self._drawn_artists = self._init_func()
            if self._blit:
                if self._drawn_artists is None:
                    raise RuntimeError('The init_func must return a '
                                       'sequence of Artist objects.')
                for a in self._drawn_artists:
                    a.set_animated(self._blit)
        self._save_seq = []

    def _draw_frame(self, framedata):
        if self._cache_frame_data:
            # Save the data for potential saving of movies.
            self._save_seq.append(framedata)

        # Make sure to respect save_count (keep only the last save_count
        # around)
        self._save_seq = self._save_seq[-self.save_count:]

        # Call the func with framedata and args. If blitting is desired,
        # func needs to return a sequence of any artists that were modified.
        self._drawn_artists = self._func(framedata, *self._args)
        if self._blit:
            if self._drawn_artists is None:
                raise RuntimeError('The animation function must return a '
                                   'sequence of Artist objects.')
            self._drawn_artists = sorted(self._drawn_artists,
                                         key=lambda x: x.get_zorder())

            for a in self._drawn_artists:
                a.set_animated(self._blit)

参考链接:

matplotlib[03]python 绘制动画animation​blog.chenzhaoqiang.com 如何用 Matplotlib 画 GIF 动图​www.tuicool.com
0df84bea25c1361013f3e79c159b7307.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值