【Python笔记】pyqt5进度条-多线程图像分块处理防止窗体卡顿

【Python笔记】pyqt5进度条-多线程图像分块处理防止窗体卡顿,第1张

目录

主要功能

环境配置

实现过程

1、设计ui

主界面

d出框

窗体文件

2、主体实现

打开文件

计算函数

代码附录

title.ui

titleok.ui

title.py

titleok.py

main多线程运算分块.py

主要功能

1、打开文件夹,读取tif文件列表,填入数据列表框

2、多线程对所有tif文件分块处理,防止阻塞窗体

3、更新进度条

环境配置

主要版本如下:

python 3.9.7

PyQt5 5.15.6

GDAL 3.4.1

numpy 1.22.3

pandas 1.4.2

较为详细的环境配置:【Python笔记】pyqt5窗体程序制作流程

实现过程

 以下主要叙述流程和关键技术点,设计部分具体过程参照:【Python笔记】pyqt5窗体程序制作流程

1、设计ui 主界面

创建一个QMainWindow,添加组件 标签QLabel、文本框LineList、数据列表框QListWeight、菜单QMenu、按钮QPushButton等,保存为

d出框

创建一个Dialog,添加组件 进度条QProgressBar

窗体文件

分别保存title.ui、titleok.ui文件,使用拓展工具转化为对应的py文件 ,完整ui、py文件代码见代码附录

2、主体实现

打开文件

设置全局变量-字典self.maindata={},打开文件夹,将文件夹路径赋给全局字典,同时获取文件夹内的所有后缀为.tif文件列表,加入数据列表框,给数据列表框添加点击事件itemClick

    def wenjianjia(self):
        if not os.path.exists(r"E:\pyimg\tif2csv"):
            self.maindata["tifDir"] = QFileDialog.getExistingDirectory(None, "选取文件夹", "C:/")  # 返回选中的文件夹路径
        else:
            self.maindata["tifDir"] = QFileDialog.getExistingDirectory(None, "选取文件夹", "E:\pyimg\tif2csv")  # 返回选中的文件夹路径
        # self.maindata["tifDir"]=QFileDialog.getExistingDirectory(None,"选取文件夹","E:/")  # 返回选中的文件夹路径
        print(self.maindata)
        self.maindata["tifs"] = [i for i in os.listdir(self.maindata["tifDir"]) if i.endswith(".tif")]
        # 数据列表框添加内容
        for img in self.maindata["tifs"]:
            self.listWidget.addItem(str(img))

#数据列表框添加点击事件
    def itemClick(self, item):
        print(item.text() + " clicked!")
        QMessageBox.information(self, "ListWidget", "你选择了:" + item.text())

计算函数

多线程d出子界面,同时开启子线程传入相关参数进行分块处理,此处略过分块代码,子线程定义信号_signal传回数据更新进度条,进度条到达100时显示完成按钮,完整代码“main多线程运算分块.py”见代码附录

#计算函数
    def start_cacu(self):
        #子窗体
        self.child = childWindow()
        self.child.pushButton.setVisible(False)
        self.child.show()

        self.maindata["分块大小"]=self.lineEdit.text()
        # 创建线程
        self.thread = Runthread(self.maindata)
        # 连接信号
        self.thread._signal.connect(self.call_backlog)  # 进程连接回传到GUI的事件
        self.child.stop_thread.connect(self.thread.terminate)
        # 开始线程
        self.thread.start()

    # 将线程的参数传入进度条事件
    def call_backlog(self, msg):
        self.child.progressBar.setValue(int(msg))
        if(msg==100):
            self.child.pushButton.setVisible(True)
# 多线程
class Runthread(QtCore.QThread):
    #  通过类成员对象定义信号对象
    _signal = pyqtSignal(int)

    def __init__(self,main):
        super(Runthread, self).__init__()
        self.main=main
#计算函数
    def run(self):
        # 分块处理代码
        print(self.main)

代码附录 title.ui


 tile
 
  
   
    0
    0
    472
    279
   
  
  
   分块
  
  
   
    
     
      10
      20
      311
      211
     
    
    
     Qt::LeftToRight
    
   
   
    
     
      330
      130
      54
      20
     
    
    
     分块大小:
    
   
   
    
     
      390
      210
      71
      23
     
    
    
     分块
    
   
   
    
     
      390
      130
      71
      20
     
    
    
     256
    
    
     false
    
    
     256
    
   
   
    
     
      330
      160
      54
      12
     
    
    
     输出路径:
    
   
   
    
     
      10
      0
      54
      16
     
    
    
     影像列表:
    
   
   
    
     
      330
      180
      131
      20
     
    
    
     
      Adobe Devanagari
      9
     
    
   
  
  
   
    
     0
     0
     472
     23
    
   
   
    
     文件
    
    
    
   
   
  
  
  
   
    打开文件夹
   
  
  
   
    false
   
   
    打开文件
   
   
    
     Adobe Devanagari
     10
    
   
   
    QAction::TextHeuristicRole
   
   
    QAction::NormalPriority
   
  
 
 
 

