简体   繁体   中英

issues in displaying matplotlib animated plot in pyqt5 gui and save in mp4 at the same time

Goal: display and save plot in mp4 at the same time

I am able to save the video but when it finishes saving, it starts to override canvases which leads to crashes and doesnt show the plot in gui. I imagine i have to use some sort of threads to run animation but i am stuck how to do that. I can also imagine a lot flaws in my code.

main.py

import matplotlib.animation as animation
from matplotlib import style
import csv
import os.path
from os import path

import matplotlib.animation as animation

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import datetime
import serial.tools.list_ports
from pyqtgraph import PlotWidget, plot
import pyqtgraph as pg
import random
from numpy import diff
import numpy as np
LARGE_FONT = ("Verdana", 12)
import pandas as pd
style.use('fivethirtyeight')


import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QPushButton, QLabel, QDialog, QVBoxLayout, QApplication, QLineEdit)
from PyQt5.QtWidgets import (QPushButton, QDialog, QTreeWidget,
                             QTreeWidgetItem, QVBoxLayout,
                             QHBoxLayout, QFrame, QLabel, QComboBox,
                             QApplication, QTreeWidgetItemIterator, QMessageBox, QProxyStyle)
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys

import matplotlib
from matplotlib.figure import Figure
matplotlib.use('QT5Agg')
from matplotlib.backends.backend_qt5agg import  FigureCanvas
from matplotlib.backends.backend_qt5agg import  NavigationToolbar2QT as NavigationToolbar

plt.rcParams['animation.ffmpeg_path'] =  'C:/ffmpeg/bin/ffmpeg.exe'
from anim import Ui_MainWindow
plt.ion()

writer = animation.FFMpegWriter()


plt.show()
class Test(QtWidgets.QMainWindow):
    def __init__(self, parent = None):
        super().__init__(parent)
        QtWidgets.QMainWindow.__init__(self, parent)
        self.i = 0
        self.ui = Ui_MainWindow()
        self.ui.setupUi(parent)
        
        vlay1 = QVBoxLayout()
        self.plotWidget1 = FigureCanvas(Figure())
        toolbar1 = NavigationToolbar(self.plotWidget1, self)
        
        openButton = QtWidgets.QPushButton("Open a file")
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(openButton.sizePolicy().hasHeightForWidth())
        openButton.setSizePolicy(sizePolicy)
        dateTimeEdit = QtWidgets.QDateTimeEdit()
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(dateTimeEdit.sizePolicy().hasHeightForWidth())
        dateTimeEdit.setSizePolicy(sizePolicy)
        
        vlay1.addWidget(openButton)
        vlay1.addWidget(dateTimeEdit)
        vlay1.addWidget(toolbar1)
        vlay1.addWidget(self.plotWidget1)
        self.ui.gridLayout.addLayout(vlay1, 0, 0, 1, 1)
        openButton.clicked.connect(self.openFile)

        self.ax1 = self.plotWidget1.figure.subplots()
        self.line, = self.ax1.plot([], [], marker='o')
        self.ax1.set_xlabel("Real")
        self.ax1.set_ylabel("Imaginary")
        
        self.ax1.relim()
        self.ax1.autoscale_view(True, True, True)
        self.ax1.set_title(str(self.i))
        self.plotWidget1.figure.tight_layout()
        self.openFile()
        anim = animation.FuncAnimation(self.plotWidget1.figure, self.animate, init_func=self.init,
                               frames=self.len-1, interval=1, blit=True)
        anim.save('plot.mp4', writer=writer, dpi=300, )
        

    def readFile(self):
        df = pd.read_excel(self.fileName)
        self.Real1 = df["Real 1"]
        self.Real2 = df["Real 2"]
        self.Real3 = df["Real 3"]
        self.Real4 = df["Real 4"]
        self.Real5 = df["Real 5"]
        self.Img1 = df["Imaginary 1 "]
        self.Img2 = df["Imaginary 2"]
        self.Img3 = df["Imaginary 3"]
        self.Img4 = df["Imaginary 4"]
        self.Img5 = df["Imaginary 5"]
        
        self.len = len(self.Img1)
        

    def getDataX(self):
        self.i += 1
        if self.i == self.len - 1:
            print("complete")
        return [self.Real1[self.i], self.Real2[self.i], self.Real3[self.i], self.Real4[self.i], self.Real5[self.i]]

    def getDataY(self):
       
        return [abs(self.Img1[self.i]), abs(self.Img2[self.i]), abs(self.Img3[self.i]), abs(self.Img4[self.i]), abs(self.Img5[self.i])]
    
        
    def openFile(self):
        dirAndName = QtWidgets.QFileDialog.getOpenFileName(self,'Open a File', "*.xlsx")
        if dirAndName[0] != []:
            self.fileName = dirAndName[0]
            self.readFile()
        print(dirAndName)
        
    def init(self):
        self.line.set_data([], [])
        return self.line,

    def animate(self, i):
        x = self.getDataX()
        y = self.getDataY()
        
        x = np.asarray(x)
        y = np.asarray(y)
        
        self.ax1.set_title(str(self.i))
        self.line.set_data(x, y)
        self.ax1.relim()
        self.ax1.autoscale_view(True, True, True)
        return self.line,

