【问题标题】:matplotlib.animate in python using multiprocessingpython中的matplotlib.animate使用多处理
【发布时间】:2021-04-15 15:34:23
【问题描述】:

我正在尝试使用 python 进程为绘图设置动画,如下所示:

from multiprocessing import Process
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

process_enabled = 1;
print("Process enabled: ", process_enabled)

x = []
y = []
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)        

def start_animation():
                 
   # Set up plot to call animate() function periodically   
   ani = animation.FuncAnimation(fig, animate, fargs=(x, y), interval=1000)
   print("Called animate function")
   plt.show()   
   
# This function is called periodically from FuncAnimation
def animate(i, xs, ys):
    
   fx=[0.045,0.02,0.0,0.04,0.015,-0.01,0.015,0.045,0.035,0.01,
        0.055,0.04,0.02,0.025,0.0,-0.005,-0.005,-0.02,-0.05,-0.03] # fx values        
    
   # Add x and y to lists
   xs.append(dt.datetime.now().strftime('%H:%M:%S.%f'))
   if(i<len(fx)):                     
      ys.append(fx[i])             

   # Draw x and y lists
   ax.clear()
   if(i<len(fx)):   
      ys_stacked = np.stack((np.array(ys),0.1+np.array(ys)),axis=1)
      ax.plot(xs, ys_stacked)
      
   print("Animating")      

   # Format plot
   if(i<len(fx)):
      plt.xticks(rotation=45, ha='right')
      plt.subplots_adjust(bottom=0.30)
      plt.title('Force/Torque Sensor Data')
      plt.ylabel('Fx (N)')    

if(process_enabled):
    
   p_graph = Process(name='Graph', target=start_animation)
   print("Created graph process")

   p_graph.start()
   print("Started graph process")           
   
else:   

   start_animation()

当我禁用该进程时,start_animation() 函数可以正常工作,并且会显示绘图并开始动画。但是,当启用该进程时,该进程将启动,然后代码在 print("Called animate function") 处中断。没有绘图窗口,终端中也没有错误消息)。

我对 python 中的多处理和 matplotlib 都是新手。任何方向将不胜感激。

干杯, 托尼

【问题讨论】:

    标签: python matplotlib animation process


    【解决方案1】:

    我正在尝试解决同样的问题,但还没有完全弄清楚。不过,我想我可以针对您的问题提供一些有用的 cmets。

    首先,您有什么理由要在单独的进程中处理动画吗?您的方法似乎在单个过程中运行良好。为此,您需要解决许多问题。如果您确实需要一个单独的过程,那么以下可能会有用。

    首先,您将无法在'graph' 进程中使用全局变量,因为该进程不共享这些变量的相同实例(请参阅Globals variables and Python multiprocessing)。

    您可以在进程之间共享状态,但这对于您想要共享的复杂对象(即plt.figure())来说很困难。有关更多信息,请参阅multiprocessing 参考 (https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes)

    最后一个建议是取消pyplot 接口。这对于简单的脚本和交互式数据分析来说很方便,但它混淆了很多重要的事情——比如当你调用 plt 方法时知道你正在处理哪个图形、轴等。

    我提供了另一种使用自定义类的面向对象的方法,它可以运行您的动画(无需单独的进程):

    import sys
    from multiprocessing import Process, Queue
    import datetime as dt
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
    from matplotlib.backends.qt_compat import QtWidgets
    import matplotlib.animation as animation
    
    class StripChart(FigureCanvasQTAgg):
        def __init__(self):
            self.fig = Figure(figsize=(8,5), dpi=100)
            self.ax = self.fig.add_subplot(111)
    
            # hold a copy of our torque data
            self.fx = [0.045,0.02,0.0,0.04,0.015,-0.01,0.015,0.045,0.035,0.01,
                       0.055,0.04,0.02,0.025,0.0,-0.005,-0.005,-0.02,-0.05,-0.03]
    
            super().__init__(self.fig)
    
            # instantiate the data arrays
            self.xs = []
            self.ys = []
    
        def start_animation(self):
            print("starting animation")
    
            # set up the animation
            self.ani = animation.FuncAnimation(self.fig, self.animate, init_func=self.clear_frame,
                                               frames=100, interval=500, blit=False)
    
        def clear_frame(self):
            self.ax.clear()
            self.ax.plot([], [])
    
    
        def animate(self, i):
            print("animate frame")
            # get the current time
            t_now = dt.datetime.now()
    
            # update trace values
            self.xs.append(t_now.strftime("%H:%M:%S.%f"))
            self.ys.append(self.fx[i % len(self.fx)])
    
            # keep max len(self.fx) points
            if len(self.xs) > len(self.fx):
                self.xs.pop(0)
                self.ys.pop(0)
    
            self.ax.clear()
            self.ax.plot(self.xs, self.ys)
    
            # need to reapply format after clearing axes
            self.fig.autofmt_xdate(rotation=45)
            self.fig.subplots_adjust(bottom=0.30)
            self.ax.set_title('Force/Torque Sensor Data')
            self.ax.set_ylabel('Fx (N)')
    
    if __name__=='__main__':
        # start a new qapplication
        qapp = QtWidgets.QApplication(sys.argv)
    
        # create our figure in the main process
        strip_chart = StripChart()
    
        strip_chart.show()
        strip_chart.start_animation()
    
        # start qt main loop
        qapp.exec()
    

    本例中的注意事项:

    • 您需要在您的环境中安装后端(即pip install pyqt5
    • 我在动画中添加了一个init_func,你不需要这个,你可以在animate方法中调用self.ax.clear()
    • 如果您需要更好的动画性能,可以使用blit=True,但您需要修改clear_frameanimate 方法以返回您想要更新的艺术家(请参阅https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/ 了解更多信息信息)。一个缺点是您将无法使用该方法更新轴标签。
    • 我已将其设置为无限运行,直到您关闭窗口

    我假设您希望在单独的进程中运行动画的原因是更新图形数据或绘制所有点涉及一些耗时/CPU 密集型任务。也许您已将其嵌入到其他 UI 中?

    我尝试在单独的进程中执行动画,但您需要传递所显示图形的实例。正如我所提到的,这并不简单,尽管似乎确实有办法做到这一点 (https://stackoverflow.com/a/57793267/13752965)。如果我找到可行的解决方案,我会更新。

    【讨论】:

    • @Tony 实际上在 matplotlib 文档 (matplotlib.org/stable/gallery/misc/multiprocess_sgskip.html) 中有一个示例。主要的收获是,在制作动画的过程中实例化你的图形是最有意义的。您可以从其他地方输入数据,假设它是一个简单类型(double、int 等)
    猜你喜欢
    • 2013-05-30
    • 1970-01-01
    • 2018-01-13
    • 1970-01-01
    • 2016-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-22
    相关资源
    最近更新 更多