titleok.ui


 Dialog
 
  
   
    0
    0
    423
    150
   
  
  
   分块
  
  
   
    
     120
     30
     261
     23
    
   
   
    0
   
  
  
   
    
     30
     30
     54
     12
    
   
   
    正在处理:
   
  
  
   
    
     250
     110
     75
     23
    
   
   
    完成
   
  
  
   
    
     330
     110
     75
     23
    
   
   
    取消
   
  
 
 
 

title.py
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'title.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 PyQt5 import QtCore, QtGui, QtWidgets


class Ui_tile(object):
    def setupUi(self, tile):
        tile.setObjectName("tile")
        tile.resize(472, 279)
        self.centralwidget = QtWidgets.QWidget(tile)
        self.centralwidget.setObjectName("centralwidget")
        self.listWidget = QtWidgets.QListWidget(self.centralwidget)
        self.listWidget.setGeometry(QtCore.QRect(10, 20, 311, 211))
        self.listWidget.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.listWidget.setObjectName("listWidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(330, 130, 54, 20))
        self.label.setObjectName("label")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(390, 210, 71, 23))
        self.pushButton.setObjectName("pushButton")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(390, 130, 71, 20))
        self.lineEdit.setDragEnabled(False)
        self.lineEdit.setObjectName("lineEdit")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(330, 160, 54, 12))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(10, 0, 54, 16))
        self.label_3.setObjectName("label_3")
        self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_2.setGeometry(QtCore.QRect(330, 180, 131, 20))
        font = QtGui.QFont()
        font.setFamily("Adobe Devanagari")
        font.setPointSize(9)
        self.lineEdit_2.setFont(font)
        self.lineEdit_2.setObjectName("lineEdit_2")
        tile.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(tile)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 472, 23))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        tile.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(tile)
        self.statusbar.setObjectName("statusbar")
        tile.setStatusBar(self.statusbar)
        self.actionda = QtWidgets.QAction(tile)
        self.actionda.setObjectName("actionda")
        self.actiondakai = QtWidgets.QAction(tile)
        self.actiondakai.setCheckable(False)
        font = QtGui.QFont()
        font.setFamily("Adobe Devanagari")
        font.setPointSize(10)
        self.actiondakai.setFont(font)
        self.actiondakai.setMenuRole(QtWidgets.QAction.TextHeuristicRole)
        self.actiondakai.setPriority(QtWidgets.QAction.NormalPriority)
        self.actiondakai.setObjectName("actiondakai")
        self.menu.addAction(self.actiondakai)
        self.menu.addAction(self.actionda)
        self.menubar.addAction(self.menu.menuAction())

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

    def retranslateUi(self, tile):
        _translate = QtCore.QCoreApplication.translate
        tile.setWindowTitle(_translate("tile", "分块"))
        self.label.setText(_translate("tile", "分块大小:"))
        self.pushButton.setText(_translate("tile", "分块"))
        self.lineEdit.setText(_translate("tile", "256"))
        self.lineEdit.setPlaceholderText(_translate("tile", "256"))
        self.label_2.setText(_translate("tile", "输出路径:"))
        self.label_3.setText(_translate("tile", "影像列表:"))
        self.menu.setTitle(_translate("tile", "文件"))
        self.actionda.setText(_translate("tile", "打开文件夹"))
        self.actiondakai.setText(_translate("tile", "打开文件"))
titleok.py
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'titleok.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 PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(423, 150)
        self.progressBar = QtWidgets.QProgressBar(Dialog)
        self.progressBar.setGeometry(QtCore.QRect(120, 30, 261, 23))
        self.progressBar.setProperty("value", 0)
        self.progressBar.setObjectName("progressBar")
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setGeometry(QtCore.QRect(30, 30, 54, 12))
        self.label.setObjectName("label")
        self.pushButton = QtWidgets.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(250, 110, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(Dialog)
        self.pushButton_2.setGeometry(QtCore.QRect(330, 110, 75, 23))
        self.pushButton_2.setObjectName("pushButton_2")

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

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "分块"))
        self.label.setText(_translate("Dialog", "正在处理:"))
        self.pushButton.setText(_translate("Dialog", "完成"))
        self.pushButton_2.setText(_translate("Dialog", "取消"))
main多线程运算分块.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QIcon
from title import *
from titleok import *
from osgeo import gdal
import os