def Home():
    f = QtWidgets.QMainWindow()
    
    #f.resize(1699, 980)
    c = Test(f)
    
    f.show()
    
    r = qApp.exec_()

if __name__ == "__main__":
    qApp = QtWidgets.QApplication(sys.argv)
    qApp.setStyle(QStyleFactory.create('Fusion'))
    proxy = QProxyStyle(qApp.style())
    qApp.setStyle(proxy)
    Home()

Anim.py

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth())
        self.pushButton.setSizePolicy(sizePolicy)
        self.pushButton.setObjectName("pushButton")
        self.verticalLayout.addWidget(self.pushButton)
        self.dateTimeEdit = QtWidgets.QDateTimeEdit(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.dateTimeEdit.sizePolicy().hasHeightForWidth())
        self.dateTimeEdit.setSizePolicy(sizePolicy)
        self.dateTimeEdit.setObjectName("dateTimeEdit")
        self.verticalLayout.addWidget(self.dateTimeEdit)
        self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "Open File"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

*** I am using Anim.py for other reasons, even though my main.py is creating qpushbutton and qtimeedit again***

Disclaimer: I am not going to use the OP's code as it has unnecessary elements that only distract as well as useless imports so I am going to show an example focused only on the required functionality.

If you want to save and show the GUI then you should not use the save() method as this is blocking, instead you should directly use the writer (FFMpegWriter), using the setup() method to configure and then the grab_frame() method to record the frames. It should be noted that when recording the speed of the animation decreases since recording a frame consumes time making the eventloop busy.

import sys

import numpy as np

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
from matplotlib import animation


class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.animation_button = QtWidgets.QPushButton("Start", checkable=True)

        self.canvas = FigureCanvas(Figure(figsize=(5, 3)))

        self.ax = self.canvas.figure.subplots()

        t = np.linspace(0, 10, 101)
        x, y = self.generate_data(0)
        (self._line,) = self.ax.plot(x, y)

        self.writer = animation.FFMpegWriter()
        self.writer.setup(self.canvas.figure, outfile="plot.mp4", dpi=300)

        self.anim = animation.FuncAnimation(
            self.canvas.figure,
            self.callback_animation,
            frames=200,
            interval=20,
            blit=True,
        )

        self.animation_button.toggled.connect(self.handle_toggled)

        central_widget = QtWidgets.QWidget()

        self.setCentralWidget(central_widget)
        layout = QtWidgets.QVBoxLayout(central_widget)
        layout.addWidget(self.canvas)
        layout.addWidget(self.animation_button)

    def callback_animation(self, i):
        x, y = self.generate_data(i)
        self._line.set_data(x, y)
        if self.animation_button.isChecked():
            self.writer.grab_frame()
        return (self._line,)

    def generate_data(self, i):
        x = np.linspace(0, 2, 1000)
        y = np.sin(2 * np.pi * (x - 0.01 * i))

        return x, y

    def handle_toggled(self):
        self.animation_button.setText(
            "Stop" if self.animation_button.isChecked() else "Start"
        )

    def closeEvent(self, event):
        super().closeEvent(event)
        self.writer.finish()


if __name__ == "__main__":
    qapp = QtWidgets.QApplication.instance()
    if not qapp:
        qapp = QtWidgets.QApplication(sys.argv)

    app = ApplicationWindow()
    app.show()
    app.activateWindow()
    app.raise_()
    qapp.exec_()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM