如何在嵌入Qt环境的matplotlib中更快地绘制大量信号?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在嵌入Qt环境的matplotlib中更快地绘制大量信号?相关的知识,希望对你有一定的参考价值。

我正在尝试在嵌入Qt环境的matplotlib图中绘制大量信号。这些图根据QScrollBar进行了更新,它修改了我需要显示的信号部分。我的问题是更新图形需要花费相当长的时间,尤其是因为我要更新250个信号。因此,我正在寻找一种优化EEG_plot.update函数以减少其绘制时间的方法。我不知道如何使用动画功能来加快处理过程或其他操作。我担心的是,我需要更新时间轴刻度,可能还要更新y轴标签的位置。另一件事是,如果我需要绘制的最后一段与选择的窗口大小不完全一致,那么我只需要绘制一部分窗口(例如,最后一段将是5s,但是窗口大小是10s)

我在下面给出了整个脚本

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np


class Viewer(QMainWindow):
    def __init__(self, parent=None):
        super(Viewer, self).__init__()
        self.parent = parent
        #######################################
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.mainVBOX_param_scene = QVBoxLayout()
        self.mascene = plot(self)


        self.paramPlotV = QVBoxLayout()
        self.horizontalSliders  = QScrollBar(Qt.Horizontal)
        self.horizontalSliders.setFocusPolicy(Qt.StrongFocus)
        self.horizontalSliders.valueChanged.connect(self.update_plot)
        self.horizontalSliders.setMinimum(0)
        self.horizontalSliders.setMaximum(1)


        self.paramPlot = QHBoxLayout()
        l_gain = QLabel('Gain')
        self.e_gain = QLineEdit('5')
        l_win = QLabel('Window')
        self.e_win = QLineEdit('10')
        l_spacing = QLabel('vertical spacing')
        self.e_spacing = QLineEdit('10')
        l_linewidth = QLabel('linewidth')
        self.e_linewidth = QLineEdit('1')

        self.e_gain.returnPressed.connect(self.update_plot)
        self.e_win.returnPressed.connect(self.udpate_plot_plus_slider)
        self.e_spacing.returnPressed.connect(self.update_plot)
        self.e_linewidth.returnPressed.connect(self.update_plot)

        self.paramPlot.addWidget(l_gain)
        self.paramPlot.addWidget(self.e_gain)
        self.paramPlot.addWidget(l_win)
        self.paramPlot.addWidget(self.e_win)
        self.paramPlot.addWidget(l_spacing)
        self.paramPlot.addWidget(self.e_spacing)
        self.paramPlot.addWidget(l_linewidth)
        self.paramPlot.addWidget(self.e_linewidth)

        self.paramPlotV.addWidget(self.horizontalSliders)
        self.paramPlotV.addLayout(self.paramPlot)

        self.mainVBOX_param_scene.addWidget(self.mascene)
        self.mainVBOX_param_scene.addLayout(self.paramPlotV)

        self.centralWidget.setLayout(self.mainVBOX_param_scene)

        self.Fs = 1024
        self.Sigs_dict = np.random.rand(250,105*self.Fs)
        self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
        self.parent.processEvents()
        self.update()

    def updateslider(self):
        self.horizontalSliders.setMinimum(0)
        self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(self.e_win.text()))-1)
        self.horizontalSliders.setPageStep(1)
        self.horizontalSliders.update()

    def udpate_plot_plus_slider(self):
        self.updateslider()
        self.mascene.update()

    def update_plot(self):
        self.mascene.update()

    def update(self):
        self.updateslider()
        self.mascene.modify_sigs()
        self.mascene.update()

class plot(QGraphicsView):
    def __init__(self, parent=None):
        super(plot, self).__init__(parent)
        self.parent = parent
        self.scene = QGraphicsScene(self)
        self.setScene(self.scene)
        self.figure = plt.figure(facecolor='white')#Figure()
        self.canvas = FigureCanvas(self.figure)

        self.widget = QWidget()
        self.widget.setLayout(QVBoxLayout())
        self.widget.layout().setContentsMargins(0, 0, 0, 0)
        self.widget.layout().setSpacing(0)
        self.scroll = QScrollArea(self.widget)
        self.scroll.setWidget(self.canvas)

        layout = QVBoxLayout()
        layout.addWidget(self.scroll)
        self.setLayout(layout)

    def modify_sigs(self):
        self.Sigs_dict = self.parent.Sigs_dict
        self.t = self.parent.t
        self.Fs= self.parent.Fs


    def update(self):
        win_num = self.parent.horizontalSliders.value()
        self.figure.clear()
        plt.figure(self.figure.number)
        plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
        self.axes = plt.subplot(1, 1, 1)
        gain = float(self.parent.e_gain.text())
        win= float(self.parent.e_win.text())
        self.spacing = float(self.parent.e_spacing.text())
        linewidth = float(self.parent.e_linewidth.text())
        ts = int(win*(win_num) * self.Fs)
        te = ts + int(win * self.Fs)
        if te > len(self.t):
            te=len(self.t)
        for i in range(self.Sigs_dict.shape[0]):
            line, = plt.plot(self.t[ts:te], gain*(self.Sigs_dict[i,ts:te]-np.mean(self.Sigs_dict[i,ts:te]))+i*self.spacing, linewidth=linewidth  )


        self.axes.autoscale(enable=True, axis='both', tight=True)
        self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
        self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))

        self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
        self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])



        self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*self.spacing)
        self.canvas.draw_idle()