# 多线程
class Runthread(QtCore.QThread):
    #  通过类成员对象定义信号对象
    _signal = pyqtSignal(int)

    def __init__(self,main):
        super(Runthread, self).__init__()
        self.main=main
#计算函数
    def run(self):
        # 分块影像所在文件夹,不能有中文
        print(self.main)
        tifDir = self.main["tifDir"]
        # 输出的文件夹,不能有中文,如果文件夹不存在则会被创建
        outPath =self.main["outPath"]
        if not os.path.exists(outPath):
            os.makedirs(outPath)

        tifs = [i for i in os.listdir(tifDir) if i.endswith(".tif")]

        print("有 %s 个tif文件" % len(tifs))
        print("tifs", tifs)
        datelist1 = []
        for i in tifs:
            datelist1.append(i[:-4])
        datelist = list(set(datelist1))
        datelist.sort(key=datelist1.index)
        print("有 %s 个日期" % len(datelist))
        print("datelist", datelist)
        # 定义切图的大小(矩形框)
        size = 256
        # len(datelist)

        for img in range(3):
            print("正在分割:", tifs[img])
            in_ds = gdal.Open(tifDir + "\" + tifs[img])  # 读取要切的原图

            width = in_ds.RasterXSize  # 获取数据宽度
            height = in_ds.RasterYSize  # 获取数据高度
            outbandsize = in_ds.RasterCount  # 获取数据波段数
            im_geotrans = in_ds.GetGeoTransform()  # 获取仿射矩阵信息
            im_proj = in_ds.GetProjection()  # 获取投影信息
            datatype = in_ds.GetRasterBand(1).DataType
            im_data = in_ds.ReadAsArray()  # 获取数据

            col_num = int(width / size)  # 宽度可以分成几块
            row_num = int(height / size)  # 高度可以分成几块
            if (width % size != 0):
                col_num += 1
            if (height % size != 0):
                row_num += 1

            # print("row_num:%d   col_num:%d" % (row_num, col_num))
            for i in range(row_num):  # 从高度下手!!! 可以分成几块!
                for j in range(col_num):
                    offset_x = i * size
                    offset_y = j * size
                    ## 从每个波段中切需要的矩形框内的数据(注意读取的矩形框不能超过原图大小)
                    b_ysize = min(width - offset_y, size)
                    b_xsize = min(height - offset_x, size)

                    # print("width:%d     height:%d    offset_x:%d    offset_y:%d     b_xsize:%d     b_ysize:%d" % (width, height, offset_x, offset_y, b_xsize, b_ysize))
                    # print(im_data.shape)

                    out_allband = im_data[:, offset_x:offset_x + b_xsize, offset_y:offset_y + b_ysize]
                    # print(out_allband.shape)

                    # 获取Tif的驱动,为创建切出来的图文件做准备
                    gtif_driver = gdal.GetDriverByName("GTiff")
                    file = outPath + "\" + tifs[img][:-4] + "-" + str(offset_x).zfill(10) + "-" + str(offset_y).zfill(
                        10) + ".tif"

                    # 创建切出来的要存的文件
                    out_ds = gtif_driver.Create(file, b_ysize, b_xsize, outbandsize, datatype)
                    # print("create new tif file succeed")

                    # 获取原图的原点坐标信息
                    ori_transform = in_ds.GetGeoTransform()
                    # if ori_transform:
                    # print(ori_transform)
                    # print("Origin = ({}, {})".format(ori_transform[0], ori_transform[3]))
                    # print("Pixel Size = ({}, {})".format(ori_transform[1], ori_transform[5]))

                    # 读取原图仿射变换参数值
                    top_left_x = ori_transform[0]  # 左上角x坐标
                    w_e_pixel_resolution = ori_transform[1]  # 东西方向像素分辨率
                    top_left_y = ori_transform[3]  # 左上角y坐标
                    n_s_pixel_resolution = ori_transform[5]  # 南北方向像素分辨率

                    # 根据反射变换参数计算新图的原点坐标
                    top_left_x = top_left_x + offset_y * w_e_pixel_resolution
                    top_left_y = top_left_y + offset_x * n_s_pixel_resolution

                    # 将计算后的值组装为一个元组,以方便设置
                    dst_transform = (
                        top_left_x, ori_transform[1], ori_transform[2], top_left_y, ori_transform[4], ori_transform[5])

                    # 设置裁剪出来图的原点坐标
                    out_ds.SetGeoTransform(dst_transform)

                    # 设置SRS属性(投影信息)
                    out_ds.SetProjection(in_ds.GetProjection())

                    # 写入目标文件
                    for ii in range(outbandsize):
                        out_ds.GetRasterBand(ii + 1).WriteArray(out_allband[ii])

                    # 将缓存写入磁盘
                    out_ds.FlushCache()
                    # print("FlushCache succeed")
                    del out_ds
                # print(i/row_num)
                # self.progressBar.setValue(int(100*(i+1)/row_num))
                # self._signal.emit(int(100*(i+1)/row_num))  # 注意这里与_signal = pyqtSignal(str)中的类型相同
                self._signal.emit(int(100 * (img/3+(i + 1) / (row_num*3))) ) # 注意这里与_signal = pyqtSignal(str)中的类型相同

