matplotlib 动画图形逐渐变慢

evrscar2  于 2023-11-22  发布在  其他
关注(0)|答案(1)|浏览(113)

我试图将一个用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')
`

字符串

5cnsuln7

5cnsuln71#

首先,你正在阅读你的动画函数中的**. csv文件,这并不是很好,因为文件每次存储数据时都会变大,因此当它读取数据时会花费很多时间,并且每帧重新绘制所有数据会效率低下。相反,更新现有的绘图数据通常更有效。首先解决从. csv阅读数据的问题,您可以使用名为deque"collections"**模块更有效地存储数据. Deque具有快速追加功能,因此当您不断添加新数据点并且只需要维护固定数量的最新数据点时,具有maxlen设置的deque会在添加新数据点时自动丢弃最旧的数据项。这种方法非常适合您的情况。

from collections import deque

字符串
然后你可以从deque中为最后10个数据点创建缓冲区

self.data_buffer = deque(maxlen=10)  # Buffer to store the last 10 data points
    self.csv_data = []  # Make a list for storing the data


如果你要把信息写在**. csv**里面,那么我建议你把数据成批地写在每一帧上。

self.csv_batch_size = 50  # Adjust this accordingly


然后在animate函数中你可以做一个追加,这样你就可以存储数据

new_data = [int1, int2, int3, int4, int5, int6]
    self.data_buffer.append(new_data)
    self.csv_data.append(new_data)


然后你可以做一个if语句来检查大小

if len(self.csv_data) >= self.csv_batch_size:
            self.write_to_csv()
            self.csv_data = []


然后你就更新了情节

df = pd.DataFrame(list(self.data_buffer), columns=['Value1', 'Value2', 'Value3', 'Value4', 'Value5', 'Value6'])
    x = range(len(df))
    self.ax.clear()
    self.ax.plot(x, df['Value1'], color='Red')
    self.ax.plot(x, df['Value2'], color='Blue')


然后你可以在if语句中调用一个函数来保存csv中的数据。

def write_to_csv(self):
    with open('csv_graph.csv', 'a', newline='') as csvfile:
        csv_writer = csv.writer(csvfile)
        csv_writer.writerows(self.csv_data)


这是我要做的第一步,更不用说你在Excel中编写的每一个框架都很昂贵,所以我会根据需要或不太频繁地这样做。

相关问题