def main():
    app = QApplication(sys.argv)
    app.setStyle('Windows')
    ex = Viewer(app)
    ex.showMaximized()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()

更新

[我做了一个新的实现,在这里我尝试更新数据而不是每次都重新绘制所有图形(update_set_data函数),我没有绘制曲线的所有点(例如,如果点数> 10000点, ,我只使用其中的50%)我使用decimate = len(self.t[ts:te]) // 10000 + 1计算了抽取度,最后我在用户拖动滑块时不重新绘制图形。

当我使用旧版本时,我有时间更新图形:

time old: 4.148899078369141
time old: 4.117990255355835
time old: 4.152893781661987

使用新版本,我得到:

time new: 2.0400094985961914
time new: 2.0248610973358154
time new: 2.0305933952331543

我不得不说,我希望将时间减少50%以上。有人有想法进一步优化吗?

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import time

class Viewer(QMainWindow):
    def __init__(self, parent=None):
        super(Viewer, self).__init__()
        self.parent = parent
        #######################################
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.mainVBOX_param_scene = QVBoxLayout()
        self.mascene = plot(self)


        self.paramPlotV = QVBoxLayout()
        self.horizontalSliders  = QScrollBar(Qt.Horizontal)
        self.horizontalSliders.setFocusPolicy(Qt.StrongFocus)
        self.horizontalSliders.valueChanged.connect(self.sliderReleasedfun)
        self.horizontalSliders.sliderPressed.connect(self.sliderPressedfun)
        self.horizontalSliders.sliderMoved.connect(self.sliderMovedfun)
        self.horizontalSliders.sliderReleased.connect(self.sliderReleasedfun)
        self.horizontalSliders.setMinimum(0)
        self.horizontalSliders.setMaximum(1)


        self.paramPlot = QHBoxLayout()
        l_gain = QLabel('Gain')
        self.e_gain = QLineEdit('5')
        l_win = QLabel('Window')
        self.e_win = QLineEdit('10')
        l_spacing = QLabel('vertical spacing')
        self.e_spacing = QLineEdit('10')
        l_linewidth = QLabel('linewidth')
        self.e_linewidth = QLineEdit('1')

        self.e_gain.returnPressed.connect(self.update_plot)
        self.e_win.returnPressed.connect(self.udpate_plot_plus_slider)
        self.e_spacing.returnPressed.connect(self.update_plot)
        self.e_linewidth.returnPressed.connect(self.update_plot)

        self.paramPlot.addWidget(l_gain)
        self.paramPlot.addWidget(self.e_gain)
        self.paramPlot.addWidget(l_win)
        self.paramPlot.addWidget(self.e_win)
        self.paramPlot.addWidget(l_spacing)
        self.paramPlot.addWidget(self.e_spacing)
        self.paramPlot.addWidget(l_linewidth)
        self.paramPlot.addWidget(self.e_linewidth)

        self.paramPlotV.addWidget(self.horizontalSliders)
        self.paramPlotV.addLayout(self.paramPlot)

        self.mainVBOX_param_scene.addWidget(self.mascene)
        self.mainVBOX_param_scene.addLayout(self.paramPlotV)

        self.centralWidget.setLayout(self.mainVBOX_param_scene)

        self.Fs = 1024
        self.Sigs_dict = np.random.rand(250,105*self.Fs)
        self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
        self.parent.processEvents()
        self.update()

    def sliderPressedfun(self):
        self.horizontalSliders.valueChanged.disconnect()

    def sliderMovedfun(self,e):
        self.horizontalSliders.setValue(e)

    def sliderReleasedfun(self):
        self.horizontalSliders.valueChanged.connect(self.movesliderfun)
        self.movesliderfun()

    def movesliderfun(self):
        t0 = time.time()

        self.horizontalSliders.setEnabled(False)
        self.update_data()
        self.horizontalSliders.setEnabled(True)
        print('time new:', time.time()-t0)

    def updateslider(self):
        self.horizontalSliders.setMinimum(0)
        self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(self.e_win.text()))-1)
        self.horizontalSliders.setPageStep(1)
        self.horizontalSliders.update()

    def udpate_plot_plus_slider(self):
        self.updateslider()
        self.mascene.update()

    def update_plot(self):
        self.mascene.update()

    def update_data(self):
        self.mascene.update_set_data()

    def update(self):
        self.updateslider()
        self.mascene.modify_sigs()
        self.mascene.update()