class MyWindow(QtWidgets.QMainWindow, Ui_tile):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setupUi(self)
        self.maindata={}

        #绑定事件
        self.pushButton.clicked.connect(self.start_cacu)
        #打开文件夹
        self.menu.triggered[QAction].connect(self.wenjianjia)

        #选择文件夹
        act = QAction(self)  # 定义一个行为
        act.setIcon(QIcon('image/select.png'))  # 设置行为icon,
        act.triggered.connect(self.outdir)  # 绑定行为槽函数,这里槽函数为一个QMessageBox信息d窗
        self.lineEdit_2.addAction(act, QLineEdit.TrailingPosition)  # 将该行为添加到lineEdit最右端

        # 数据列表添加点击事件
        self.listWidget.itemClicked.connect(self.itemClick)

    def outdir(self):
        if not os.path.exists(r"E:\pyimg\tif2csv"):
            self.maindata["outPath"] = QFileDialog.getExistingDirectory(None, "输出文件夹", "C:/")  # 返回选中的文件夹路径
            # QFileDialog.getOpenFileName()  # 返回选中的文件路径
            # QFileDialog.getOpenFileNames()  # 返回选中的多个文件路径
            # QFileDialog.getSaveFileName()  # 存储文件
        else:
            self.maindata["outPath"] = QFileDialog.getExistingDirectory(None, "输出文件夹", "E:\pyimg\tif2csv")  # 返回选中的文件夹路径
        self.lineEdit_2.setText((QtCore.QCoreApplication.translate("tile", self.maindata["outPath"])))
        print(self.maindata)

    def wenjianjia(self):
        if not os.path.exists(r"E:\pyimg\tif2csv"):
            self.maindata["tifDir"] = QFileDialog.getExistingDirectory(None, "选取文件夹", "C:/")  # 返回选中的文件夹路径
        else:
            self.maindata["tifDir"] = QFileDialog.getExistingDirectory(None, "选取文件夹", "E:\pyimg\tif2csv")  # 返回选中的文件夹路径
        # self.maindata["tifDir"]=QFileDialog.getExistingDirectory(None,"选取文件夹","E:/")  # 返回选中的文件夹路径
        print(self.maindata)
        self.maindata["tifs"] = [i for i in os.listdir(self.maindata["tifDir"]) if i.endswith(".tif")]
        # 数据列表框添加内容
        for img in self.maindata["tifs"]:
            self.listWidget.addItem(str(img))


    #数据列表框添加点击事件
    def itemClick(self, item):
        print(item.text() + " clicked!")
        QMessageBox.information(self, "ListWidget", "你选择了:" + item.text())

    #计算函数
    def start_cacu(self):
        #子窗体
        self.child = childWindow()
        self.child.pushButton.setVisible(False)
        self.child.show()

        self.maindata["分块大小"]=self.lineEdit.text()
        # 创建线程
        self.thread = Runthread(self.maindata)
        # 连接信号
        self.thread._signal.connect(self.call_backlog)  # 进程连接回传到GUI的事件
        self.child.stop_thread.connect(self.thread.terminate)
        # 开始线程
        self.thread.start()

    # 将线程的参数传入进度条事件
    def call_backlog(self, msg):
        self.child.progressBar.setValue(int(msg))
        if(msg==100):
            self.child.pushButton.setVisible(True)

    # 重写closeEvent方法,关闭窗口时触发
    def closeEvent(self, QCloseEvent):
        reply = QtWidgets.QMessageBox.question(self, '分块程序', "是否要退出程序?",QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,QtWidgets.QMessageBox.No)
        if reply == QtWidgets.QMessageBox.Yes:
            QCloseEvent.accept()
        else:
            QCloseEvent.ignore()

class childWindow(QDialog,Ui_Dialog):
    stop_thread = pyqtSignal()  # 定义关闭子线程的信号
    def __init__(self):
        super(childWindow, self).__init__()
        self.setupUi(self)
        # self.pushButton.clicked.connect(self.btn1)
        self.pushButton.clicked.connect(self.accept)
        self.pushButton_2.clicked.connect(self.accept)

    #窗口关闭就关闭线程
    def closeEvent(self, event):
        self.stop_thread.emit()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    myWin = MyWindow()
    myWin.show()
    sys.exit(app.exec_())

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/920792.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存