我试图将一个用matplotlib funcAnimation制作的动画图放入PySide 6小部件中。它从串行端口获取数据,绘制它,并将其记录到CSV中。我的问题是,经过一段时间后,它开始明显变慢。虽然数据每秒都在进来,但大约20分钟后,它开始每2秒更新一次。
我试着改变间隔时间,虽然这确实使它持续更长一点,最终,它们最终还是会慢下来。我实现了一个检查执行时间的代码,并注意到它逐渐增加,直到超过一秒。然后我试图将其从小部件中取出,但结果相同。我还有一些其他的想法是使用blitting或将其转换为列表/dict而不是csv,但只是想在实现之前得到一些输入。我在reddit上看到一篇文章,其中blitting不起作用,他使用了csv,所以我不确定我以前的想法:[Reddit Post](https://www.reddit.com/r/AskProgramming/comments/owde4a/matplotlib_animation_exponnetially_slower_the/在这篇文章中,他们得到了一些建议,似乎对他们有用,但只有这一个适用于我的代码:为什么在animate()函数中每个轴都有ax.set_xlabel()、ax.grid等?这些应该在创建轴时设置一次,而不是在每次调用animate()时设置。所以我改变了它并将其放入设置中,这意味着我必须在animate循环中删除ax.cla()。现在,它不再只显示最后10个值,所以我也需要修复它。
我的代码:
`
import sys
from PySide6 import QtGui, QtCore
from PySide6.QtGui import QScreen, QPixmap
from pathlib import Path
from PySide6.QtWidgets import QWidget, QApplication, QPushButton, QVBoxLayout, QMainWindow, QHBoxLayout, QLabel
from matplotlib.backends.backend_qtagg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from PySide6.QtCore import Qt
from matplotlib.animation import FuncAnimation
import pandas as pd
import serial
import matplotlib.image as image
import csv
import subprocess
import time
import openpyxl
import cProfile
#Initialize Serial Port
ser = serial.Serial()
ser.baudrate = 28800
ser.port = 'COM3'
timeout = 1
parity = serial.PARITY_ODD
bytesize = serial.EIGHTBITS
ser.open()
#Image Widget
class Widget(QWidget):
def __init__(self):
super().__init__()
#Initialize value that will select one image if the value is reached and another if not
self.int4_value = 0
#Creating the pixmap
self.image_label = QLabel()
self.original_pixmap = QPixmap("C:/Users/mlee/Downloads/mid_green_background (1).png")
self.scaled_pixmap = self.original_pixmap.scaled(20, 20)
self.image_label.setPixmap(self.scaled_pixmap)
#Layout
v_layout = QVBoxLayout()
v_layout.addWidget(self.image_label, alignment=Qt.AlignTop)
self.setLayout(v_layout)
def update_int4_value(self, new_value):
#Updating value to change image
self.int4_value = new_value
if self.int4_value == 1365:
self.original_pixmap = QPixmap("C:/Users/mlee/Downloads/mid_green_background (1).png")
else:
self.original_pixmap = QPixmap("C:/Users/mlee/Downloads/solid_red_background (1).jpg")
self.scaled_pixmap = self.original_pixmap.scaled(20, 20)
self.image_label.setPixmap(self.scaled_pixmap)
class Window(QMainWindow):
def __init__(self):
#Counts the number of screenshots
self.screenshot_counter = self.load_screenshot_counter()
super().__init__()
#Import widget
self.widget1 = Widget()
self.app = app
self.setWindowTitle("Custom")
#Add menu bar
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("&File")
quit_action = file_menu.addAction("Quit")
quit_action.triggered.connect(self.quit)
save_menu = menu_bar.addMenu("&Save")
Screenshot = save_menu.addAction("Screenshot")
Screenshot.triggered.connect(self.screenshot)
#Set up graph as widget
self._main = QWidget()
self.setCentralWidget(self._main)
layout = QHBoxLayout(self._main)
self.fig = Figure(figsize=(5, 3))
self.canvas = FigureCanvas(self.fig)
layout.addWidget(self.canvas, stretch=24)
layout.addWidget(self.widget1, stretch=1)
#Set up toolbar
self.addToolBar(NavigationToolbar(self.canvas, self))
#Creating csv file to store incoming data and adding headers
with open('csv_graph.csv', 'w', newline='') as csvfile:
fieldnames = ['Value1', 'Value2', 'Value3', 'Value4', 'Value5', 'Value6']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
#Call the loop
self.setup()
def setup(self):
#At 500ms interval, failed after 1400 frames
#At 250 ms interval, failed after 1600 frames
#At 1ms interval, failed after 1800 frames
#Create the subplot
self.ax = self.fig.add_subplot(111)
# Legend and labels
self.ax.legend(loc='upper left')
self.ax.set_xlabel('Time(seconds)')
self.ax.set_ylabel('Value')
self.ax.set_title('Random Data')
#Animation function
self.ani = FuncAnimation(self.canvas.figure, self.animate, interval=1, cache_frame_data=False)
def animate(self, i):
#Timer to check execution time
start_time = time.time()
#Reading serial port data and only going htrough the loop if length is larger than 2
# since the first input is normally all 0
bytestoread = ser.inWaiting()
new_value = ser.read(bytestoread)
if len(new_value) >= 2:
#Turning all the bytes into ints
byte1 = new_value[2:4] # Extract the first byte
int1 = int.from_bytes(byte1, byteorder='little')
print(int1)
byte2 = new_value[4:6] # Extract the second byte
int2 = int.from_bytes(byte2, byteorder='little')
print(int2)
byte3 = new_value[6:8] # Extract the third byte
int3 = int.from_bytes(byte3, byteorder='little')
print(int3)
byte4 = new_value[8:10]
int4 = int.from_bytes(byte4, byteorder='little')
print(int4)
#Pass int4 to the image widget
self.widget1.update_int4_value(int4)
byte5 = new_value[10:12] # Day & Hour
int5 = int.from_bytes(byte5, byteorder='little')
# print(int5)
byte6 = new_value[12:14] # Minutes & Seconds
int6 = int.from_bytes(byte6, byteorder='little')
print(int6)
#Write the data into the csv
with open('csv_graph.csv', 'a', newline='') as csvfile:
csv_writer = csv.writer(csvfile)
csv_writer.writerow([int1, int2, int3, int4, int5, int6])
#Read from that csv and then take the last 10 rows
data = pd.read_csv('csv_graph.csv')
last_x_rows = data.tail(10)
#Assigning the values to variables
x = last_x_rows['Value6']
y1 = last_x_rows['Value1']
y2 = last_x_rows['Value2']
y3 = last_x_rows['Value3']
y4 = last_x_rows['Value4']
# Plotting
# self.ax.cla()
self.ax.plot(x, y1, color='Red', label='Red')
self.ax.plot(x, y2, color='Blue', label='Blue')
self.ax.plot(x, y3, color='Purple', label='Purple')
self.ax.plot(x, y4, color='Green', label='Green')
#Opening the csv in an excel
with pd.ExcelWriter("C:/Users/mlee/Documents/Excel_CSV/New_Excel.xlsx", mode='a', engine='openpyxl',
if_sheet_exists='replace') as writer:
data.to_excel(writer, sheet_name='Sheet_1')
#Execution time check
end_time = time.time()
self.execution_time = end_time - start_time
print(f"Frame {i}: Execution Time = {self.execution_time:.2f} seconds")
#Was trying to use blitting but didn't do it right
return self.ax
#Functions for menu bar
def quit(self):
self.app.quit()
def load_screenshot_counter(self):
counter_file = Path("screenshot_counter.txt")
if counter_file.exists():
with counter_file.open("r") as file:
return int(file.read())
else:
return 1
def save_screenshot_counter(self):
counter_file = Path("C:/Users/mlee/Downloads/screenshot_counter.txt")
with counter_file.open("w") as file:
file.write(str(self.screenshot_counter))
def screenshot(self):
# Get the primary screen
primary_screen = QApplication.primaryScreen()
# Get the available size of the screen (excluding reserved areas)
available_size = primary_screen.availableSize()
print(available_size)
shot = QScreen.grabWindow(QApplication.primaryScreen(), 0, 90, 95, 1410, 700)
file_path = f"C:/Users/mlee/Downloads/Screenshot_{self.screenshot_counter}.png"
shot.save(file_path, "PNG")
self.screenshot_counter += 1
self.save_screenshot_counter()
app = QApplication(sys.argv)
widget = Window()
widget.show()
with cProfile.Profile() as pr:
sys.exit(app.exec())
pr.print_stats(sort='cumtime')
`
字符串
1条答案
按热度按时间5cnsuln71#
首先,你正在阅读你的动画函数中的**. csv文件,这并不是很好,因为文件每次存储数据时都会变大,因此当它读取数据时会花费很多时间,并且每帧重新绘制所有数据会效率低下。相反,更新现有的绘图数据通常更有效。首先解决从. csv阅读数据的问题,您可以使用名为deque的"collections"**模块更有效地存储数据. Deque具有快速追加功能,因此当您不断添加新数据点并且只需要维护固定数量的最新数据点时,具有maxlen设置的deque会在添加新数据点时自动丢弃最旧的数据项。这种方法非常适合您的情况。
字符串
然后你可以从deque中为最后10个数据点创建缓冲区
型
如果你要把信息写在**. csv**里面,那么我建议你把数据成批地写在每一帧上。
型
然后在animate函数中你可以做一个追加,这样你就可以存储数据
型
然后你可以做一个if语句来检查大小
型
然后你就更新了情节
型
然后你可以在if语句中调用一个函数来保存csv中的数据。
型
这是我要做的第一步,更不用说你在Excel中编写的每一个框架都很昂贵,所以我会根据需要或不太频繁地这样做。