class plot(QGraphicsView):
    def __init__(self, parent=None):
        super(plot, self).__init__(parent)
        self.parent = parent
        self.scene = QGraphicsScene(self)
        self.setScene(self.scene)
        self.figure = plt.figure(facecolor='white')#Figure()
        self.canvas = FigureCanvas(self.figure)

        self.widget = QWidget()
        self.widget.setLayout(QVBoxLayout())
        self.widget.layout().setContentsMargins(0, 0, 0, 0)
        self.widget.layout().setSpacing(0)
        self.scroll = QScrollArea(self.widget)
        self.scroll.setWidget(self.canvas)

        layout = QVBoxLayout()
        layout.addWidget(self.scroll)
        self.setLayout(layout)
        self.win=10

    def modify_sigs(self):
        self.Sigs_dict = self.parent.Sigs_dict
        self.t = self.parent.t
        self.Fs= self.parent.Fs

    def update_set_data(self):
        win_num = self.parent.horizontalSliders.value()
        gain = float(self.parent.e_gain.text())
        win= float(self.parent.e_win.text())
        if not self.spacing == float(self.parent.e_spacing.text()):
            self.spacing = float(self.parent.e_spacing.text())
            spacing = True
        else:
            spacing = False
        self.linewidth = float(self.parent.e_linewidth.text())

        ts = int(self.win * (win_num) * self.Fs)
        te = ts + int(self.win * self.Fs)
        if te > len(self.t):
            diff = te - len(self.t)
            ts = ts - diff
            te = len(self.t)

        decimate = len(self.t[ts:te]) // 10000 + 1

        for i in range(self.Sigs_dict.shape[0]):
            self.Lines[i].set_data(self.t[ts:te:decimate], gain*(self.Sigs_dict[i,ts:te:decimate]-np.mean(self.Sigs_dict[i,ts:te:decimate]))+i*self.spacing )
            self.Lines[i].set_linewidth(self.linewidth)

        if spacing:
            self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
            self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
            self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])



        self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))
        # self.canvas.draw_idle()
        self.canvas.draw()

    def update(self):
        win_num = self.parent.horizontalSliders.value()
        self.figure.clear()
        plt.figure(self.figure.number)
        plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
        self.axes = plt.subplot(1, 1, 1)
        gain = float(self.parent.e_gain.text())
        win= float(self.parent.e_win.text())
        self.spacing = float(self.parent.e_spacing.text())
        linewidth = float(self.parent.e_linewidth.text())
        ts = int(self.win * (win_num) * self.Fs)
        te = ts + int(self.win * self.Fs)
        if te > len(self.t):
            diff = te - len(self.t)
            ts = ts - diff
            te = len(self.t)
        decimate = len(self.t[ts:te]) // 10000 + 1
        self.Lines = []
        for i in range(self.Sigs_dict.shape[0]):
            line, = plt.plot(self.t[ts:te:decimate], gain*(self.Sigs_dict[i,ts:te:decimate]-np.mean(self.Sigs_dict[i,ts:te:decimate]))+i*self.spacing, linewidth=linewidth  )
            self.Lines.append(line)

        self.axes.autoscale(enable=True, axis='both', tight=True)
        self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
        self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))

        self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
        self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])



        self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*self.spacing)
        self.canvas.draw_idle()


def main():
    app = QApplication(sys.argv)
    app.setStyle('Windows')
    ex = Viewer(app)
    ex.showMaximized()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()
答案

您可以尝试使用多线程,这样您就可以将整个代码分成许多子代码并且您的所有代码将通过多线程同时运行

以上是关于如何在嵌入Qt环境的matplotlib中更快地绘制大量信号?的主要内容,如果未能解决你的问题,请参考以下文章

将 matplotlib 图表嵌入 Qt/C++ 应用程序

如何在 Windows 上使用 conda 安装 matplotlib 而不安装 Qt?

如何在 PyQt4 小部件中嵌入的 Axes3D (matplotlib) 中启用旋转?

导入 matplotlib.pyplot 时嵌入式 python 崩溃

Matplotlib NavigationToolbar2QT 仅显示禁用的左右箭头。如何显示缩放和平移按钮?

无法在 pyqt5 中嵌入的 matplotlib 上绘制线条