本科毕设被研导要求添加可视化界面展示结果功能以填补实验室的需求,同时需要做好来自MatLab与python的接口。由于实验室大量项目工作在演示时需要使用界面,常有同门前来问询,而此工作中有大量踩坑踩雷需要注意,空口说自然不如图文来的明白完整,故写此系列博客,以避免不必要的时间浪费。
文章并非专业的界面设计教程,唯一写作目的为帮助不希望透彻理解PyQt5的人能够制作界面以回应导师/领导等人的期待,满足教学/项目等需求,为避免误导,以入职软件公司为目的请勿借鉴本博客。
上一篇:实验室项目展示用PyQt系列(5)界面的整体控制架构、风格调整
1.如何使用“打开文件”与“打开文件夹”对话框
PyQt5自带了一个文件对话框类,能够对系统内单个或复数文件进行遍历并获得其文件路径,或者是这里仅介绍最为简单的一种方法。
self.fname, _ = QFileDialog.getOpenFileName(self, '打开文件', filter='.mat')
以上代码适用于获取单一文件的路径。其中fname将会被赋值为选中的文件所在绝对路径,函数的第二个参数为自定义的打开文件按钮名称,filter参数为过滤,上方代码过滤将仅显示文件夹与.mat后缀的文件。
self.root_path = QFileDialog.getExistingDirectory(self,"选择文件夹","E:\\MSTAR")
以上代码适用于获取某个文件夹的路径。root_path将会得到该文件夹的绝对路径,函数的第二个参数同样为自定义的打开文件夹按钮名称,第三个参数为默认的起始路径。
2.界面运行较为耗时的程序时,如何确保程序依然相应操作
本系列所介绍的为单线程的界面设计,故运行时间复杂度较大的程序时,界面可能会持续地无响应,影响体验。为了避免此时用户反复操作界面造成假死、闪退,除了如之前所述方法增强鲁棒性之外,还有必要使用PyQt所自带的函数以提高使用体验。
QApplication.processEvents()
这一函数的具体原理暂未能查到,但其确实能够提升界面运行高时间复杂度程序时的用户体验。具体做法即在于,在主程序内,对需要较长时间执行的行代码,上下都加上上述函数。
def ompscatter(self,fname):
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
self.progressBar.setValue(15)
self.zhuangtaixianshi.setText('正在执行特征提取')
QApplication.processEvents()
[len, wid, or_axis, oa_axis, processedsig, oX, olinel, oxy, oxx2, oyy2, oxx2_o, oyy2_o] = self.oss.ompscatterforsar(
str(fname), nargout=12)
QApplication.processEvents()
self.progressBar.setValue(80)
self.zhuangtaixianshi.setText('正在绘图')
Fig2 = MyFigure(width=100, height=100, dpi=100)
#Fig2.axes.set_title('稀疏重构图像')
Fig2.axes.imshow(processedsig)
Fig2.axes.set_xlabel('距离单元/个')
Fig2.axes.set_ylabel('距离单元/个')
self.gridlayout2 = QGridLayout(self.groupBox_2)
#time.sleep(2)
self.gridlayout2.addWidget(Fig2, 0, 1)
如上代码,由于函数self.oss.ompscatterforsar需要极长的时间完成运行,因此在其上下增加这一函数,确保运行这一函数时,界面依然能够相应操作。
需要注意的是,这一函数能够改善用户体验,但若用户过于心急,在函数未能运行完成就点击其他按钮,仍然可能导致报错,因此,仍有必要通过系列(5)所述或其他方法增加程序运行稳定性。
3.界面输出的图片更新频繁,QGridLayout可能会不响应更新
对于一些实时显示图片更新的程序,如果更新速度过快、过于频繁,可能会发生QGridLayout不刷新图片,一直卡在某一张的情况。出现这一问题的可能原因是,这一进程创建了一个plt的进程,或者是QGridLayout的显示属于新创建的另一进程,不属于界面主程序的进程之内,解决这一问题摸索到两种解决方法。
第一种方法时,在对需要更新的图片进行绘制的过程、界面更新的代码上下增加QApplication.processEvents()函数,代码示例如下。
QApplication.processEvents()#此处增加
#判别直方图
Fig5 = MyFigure(width=100, height=100, dpi=100)
temp_min = min(self.total_tgt_pred[self.graph_num])
temp_pred = self.total_tgt_pred[self.graph_num]
for i, j in enumerate(temp_pred):
temp_pred[i] = j - temp_min
temp_sum = sum(temp_pred)
for i, j in enumerate(temp_pred):
temp_pred[i] = j / temp_sum
plt.bar([i for i in range(len(temp_pred))], temp_pred)
plt.ylim(0, 1)
plt.xticks(range(3), self.labels, fontsize=24)
plt.savefig('./temp.png', bbox_inches='tight', dpi=100, pad_inches=0.0)
plt.close()
temp_img = mpimg.imread('./temp.png')
Fig5.axes.imshow(temp_img)
Fig5.axes.axis('off')
self.gridlayout5.removeWidget(Fig5)
self.gridlayout5.addWidget(Fig5, 0, 1)
QApplication.processEvents()#此处增加
第二种方法是,在第一种方法仍不能解决的情况下,增加延时函数。代码如下。
#展示原始SAR图片
Fig3 = MyFigure(width=100, height=100, dpi=100)
fig, ax = plt.subplots(1, 1)
temp_img = mpimg.imread(self.tgt_test_loader_test.dataset.imgs[self.graph_num][0])
ax.imshow(temp_img)
ax.axis('off')
ax.set_title(self.labels[self.tgt_test_loader_test.dataset.imgs[self.graph_num][1]],
fontsize='medium', y=0.91)
plt.savefig('./temp.png', bbox_inches='tight', dpi=100, pad_inches=0.0)
plt.close(fig=fig)
temp_img = mpimg.imread('./temp.png')
Fig3.axes.imshow(temp_img)
Fig3.axes.axis('off')
self.gridlayout3.removeWidget(Fig3)
self.gridlayout3.addWidget(Fig3, 0, 1)
time.sleep(5)#此处添加的延时五秒