matplotlib动画需要大量的时间来运行
我正在使用Animation.funcanimation
进行相当简单的matplotlib动画,并且运行时间很长。在11秒长的视频中,每帧约0.81和220帧的180帧。我已经结合了标准的性能改进,即在动画循环中没有创建艺术家对象并返回所有更新的艺术家对象,以便可以完成闪光,但都没有对性能产生重大影响。我已经计时了newFrame
函数的内容,并且运行约为0.6ms。我的代码在下面。
还有其他建议如何加快它或并行化?
编辑:看来性能问题与子图主要有关。减少子图的数量会大大减少执行时间。并不是一个很好的解决方案,但值得注意的
编辑2:评论axsArray [a,b] .minorticks_on()
将性能翻倍至〜0.4 s每一帧。这使得性能更符合我基于Matplotlib动画的先前经验的期望,尽管对于简单的情节来说仍然非常慢。
#!/usr/bin/env python3
from timeit import default_timer
import matplotlib
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
import os
import sys
matplotlib.use("Agg")
plt.close('all')
start = default_timer()
# ==============================================================================
def main():
# ==========================================================================
# Settings
# ==========================================================================
# Check for CLI arguments
if len(sys.argv) == 2:
cliFPS = int(sys.argv[1])
elif len(sys.argv) > 2:
raise TypeError(f"Too many ({len(sys.argv)}) command line arguments given")
# Data settings
loadPath = ''
yPos = 16 # y position of the x line to load
zPos = 16 # z position of the x line to load
# Plot Settings
supTitleText = "Time Evolution of Initial Conditions"
densityColor = 'blue' # color of the density plot
velocityColor = 'purple' # color of the velocity plots
magneticColor = 'tab:orange' # color of the magnetic field plots
pressureColor = 'green' # color of the pressure plot
ieColor = 'red' # color of the specific internal energy plot
linestyle = '-' # The line style
linewidth = 0.5 # How wide to make the lines
marker = "." # Marker kind for points
markersize = 3 # Size of the marker
figSizeScale = 2. # Scaling factor for the figure size
figHeight = 4.8 * figSizeScale # height of the plot in inches, default is 4.8
figWidth = 7.0 * figSizeScale # width of the plot in inches, default is 6.4
padPercent = 0.05 # How many percent larger the limits should be than the data
# Video Settings
OutFile = "mvp.mp4" # Output filename
Duration = 10. # How long the video is in seconds
dpi = 150 # Dots per inch
index = 0 # Initialize index
initIndex = 0 # Index for init frames
fps = cliFPS if ("cliFPS" in locals()) else 20 # Framerate
FrameTime = (1./fps) * 1000 # Frametime in milliseconds
totFrames = int(fps * Duration) # Total number of frames (floor)
# ==========================================================================
# End settings
# ==========================================================================
# Load data
(densityData, velocityXData, velocityYData, velocityZData, pressureData,
ieData, magneticXData, magneticYData, magneticZData, positions,
timeStepNum, dims, physicalSize) = loadData(loadPath, yPos, zPos)
# Compute which time steps to plot
if timeStepNum.size >= totFrames:
floatSamples = np.arange(0, timeStepNum.size, timeStepNum.size/totFrames)
timeStepSamples = np.asarray(np.floor(floatSamples), dtype="int")
else: # if the number of simulation steps is less than the total number of frames
totFrames = timeStepNum.size
fps = np.ceil(totFrames/Duration)
FrameTime = (1./fps) * 1000
timeStepSamples = np.arange(0, timeStepNum.size, 1, dtype="int")
# Insert the initial second of the initial conditions
timeStepSamples = np.insert(timeStepSamples, 0, [0]*fps)
# Get the plot limits
densityLowLim, densityHighLim = computeLimit(densityData, padPercent)
ieLowLim, ieHighLim = computeLimit(ieData, padPercent)
pressureLowLim, pressureHighLim = computeLimit(pressureData, padPercent)
velocityXLowLim, velocityXHighLim = computeLimit(velocityXData, padPercent)
velocityYLowLim, velocityYHighLim = computeLimit(velocityYData, padPercent)
velocityZLowLim, velocityZHighLim = computeLimit(velocityXData, padPercent)
magneticXLowLim, magneticXHighLim = computeLimit(magneticXData, padPercent)
magneticYLowLim, magneticYHighLim = computeLimit(magneticYData, padPercent)
magneticZLowLim, magneticZHighLim = computeLimit(magneticZData, padPercent)
# Set up plots
# Create 9 subplots
fig, subPlot = plt.subplots(3, 3, figsize = (figWidth, figHeight))
# Super Title
titleText = fig.suptitle(supTitleText)
# Shared x-label
subPlot[2,0].set_xlabel("Position")
subPlot[2,1].set_xlabel("Position")
subPlot[2,2].set_xlabel("Position")
# Set values for the subplots
densityPlot, subPlot = setupSubPlots('Density', densityLowLim, densityHighLim, positions, densityData, linestyle, linewidth, marker, markersize, densityColor, subPlot)
pressurePlot, subPlot = setupSubPlots('Pressure', pressureLowLim, pressureHighLim, positions, pressureData, linestyle, linewidth, marker, markersize, pressureColor, subPlot)
iePlot, subPlot = setupSubPlots('Internal Energy', ieLowLim, ieHighLim, positions, ieData, linestyle, linewidth, marker, markersize, ieColor, subPlot)
velocityXPlot, subPlot = setupSubPlots('$V_x$', velocityXLowLim, velocityXHighLim, positions, velocityXData, linestyle, linewidth, marker, markersize, velocityColor, subPlot)
velocityYPlot, subPlot = setupSubPlots('$V_y$', velocityYLowLim, velocityYHighLim, positions, velocityYData, linestyle, linewidth, marker, markersize, velocityColor, subPlot)
velocityZPlot, subPlot = setupSubPlots('$V_z$', velocityZLowLim, velocityZHighLim, positions, velocityZData, linestyle, linewidth, marker, markersize, velocityColor, subPlot)
magneticXPlot, subPlot = setupSubPlots('$B_x$', magneticXLowLim, magneticXHighLim, positions, magneticXData, linestyle, linewidth, marker, markersize, magneticColor, subPlot)
magneticYPlot, subPlot = setupSubPlots('$B_y$', magneticYLowLim, magneticZHighLim, positions, magneticYData, linestyle, linewidth, marker, markersize, magneticColor, subPlot)
magneticZPlot, subPlot = setupSubPlots('$B_z$', magneticZLowLim, magneticZHighLim, positions, magneticZData, linestyle, linewidth, marker, markersize, magneticColor, subPlot)
# Layout
plt.tight_layout()
fig.subplots_adjust(top=0.88)
# Generate animation
simulation = animation.FuncAnimation(fig,
newFrame,
fargs = (fig, supTitleText, densityPlot, pressurePlot, iePlot,
velocityXPlot, velocityYPlot, velocityZPlot,
magneticXPlot, magneticYPlot, magneticZPlot,
densityData, pressureData, ieData,
velocityXData, velocityYData, velocityZData,
magneticXData, magneticYData, magneticZData,
timeStepSamples, titleText),
blit = True,
frames = timeStepSamples,
interval = FrameTime,
repeat = False)
FFwriter = animation.FFMpegWriter(bitrate=1000,
fps=fps,
codec='libx264',
extra_args=['-crf','28','-preset','ultrafast','-pix_fmt','yuv420p'])
simulation.save(filename=OutFile, writer = FFwriter)
# simulation.save(filename=OutFile, fps=fps, dpi=dpi)
print(f"\n\nAnimation complete. Framerate: {fps} fps, Total Number of Frames: {timeStepSamples.size}")
# ==============================================================================
# ==============================================================================
def loadData(path, yPos, zPos):
numFiles = 870
dims = [32, 32, 32]
physicalSize = [1,1,1]
np.random.random((numFiles, dims[0]))
# Allocate Arrays
densityData = np.random.random((numFiles, dims[0]))
velocityXData = np.random.random((numFiles, dims[0]))
velocityYData = np.random.random((numFiles, dims[0]))
velocityZData = np.random.random((numFiles, dims[0]))
pressureData = np.random.random((numFiles, dims[0]))
ieData = np.random.random((numFiles, dims[0]))
magneticXData = np.random.random((numFiles, dims[0]))
magneticYData = np.random.random((numFiles, dims[0]))
magneticZData = np.random.random((numFiles, dims[0]))
timeStepNum = np.arange(0, numFiles, 1)
positions = np.linspace(0., 1, dims[0])
return (densityData, velocityXData, velocityYData, velocityZData,
pressureData, ieData, magneticXData, magneticYData, magneticZData,
positions, timeStepNum, dims, physicalSize)
# ==============================================================================
# ==============================================================================
def setupSubPlots(fieldName, lowLim, highLim, positions, data, linestyle, linewidth, marker, markersize, color, axsArray):
# Get the subplot coordinates to set
if fieldName == 'Density':
a, b = (0,0)
elif fieldName == 'Pressure':
a, b = (0,1)
elif fieldName == 'Internal Energy':
a, b = (0,2)
elif fieldName == '$V_x$':
a, b = (1,0)
elif fieldName == '$V_y$':
a, b = (1,1)
elif fieldName == '$V_z$':
a, b = (1,2)
elif fieldName == '$B_x$':
a, b = (2,0)
elif fieldName == '$B_y$':
a, b = (2,1)
elif fieldName == '$B_z$':
a, b = (2,2)
else:
raise ValueError('setSubPlots received invalid fieldName')
# Set plot parameters
axsArray[a,b].set_ylim(lowLim, highLim)
axsArray[a,b].set_ylabel(fieldName)
axsArray[a,b].minorticks_on()
axsArray[a,b].grid(which = "both")
# Set initial values
returnPlot, = axsArray[a,b].plot(positions,
data[0,:],
linestyle = linestyle,
linewidth = linewidth,
marker = marker,
markersize = markersize,
color = color,
label = fieldName,
animated = True)
return returnPlot, axsArray
# ==============================================================================
# ==============================================================================
def computeLimit(dataSet, padPercent):
pad = np.max(np.abs([dataSet.min(), dataSet.max()])) * padPercent
if pad == 0:
# if the dataset doesn't exist or is zero return reasonable limits
return -1, 1
else:
lowLim = dataSet.min() - pad
highLim = dataSet.max() + pad
return lowLim, highLim
# ==============================================================================
# ==============================================================================
def newFrame(idx, fig, supTitleText, densityPlot, pressurePlot, iePlot,
velocityXPlot, velocityYPlot, velocityZPlot,
magneticXPlot, magneticYPlot, magneticZPlot,
densityData, pressureData, ieData,
velocityXData, velocityYData, velocityZData,
magneticXData, magneticYData, magneticZData,
timeStepSamples, titleText):
titleText.set_text(f"{supTitleText} \n Time Step: {idx}")
densityPlot .set_ydata(densityData[idx,:])
pressurePlot .set_ydata(pressureData[idx,:])
iePlot .set_ydata(ieData[idx,:])
velocityXPlot.set_ydata(velocityXData[idx,:])
velocityYPlot.set_ydata(velocityYData[idx,:])
velocityZPlot.set_ydata(velocityZData[idx,:])
magneticXPlot.set_ydata(magneticXData[idx,:])
magneticYPlot.set_ydata(magneticYData[idx,:])
magneticZPlot.set_ydata(magneticZData[idx,:])
# Report progress
if not hasattr(newFrame, "counter"):
newFrame.counter = -1 # Accounts for first call which is performed before the animation starts
print()
if newFrame.counter >= 0:
print(f'Animation is {100*(newFrame.counter/timeStepSamples.shape[0]):.1f}% complete', end='\r')
newFrame.counter += 1
# The return is required to make blit work
return (titleText, densityPlot, pressurePlot, iePlot,
velocityXPlot, velocityYPlot, velocityZPlot,
magneticXPlot, magneticYPlot, magneticZPlot)
# ==============================================================================
main()
print(f'Time to execute: {round(default_timer()-start,2)} seconds')
I'm working on a fairly simple matplotlib animation using animation.FuncAnimation
and it takes a very long time to run; about 0.81s per frame and 180s for the 220 frames in an 11 second long video. I've already incorporated the standard performance improvements, namely not creating Artist objects in the animation loop and returning all updated Artists objects so that bliting can be done but neither has had a significant impact on performance. I've timed the contents of the newFrame
function and it only takes about 0.6ms to run. My code is below.
Any other suggestions how how to speed this up or parallelize it?
EDIT: It looks like the performance issues are largely to do with the subplots. Reducing the number of subplots reduces the execution time dramatically. Not really a great solution but noteworthy
EDIT 2: Commenting out axsArray[a,b].minorticks_on()
doubles the performance to ~0.4s per frame. This brings performance more in line with what I expect based on previous experience with matplotlib animations, though still incredibly slow for a simple plot.
#!/usr/bin/env python3
from timeit import default_timer
import matplotlib
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
import os
import sys
matplotlib.use("Agg")
plt.close('all')
start = default_timer()
# ==============================================================================
def main():
# ==========================================================================
# Settings
# ==========================================================================
# Check for CLI arguments
if len(sys.argv) == 2:
cliFPS = int(sys.argv[1])
elif len(sys.argv) > 2:
raise TypeError(f"Too many ({len(sys.argv)}) command line arguments given")
# Data settings
loadPath = ''
yPos = 16 # y position of the x line to load
zPos = 16 # z position of the x line to load
# Plot Settings
supTitleText = "Time Evolution of Initial Conditions"
densityColor = 'blue' # color of the density plot
velocityColor = 'purple' # color of the velocity plots
magneticColor = 'tab:orange' # color of the magnetic field plots
pressureColor = 'green' # color of the pressure plot
ieColor = 'red' # color of the specific internal energy plot
linestyle = '-' # The line style
linewidth = 0.5 # How wide to make the lines
marker = "." # Marker kind for points
markersize = 3 # Size of the marker
figSizeScale = 2. # Scaling factor for the figure size
figHeight = 4.8 * figSizeScale # height of the plot in inches, default is 4.8
figWidth = 7.0 * figSizeScale # width of the plot in inches, default is 6.4
padPercent = 0.05 # How many percent larger the limits should be than the data
# Video Settings
OutFile = "mvp.mp4" # Output filename
Duration = 10. # How long the video is in seconds
dpi = 150 # Dots per inch
index = 0 # Initialize index
initIndex = 0 # Index for init frames
fps = cliFPS if ("cliFPS" in locals()) else 20 # Framerate
FrameTime = (1./fps) * 1000 # Frametime in milliseconds
totFrames = int(fps * Duration) # Total number of frames (floor)
# ==========================================================================
# End settings
# ==========================================================================
# Load data
(densityData, velocityXData, velocityYData, velocityZData, pressureData,
ieData, magneticXData, magneticYData, magneticZData, positions,
timeStepNum, dims, physicalSize) = loadData(loadPath, yPos, zPos)
# Compute which time steps to plot
if timeStepNum.size >= totFrames:
floatSamples = np.arange(0, timeStepNum.size, timeStepNum.size/totFrames)
timeStepSamples = np.asarray(np.floor(floatSamples), dtype="int")
else: # if the number of simulation steps is less than the total number of frames
totFrames = timeStepNum.size
fps = np.ceil(totFrames/Duration)
FrameTime = (1./fps) * 1000
timeStepSamples = np.arange(0, timeStepNum.size, 1, dtype="int")
# Insert the initial second of the initial conditions
timeStepSamples = np.insert(timeStepSamples, 0, [0]*fps)
# Get the plot limits
densityLowLim, densityHighLim = computeLimit(densityData, padPercent)
ieLowLim, ieHighLim = computeLimit(ieData, padPercent)
pressureLowLim, pressureHighLim = computeLimit(pressureData, padPercent)
velocityXLowLim, velocityXHighLim = computeLimit(velocityXData, padPercent)
velocityYLowLim, velocityYHighLim = computeLimit(velocityYData, padPercent)
velocityZLowLim, velocityZHighLim = computeLimit(velocityXData, padPercent)
magneticXLowLim, magneticXHighLim = computeLimit(magneticXData, padPercent)
magneticYLowLim, magneticYHighLim = computeLimit(magneticYData, padPercent)
magneticZLowLim, magneticZHighLim = computeLimit(magneticZData, padPercent)
# Set up plots
# Create 9 subplots
fig, subPlot = plt.subplots(3, 3, figsize = (figWidth, figHeight))
# Super Title
titleText = fig.suptitle(supTitleText)
# Shared x-label
subPlot[2,0].set_xlabel("Position")
subPlot[2,1].set_xlabel("Position")
subPlot[2,2].set_xlabel("Position")
# Set values for the subplots
densityPlot, subPlot = setupSubPlots('Density', densityLowLim, densityHighLim, positions, densityData, linestyle, linewidth, marker, markersize, densityColor, subPlot)
pressurePlot, subPlot = setupSubPlots('Pressure', pressureLowLim, pressureHighLim, positions, pressureData, linestyle, linewidth, marker, markersize, pressureColor, subPlot)
iePlot, subPlot = setupSubPlots('Internal Energy', ieLowLim, ieHighLim, positions, ieData, linestyle, linewidth, marker, markersize, ieColor, subPlot)
velocityXPlot, subPlot = setupSubPlots('$V_x
, velocityXLowLim, velocityXHighLim, positions, velocityXData, linestyle, linewidth, marker, markersize, velocityColor, subPlot)
velocityYPlot, subPlot = setupSubPlots('$V_y
, velocityYLowLim, velocityYHighLim, positions, velocityYData, linestyle, linewidth, marker, markersize, velocityColor, subPlot)
velocityZPlot, subPlot = setupSubPlots('$V_z
, velocityZLowLim, velocityZHighLim, positions, velocityZData, linestyle, linewidth, marker, markersize, velocityColor, subPlot)
magneticXPlot, subPlot = setupSubPlots('$B_x
, magneticXLowLim, magneticXHighLim, positions, magneticXData, linestyle, linewidth, marker, markersize, magneticColor, subPlot)
magneticYPlot, subPlot = setupSubPlots('$B_y
, magneticYLowLim, magneticZHighLim, positions, magneticYData, linestyle, linewidth, marker, markersize, magneticColor, subPlot)
magneticZPlot, subPlot = setupSubPlots('$B_z
, magneticZLowLim, magneticZHighLim, positions, magneticZData, linestyle, linewidth, marker, markersize, magneticColor, subPlot)
# Layout
plt.tight_layout()
fig.subplots_adjust(top=0.88)
# Generate animation
simulation = animation.FuncAnimation(fig,
newFrame,
fargs = (fig, supTitleText, densityPlot, pressurePlot, iePlot,
velocityXPlot, velocityYPlot, velocityZPlot,
magneticXPlot, magneticYPlot, magneticZPlot,
densityData, pressureData, ieData,
velocityXData, velocityYData, velocityZData,
magneticXData, magneticYData, magneticZData,
timeStepSamples, titleText),
blit = True,
frames = timeStepSamples,
interval = FrameTime,
repeat = False)
FFwriter = animation.FFMpegWriter(bitrate=1000,
fps=fps,
codec='libx264',
extra_args=['-crf','28','-preset','ultrafast','-pix_fmt','yuv420p'])
simulation.save(filename=OutFile, writer = FFwriter)
# simulation.save(filename=OutFile, fps=fps, dpi=dpi)
print(f"\n\nAnimation complete. Framerate: {fps} fps, Total Number of Frames: {timeStepSamples.size}")
# ==============================================================================
# ==============================================================================
def loadData(path, yPos, zPos):
numFiles = 870
dims = [32, 32, 32]
physicalSize = [1,1,1]
np.random.random((numFiles, dims[0]))
# Allocate Arrays
densityData = np.random.random((numFiles, dims[0]))
velocityXData = np.random.random((numFiles, dims[0]))
velocityYData = np.random.random((numFiles, dims[0]))
velocityZData = np.random.random((numFiles, dims[0]))
pressureData = np.random.random((numFiles, dims[0]))
ieData = np.random.random((numFiles, dims[0]))
magneticXData = np.random.random((numFiles, dims[0]))
magneticYData = np.random.random((numFiles, dims[0]))
magneticZData = np.random.random((numFiles, dims[0]))
timeStepNum = np.arange(0, numFiles, 1)
positions = np.linspace(0., 1, dims[0])
return (densityData, velocityXData, velocityYData, velocityZData,
pressureData, ieData, magneticXData, magneticYData, magneticZData,
positions, timeStepNum, dims, physicalSize)
# ==============================================================================
# ==============================================================================
def setupSubPlots(fieldName, lowLim, highLim, positions, data, linestyle, linewidth, marker, markersize, color, axsArray):
# Get the subplot coordinates to set
if fieldName == 'Density':
a, b = (0,0)
elif fieldName == 'Pressure':
a, b = (0,1)
elif fieldName == 'Internal Energy':
a, b = (0,2)
elif fieldName == '$V_x
:
a, b = (1,0)
elif fieldName == '$V_y
:
a, b = (1,1)
elif fieldName == '$V_z
:
a, b = (1,2)
elif fieldName == '$B_x
:
a, b = (2,0)
elif fieldName == '$B_y
:
a, b = (2,1)
elif fieldName == '$B_z
:
a, b = (2,2)
else:
raise ValueError('setSubPlots received invalid fieldName')
# Set plot parameters
axsArray[a,b].set_ylim(lowLim, highLim)
axsArray[a,b].set_ylabel(fieldName)
axsArray[a,b].minorticks_on()
axsArray[a,b].grid(which = "both")
# Set initial values
returnPlot, = axsArray[a,b].plot(positions,
data[0,:],
linestyle = linestyle,
linewidth = linewidth,
marker = marker,
markersize = markersize,
color = color,
label = fieldName,
animated = True)
return returnPlot, axsArray
# ==============================================================================
# ==============================================================================
def computeLimit(dataSet, padPercent):
pad = np.max(np.abs([dataSet.min(), dataSet.max()])) * padPercent
if pad == 0:
# if the dataset doesn't exist or is zero return reasonable limits
return -1, 1
else:
lowLim = dataSet.min() - pad
highLim = dataSet.max() + pad
return lowLim, highLim
# ==============================================================================
# ==============================================================================
def newFrame(idx, fig, supTitleText, densityPlot, pressurePlot, iePlot,
velocityXPlot, velocityYPlot, velocityZPlot,
magneticXPlot, magneticYPlot, magneticZPlot,
densityData, pressureData, ieData,
velocityXData, velocityYData, velocityZData,
magneticXData, magneticYData, magneticZData,
timeStepSamples, titleText):
titleText.set_text(f"{supTitleText} \n Time Step: {idx}")
densityPlot .set_ydata(densityData[idx,:])
pressurePlot .set_ydata(pressureData[idx,:])
iePlot .set_ydata(ieData[idx,:])
velocityXPlot.set_ydata(velocityXData[idx,:])
velocityYPlot.set_ydata(velocityYData[idx,:])
velocityZPlot.set_ydata(velocityZData[idx,:])
magneticXPlot.set_ydata(magneticXData[idx,:])
magneticYPlot.set_ydata(magneticYData[idx,:])
magneticZPlot.set_ydata(magneticZData[idx,:])
# Report progress
if not hasattr(newFrame, "counter"):
newFrame.counter = -1 # Accounts for first call which is performed before the animation starts
print()
if newFrame.counter >= 0:
print(f'Animation is {100*(newFrame.counter/timeStepSamples.shape[0]):.1f}% complete', end='\r')
newFrame.counter += 1
# The return is required to make blit work
return (titleText, densityPlot, pressurePlot, iePlot,
velocityXPlot, velocityYPlot, velocityZPlot,
magneticXPlot, magneticYPlot, magneticZPlot)
# ==============================================================================
main()
print(f'Time to execute: {round(default_timer()-start,2)} seconds')
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论