目录
主要功能
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等,保存为
弹出框
创建一个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())
计算函数
多线程弹出子界面,同时开启子线程传入相关参数进行分块处理,此处略过分块代码,子线程定义信号_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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>tile</class>
<widget class="QMainWindow" name="tile">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>472</width>
<height>279</height>
</rect>
</property>
<property name="windowTitle">
<string>分块</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QListWidget" name="listWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>311</width>
<height>211</height>
</rect>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>330</x>
<y>130</y>
<width>54</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>分块大小:</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>390</x>
<y>210</y>
<width>71</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>分块</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit">
<property name="geometry">
<rect>
<x>390</x>
<y>130</y>
<width>71</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>256</string>
</property>
<property name="dragEnabled">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>256</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>330</x>
<y>160</y>
<width>54</width>
<height>12</height>
</rect>
</property>
<property name="text">
<string>输出路径:</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>54</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>影像列表:</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit_2">
<property name="geometry">
<rect>
<x>330</x>
<y>180</y>
<width>131</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<family>Adobe Devanagari</family>
<pointsize>9</pointsize>
</font>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>472</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menu">
<property name="title">
<string>文件</string>
</property>
<addaction name="actiondakai"/>
<addaction name="actionda"/>
</widget>
<addaction name="menu"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionda">
<property name="text">
<string>打开文件夹</string>
</property>
</action>
<action name="actiondakai">
<property name="checkable">
<bool>false</bool>
</property>
<property name="text">
<string>打开文件</string>
</property>
<property name="font">
<font>
<family>Adobe Devanagari</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="menuRole">
<enum>QAction::TextHeuristicRole</enum>
</property>
<property name="priority">
<enum>QAction::NormalPriority</enum>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
titleok.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>423</width>
<height>150</height>
</rect>
</property>
<property name="windowTitle">
<string>分块</string>
</property>
<widget class="QProgressBar" name="progressBar">
<property name="geometry">
<rect>
<x>120</x>
<y>30</y>
<width>261</width>
<height>23</height>
</rect>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>30</x>
<y>30</y>
<width>54</width>
<height>12</height>
</rect>
</property>
<property name="text">
<string>正在处理:</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>250</x>
<y>110</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>完成</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton_2">
<property name="geometry">
<rect>
<x>330</x>
<y>110</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>取消</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
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信息弹窗
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_())