目前主要实现效果:
窗体ui草稿:设计排版、图表插入、QWidget提升。
功能:设置计时器,随时间更新频域上的瀑布图。

QCustomPlot2是导师推荐的一款作图软件,相比之前用的matplotlib.pyplot更复杂一点,但是功能也更加多样化,据说作图速度也更快(未测试)。由于QCustomPlot2并不是PyQt5自带的,首先需要用pip安装一下。

安装前先要安装 pyqt5 和 pyqt5-tools(非常重要必须安装)

pip install pyqt5 pip install pyqt5-tools pip install qcustomplot2

目前效果展示:

python librosa瀑布图 python绘制频谱瀑布图_Layout


上面那张空白的图计划绘制成折线图。现在在点击单次调试后会在下图增加一行,点击开始采集后将自动以0.2s为间隔增加新数据。为测试图标功能性,现在暂时使用随机数据作为输入数据(所以瀑布图上看起来都是噪声)。

下面是源代码:

# mainWindow.py
import math
import sys
import time
from random import randint, random

import numpy as np

from matplotlib import pyplot as plt
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QDateTime, QObject, Qt, QThread, pyqtSignal, QTimer
from PyQt5.QtGui import QBrush, QColor, QPen
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QLineEdit,
                             QMainWindow, QVBoxLayout, QWidget)

from QCustomPlot2 import *

from ui.upchart import Ui_Form      # 使用QtDesigner将ui转为py文件,从外部导入作为界面


class MainWidow(QMainWindow, Ui_Form):
    def __init__(self):
        super(MainWidow, self).__init__()
        self.setupUi(self)          # setupUi方法写在Ui_Form类中

        self.pushButton.clicked.connect(self.waveplot)
        self.pushButton_2.clicked.connect(self.waterfall)

        self.timer = QTimer()       # 定义计时器
        self.timer.timeout.connect(self.waterfall)
        self.is_running = False
        self.log_value = []

    '''方法实现区'''

    def waveplot(self):
        if self.is_running:
            self.timer.stop()
            self.is_running = False
        else:
            self.timer.start(200)
            self.is_running = True

    def waterfall(self):
        customPlot = self.fallchart
        self.colorMap = QCPColorMap(customPlot.xAxis, customPlot.yAxis)
        self.colorMap.data().setSize(200, 50)
        self.colorMap.data().setRange(QCPRange(0, 1024), QCPRange(0, 100))

        self.log_value.insert(0, np.random.randint(
            0, 255, 200))   # 随即生成新的随机向量模拟新到来数据
        if len(self.log_value) > 50:
            # 只缓存最新的50条数据
            self.log_value.pop()

        for i in range(len(self.log_value)):
            row_vec = self.log_value[i]
            for j in range(row_vec.size):
                self.colorMap.data().setCell(
                    j, 49-i, row_vec[j])  # 根据生成的数据,逐个单元调整颜色深度
        self.colorMap.rescaleDataRange(True)
        customPlot.rescaleAxes()
        customPlot.replot()


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    form = MainWidow()
    form.show()
    sys.exit(app.exec_())

另外,ui文件就不上传了,直接上传经过pyuic5转为的py文件,源码如下:
(注意,其中两个用于绘图的QWidget窗口提升为了QCustomPlot2 中的QCustomPlot)

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from QCustomPlot2 import QCustomPlot
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(1283, 889)
        Form.setAutoFillBackground(True)
        self.label = QtWidgets.QLabel(Form)
        self.label.setEnabled(True)
        self.label.setGeometry(QtCore.QRect(460, 100, 496, 36))
        font = QtGui.QFont()
        font.setFamily("Consolas")
        font.setPointSize(18)
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.log = QtWidgets.QLabel(Form)
        self.log.setGeometry(QtCore.QRect(210, 60, 151, 151))
        self.log.setText("")
        self.log.setPixmap(QtGui.QPixmap("F:/图片/保存的图片/科大校徽1.jpg"))
        self.log.setScaledContents(True)
        self.log.setAlignment(QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter)
        self.log.setObjectName("log")
        self.layoutWidget = QtWidgets.QWidget(Form)
        self.layoutWidget.setGeometry(QtCore.QRect(460, 220, 511, 541))
        self.layoutWidget.setObjectName("layoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.linechart = QCustomPlot(self.layoutWidget)
        self.linechart.setObjectName("linechart")
        self.verticalLayout.addWidget(self.linechart)
        self.fallchart = QCustomPlot(self.layoutWidget)
        self.fallchart.setObjectName("fallchart")
        self.verticalLayout.addWidget(self.fallchart)
        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(230, 360, 93, 31))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(Form)
        self.pushButton_2.setGeometry(QtCore.QRect(230, 300, 93, 28))
        self.pushButton_2.setObjectName("pushButton_2")

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

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.label.setText(_translate(
            "Form", "Deep Learning Signal Classifier"))
        self.pushButton.setText(_translate("Form", "开始采集"))
        self.pushButton_2.setText(_translate("Form", "单次调试"))

总结:

其实上面那些都是我踩了不少坑以后写出来的。由于我一开始代码写得不规范,并不符合pyqt5和qtdesigner的设计逻辑,所以在修改代码,希望它能由静态瀑布图到动态瀑布图的过程中,踩了无数的坑。

按照pyqt5的设计逻辑,ui转为的py文件理论上不应该对其做任何修改就可以直接使用。
所有外部窗口的设计都应作为继承相应ui,在此基础上增加新的功能函数。
在设计槽和信号的时候,一定要注意传入方法、类和self的区别。

之后会继续完善功能,预计添加完整的绘图界面。

注:有同学安装后发现无法导入 QCustomPlot2。报错是

DLL load failed while importing QCustomPlot2。 这是因为没有导入PyQt5 包。必须在调用 QCustomPlot2 前调用 PyQt5。解决方法也很简单,先 import PyQt5 即可。

notebook 验证如下:

python librosa瀑布图 python绘制频谱瀑布图_python librosa瀑布图_02


原因是 QCustomPlot2 是完全依赖于 PyQt5。然而,在由 ui 生成的界面中 pyqt5导入在 QCustomPlot2 之后。说实话这个设计真糟糕。