Как быстрее нарисовать большую часть сигнала в matplotlib, встроенном в среду Qt? - PullRequest
0 голосов
/ 18 октября 2019

Я пытаюсь отобразить большое число сигналов на рисунке matplotlib, который встроен в среду Qt. Графики обновляются в соответствии с QScrollBar, который изменяет часть сигналов, которые мне нужно показать. Моя проблема в том, что обновление рисунка занимает довольно много времени, особенно потому, что у меня есть 250 сигналов для обновления. Итак, я ищу способ оптимизировать функцию EEG_plot.update, чтобы сократить время ее отрисовки. Я не знаю, как я мог бы использовать функцию анимации, чтобы ускорить процесс или что-то еще. Меня беспокоит то, что мне нужно обновить отметки оси времени и, возможно, также положения меток оси y. Другое дело, что если последний сегмент, который мне нужно построить, не соответствует точно выбранному размеру окна, мне нужно построить только часть окна (например, последний сегмент будет 5 с, но размер окна равен 10 с)

Я даю весь сценарий прямо ниже

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()
...