This commit is contained in:
copper 2022-05-03 13:16:39 +08:00
commit d5c57a2402
50 changed files with 2279 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.onnx filter=lfs diff=lfs merge=lfs -text

212
.gitignore vendored Normal file
View File

@ -0,0 +1,212 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
*_qmlcache.qrc
# custom
output*
.vscode/settings.json
package
rc.py
*.aux.xml
model/

12
ReadMe.md Normal file
View File

@ -0,0 +1,12 @@
# 变化检测
王铜
CVEO团队
# 环境
Python3 + PyQt + QGIS?
# 当前依赖
pyqt qgis gdal numpy
# 打包方式
comming soon

0
__init__.py Normal file
View File

341
actions/actions.py Normal file
View File

@ -0,0 +1,341 @@
import logging
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QAction, QActionGroup, QLabel, QFileDialog
from gui.about import AboutDialog
from gui.project import Create
from utils.project import Project
class ActionManager(QtCore.QObject):
def __init__(self,
double_map,
layer_tree,
follow_box,
result_box,
message_box,
parent=None):
super().__init__(parent)
self.w_parent = parent
self.actions = {}
self.action_groups = {}
self.action_group_actions = {}
self.double_map = double_map
self.layer_tree = layer_tree
self.follow_box = follow_box
self.result_box = result_box
self.message_box = message_box
self.allways_enable = []
self.init_enable = []
self.dataload_enable = []
self.toolbar = None
self.menubar = None
self.status_bar = None
def set_menus(self, menubar):
self.menubar = menubar
self.file_menu = menubar.addMenu('&文件')
# self.view_menu = menubar.addMenu('&视图')
self.basic_menu = menubar.addMenu('&基本工具')
self.preop_menu = menubar.addMenu('&预处理')
self.change_detection_menu = menubar.addMenu('&变化检测')
self.special_chagne_detec_menu = menubar.addMenu('&专题变化检测')
self.postop_menu = menubar.addMenu('&后处理')
self.eval_menu = menubar.addMenu('&评估')
self.help_menu = menubar.addMenu('&帮助')
def set_toolbar(self, toolbar):
self.toolbar = toolbar
self.toolbar.setIconSize(QtCore.QSize(24, 24))
def set_status_bar(self, status_bar):
self.status_bar = status_bar
def set_actions(self):
'''
File menu
'''
project_create = self.add_action(QAction('&工程创建', self.w_parent), 'File')
project_open = self.add_action(QAction('&打开工程', self.w_parent), 'File')
project_save = self.add_action(QAction('&保存工程', self.w_parent), 'File')
data_load = self.add_action(QAction('&数据加载', self.w_parent), 'File')
view_setting = self.add_action(QAction('&界面定制', self.w_parent), 'File')
exit_app = self.add_action(QAction('&退出', self.w_parent), 'File')
project_create.triggered.connect(self.project_create)
project_open.triggered.connect(self.project_open)
project_save.triggered.connect(self.project_save)
data_load.triggered.connect(self.data_load)
view_setting.triggered.connect(self.view_setting)
exit_app.triggered.connect(self.w_parent.close)
self.allways_enable.append(project_create, project_open, exit_app, view_setting)
self.init_enable.append(project_save, data_load)
self.file_menu.addAction(project_create)
self.file_menu.addAction(project_open)
self.file_menu.addAction(project_save)
self.file_menu.addAction(data_load)
self.file_menu.addAction(view_setting)
self.file_menu.addAction(exit_app)
if self.toolbar is not None:
self.toolbar.addAction(project_create)
self.toolbar.addAction(project_open)
self.toolbar.addAction(project_save)
'''
Basic menu
'''
grid_line = self.add_action(QAction('&网格线', self.w_parent), 'Basic')
grid_line.setCheckable(True)
grid_line.setChecked(True)
zomm_in = self.add_action(QAction('&放大', self.w_parent), 'Basic')
zomm_out = self.add_action(QAction('&缩小', self.w_parent), 'Basic')
pan = self.add_action(QAction('&漫游', self.w_parent), 'Basic')
locate = self.add_action(QAction('&定位', self.w_parent), 'Basic')
self.basic_menu.addAction(grid_line)
self.basic_menu.addAction(zomm_in)
self.basic_menu.addAction(zomm_out)
self.basic_menu.addAction(pan)
self.basic_menu.addAction(locate)
'''
Preop menu
'''
morphology_filter = self.add_action(QAction('&形态学滤波', self.w_parent), 'filter')
lee_filter = self.add_action(QAction('&Lee滤波', self.w_parent), 'filter')
auto_filter = self.add_action(QAction('&自适应滤波-自主', self.w_parent), 'filter')
auto_filter_no_params = self.add_action(QAction('自动滤波(无参自适应滤波)-自主', self.w_parent), 'filter')
double_filter = self.add_action(QAction('&双边滤波', self.w_parent), 'filter')
align_action = self.add_action(QAction('&配准', self.w_parent), 'align')
filter_action_group = self.get_action_group('filter')
filter_menu = self.preop_menu.addMenu('&滤波')
for action in filter_action_group.actions():
filter_menu.addAction(action)
# self.preop_menu.addActionGroup(filter_action_group)
self.preop_menu.addAction(align_action)
if self.toolbar is not None:
self.toolbar.addAction(morphology_filter)
self.toolbar.addAction(lee_filter)
self.toolbar.addAction(auto_filter)
self.toolbar.addAction(auto_filter_no_params)
self.toolbar.addAction(double_filter)
'''
Change detection menu
'''
diff_method = self.add_action(QAction('&差分法', self.w_parent), 'unsuper_change_detection')
log_diff = self.add_action(QAction('&对数差分法', self.w_parent), 'unsuper_change_detection')
lsts_ = self.add_action(QAction('&LSTS法', self.w_parent), 'unsuper_change_detection')
lhba = self.add_action(QAction('&LHBA法', self.w_parent), 'unsuper_change_detection')
aht = self.add_action(QAction('&AHT法', self.w_parent), 'unsuper_change_detection')
kpvd = self.add_action(QAction('&KPVD法', self.w_parent), 'unsuper_change_detection')
mohd = self.add_action(QAction('&MOHD法', self.w_parent), 'unsuper_change_detection')
sh = self.add_action(QAction('&SH法', self.w_parent), 'unsuper_change_detection')
cva = self.add_action(QAction('&CVA法', self.w_parent), 'unsuper_change_detection')
mls = self.add_action(QAction('&MLS法', self.w_parent), 'unsuper_change_detection')
pca_kmean = self.add_action(QAction('&PCA-KMean法', self.w_parent), 'unsuper_change_detection')
semi_fcm = self.add_action(QAction('&Semi-FCM法', self.w_parent), 'unsuper_change_detection')
mls_svm = self.add_action(QAction('&MLS-SVM法', self.w_parent), 'unsuper_change_detection')
cva_fcm = self.add_action(QAction('&CVA-FCM法', self.w_parent), 'unsuper_change_detection')
cva_emgmm = self.add_action(QAction('&CVA-EMGMM法', self.w_parent), 'unsuper_change_detection')
gwdm = self.add_action(QAction('&GWDM法', self.w_parent), 'unsuper_change_detection')
mrf = self.add_action(QAction('&MRF法', self.w_parent), 'super_change_detection')
mad = self.add_action(QAction('&MAD法', self.w_parent), 'super_change_detection')
irmad = self.add_action(QAction('&IRMAD法', self.w_parent), 'super_change_detection')
dcva = self.add_action(QAction('&DCVA法', self.w_parent), 'ai_change_detection')
dp_fcn = self.add_action(QAction('&DP-FCN法', self.w_parent), 'ai_change_detection')
rcnn = self.add_action(QAction('&RCNN法', self.w_parent), 'ai_change_detection')
if self.toolbar is not None:
self.toolbar.addAction(diff_method)
self.toolbar.addAction(log_diff)
self.toolbar.addAction(lsts_)
self.toolbar.addAction(lhba)
unsuper_change_detection = self.get_action_group('unsuper_change_detection')
super_change_detection = self.get_action_group('super_change_detection')
ai_change_detection = self.get_action_group('ai_change_detection')
unsuper_menu = self.change_detection_menu.addMenu('&非监督')
for action in unsuper_change_detection.actions():
unsuper_menu.addAction(action)
super_menu = self.change_detection_menu.addMenu('&监督')
for action in super_change_detection.actions():
super_menu.addAction(action)
ai_menu = self.change_detection_menu.addMenu('&AI')
for action in ai_change_detection.actions():
ai_menu.addAction(action)
# self.change_detection_menu.addActionGroup(super_change_detection)
# self.change_detection_menu.addActionGroup(ai_change_detection)
'''
Special change detection menu
'''
water_change = self.add_action(QAction('&水体变化', self.w_parent), 'special_change_detection')
vegetation_change = self.add_action(QAction('&植被变化', self.w_parent), 'special_change_detection')
build_change = self.add_action(QAction('&房屋变化', self.w_parent), 'special_change_detection')
self.special_chagne_detec_menu.addAction(water_change)
self.special_chagne_detec_menu.addAction(vegetation_change)
self.special_chagne_detec_menu.addAction(build_change)
'''
Postop menu
'''
slide_window = self.add_action(QAction('&滑动窗口法', self.w_parent), 'noise_reduction')
density = self.add_action(QAction('&密度法', self.w_parent), 'noise_reduction')
raster_export = self.add_action(QAction('&二值栅格数据导出', self.w_parent), 'export')
txt_pos_export = self.add_action(QAction('&兼容ArcMap的坐标Txt文件', self.w_parent), 'export')
render_export = self.add_action(QAction('&渲染图像导出', self.w_parent), 'export')
noise_reduction = self.get_action_group('noise_reduction')
export = self.get_action_group('export')
noise_reduction_menu = self.postop_menu.addMenu('&噪声抑制')
for action in noise_reduction.actions():
noise_reduction_menu.addAction(action)
export_menu = self.postop_menu.addMenu('&导出')
for action in export.actions():
export_menu.addAction(action)
# self.postop_menu.addActionGroup(noise_reduction)
# self.postop_menu.addActionGroup(export)
'''
Evaluation menu
'''
evaluation = self.add_action(QAction('&评估', self.w_parent), 'evaluation')
self.eval_menu.addAction(evaluation)
'''
Help menu
'''
about = self.add_action(QAction('&关于', self.w_parent), 'about')
about.triggered.connect(lambda : AboutDialog(self.w_parent).show())
self.help_menu.addAction(about)
self.message_box.info('Menu init finished')
self.message_box.info(self.actions.keys())
for group in self.action_groups.keys():
self.message_box.info('%s:' % (group))
for action in self.action_groups[group].actions():
action.setEnabled(False)
self.message_box.info('\t%s' % (action.text()))
'''
Enabled actions
'''
about.setEnabled(True)
project_create.setEnabled(True)
project_open.setEnabled(True)
Project().project_init.connect(self.project_init)
if self.status_bar is not None:
corr_widget = QLabel(self.status_bar)
# corr_widget.setLineWidth(200)
corr_widget.setFixedWidth(200)
self.status_bar.addWidget(corr_widget)
scale_widget = QLabel(self.status_bar)
scale_widget.setFixedWidth(200)
self.status_bar.addWidget(scale_widget)
self.double_map.corr_changed.connect(corr_widget.setText)
self.double_map.scale_changed.connect(scale_widget.setText)
def project_create(self):
project = Project()
if project.is_init:
project.save()
projec_create = Create(self.w_parent)
if(projec_create.exec_()):
project.setup(os.path.join(projec_create.file, projec_create.name + '.prj'))
project.is_init = True
project.cell_size = projec_create.cell_size
def project_init(self, state):
self.message_box.info('Project init')
for group in self.action_groups.keys():
# self.message_box.info('%s:' % (group))
for action in self.action_groups[group].actions():
action.setEnabled(state)
# self.message_box.info('\t%s' % (action.text()))
self.message_box.info('Project init finished')
def project_open(self):
if Project().is_init:
Project().save()
project_file = QFileDialog.getOpenFileName(self.w_parent, '打开工程', '.', '*.prj')
if project_file[0] != '':
Project().clear()
Project().setup(project_file[0])
def project_save(self):
if Project().is_init:
Project().save()
def data_load(self):
if Project().is_init:
Project().save()
def view_setting(self):
pass
def add_action(self, action, group=None):
if group is None:
self.actions[action.text()] = action
else:
if group not in self.action_groups:
self.action_groups[group] = QActionGroup(self.w_parent)
self.action_groups[group].setExclusive(True)
self.action_groups[group].addAction(action)
self.action_group_actions[group] = action
return action
def get_action(self, action_name, group_name=None):
if action_name in self.actions:
return self.actions[action_name]
else:
if group_name is None:
return None
else:
if group_name not in self.action_group_actions:
return None
else:
group = self.action_group_actions[group_name]
for action in group.actions():
if action.text() == action_name:
return action
return None
def get_action_group(self, group_name):
return self.action_groups[group_name]
def get_action_group_action(self, group_name):
return self.action_group_actions[group_name]
def get_action_group_actions(self, group_name):
return self.action_group_actions[group_name].actions()
def get_actions(self):
return self.actions

42
gui/about.py Normal file
View File

@ -0,0 +1,42 @@
from PyQt5.QtWidgets import QDialog, QApplication, QLabel, QTextEdit, QVBoxLayout
from PyQt5.QtCore import Qt
class AboutDialog(QDialog):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
self.setWindowTitle("About")
self.setFixedSize(800, 400)
self.label = QLabel("<h1>"+ QApplication.applicationName() + "</h1>")
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("font-size: 20px;")
self.label2 = QLabel("<h2>Version: " + QApplication.applicationVersion() + "</h2>")
self.label2.setAlignment(Qt.AlignCenter)
self.label2.setStyleSheet("font-size: 15px;")
self.label3 = QLabel("<h2>" + QApplication.organizationName() + "</h2>")
self.label3.setAlignment(Qt.AlignCenter)
self.label3.setStyleSheet("font-size: 15px;")
self.label4 = QLabel("<h3>Copyright (c) 2020</h3>")
self.label4.setAlignment(Qt.AlignCenter)
self.label4.setStyleSheet("font-size: 10px;")
self.text = QTextEdit()
self.text.setReadOnly(True)
self.text.setText('''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
''')
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.label2)
self.layout.addWidget(self.label3)
self.layout.addWidget(self.label4)
self.layout.addWidget(self.text)
self.setLayout(self.layout)

54
gui/layertree.py Normal file
View File

@ -0,0 +1,54 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt,QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import (QTreeView, QTreeWidgetItem, QAbstractItemView, QHeaderView, QStyleFactory)
from utils.project import PairLayer
class LayerTree(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.tree_view = QTreeView(self)
self.tree = QtWidgets.QTreeWidget(self)
self.tree.setColumnCount(1)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.right_menu_show)
self.root=QTreeWidgetItem(self.tree)
self.tree.setHeaderHidden(True)
# self.tree.setHeaderLabels(['图层'])
self.root.setText(0,'Root')
child1=QTreeWidgetItem()
child1.setText(0,'child1')
child1.setCheckState(0,Qt.Checked)
self.root.addChild(child1)
self.tree.expandAll()
self.tree.addTopLevelItem(self.root)
self.tree.clicked.connect(self.onClicked)
layout = QtWidgets.QGridLayout()
layout.addWidget(self.tree)
self.setLayout(layout)
self.setLayoutDirection(Qt.LeftToRight)
def onClicked(self,index):
print(index.row())
def add_layer(self, layer:PairLayer):
pass
def right_menu_show(self, position):
rightMenu = QtWidgets.QMenu(self)
# QAction = QtWidgets.QAction(self.menuBar1)
self.actionreboot = QtWidgets.QAction('zhangji')
self.actionreboot.setObjectName("actionreboot")
self.actionreboot.setText('aaa')
rightMenu.addAction(self.actionreboot)
rightMenu.exec_(self.mapToGlobal(position))

74
gui/license.py Normal file
View File

@ -0,0 +1,74 @@
import shutil
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtGui import QIcon
import os
from utils.license import LicenseHelper
class License(QtWidgets.QDialog):
def __init__(self, parent = None, flags = QtCore.Qt.WindowFlags() ) -> None:
super().__init__(parent, flags)
self.setWindowTitle("License")
self.setWindowIcon(QIcon(os.path.join("gui", "icons", "license.png")))
self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
self.setFixedSize(600, 400)
self.text = QtWidgets.QLineEdit()
self.text.setReadOnly(False)
self.label = QtWidgets.QLabel()
self.label.setText("License File Path: ")
self.setModal(True)
self.btn_open = QtWidgets.QPushButton("Open")
self.btn_open.clicked.connect(self.open_file)
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(self.label, 0, alignment=QtCore.Qt.AlignTop)
hlayout.addWidget(self.text, 0, alignment=QtCore.Qt.AlignTop)
hlayout.addWidget(self.btn_open, 0, alignment=QtCore.Qt.AlignTop)
self.btn_ok = QtWidgets.QPushButton("OK")
self.btn_ok.clicked.connect(self.ok_clicked)
hlayout2 = QtWidgets.QHBoxLayout()
hlayout2.addWidget(self.btn_ok, alignment = QtCore.Qt.AlignRight, stretch= 0)
vlayout = QtWidgets.QVBoxLayout()
infobox = QtWidgets.QTextEdit(self)
infobox.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
infobox.setPlainText("""
This program is NOT free software: you can NOT redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Copy the MAC address to the clipboard and send it to the developer.
MAC address: {}""".format(LicenseHelper().get_mac_address()))
vlayout.addLayout(hlayout)
vlayout.addWidget(infobox)
vlayout.addLayout(hlayout2)
self.setLayout(vlayout)
def open_file(self) -> None:
file_path = QtWidgets.QFileDialog.getOpenFileName(self, "Open File", "", "License Files (*.*)")
if file_path[0]:
self.text.setText(file_path[0])
# self.label.setText("License File Path: " + file_path[0])
def ok_clicked(self) -> None:
if self.text.text() == "":
QtWidgets.QMessageBox.warning(self, "Warning", "Please select a license file.")
else:
pth = self.text.text()
if not os.path.exists(pth):
QtWidgets.QMessageBox.warning(self, "Warning", "The selected file does not exist.")
else:
shutil.copy(pth, os.path.join("lic", "license.lic"))
self.accept()
self.close()

113
gui/mainwindow.py Normal file
View File

@ -0,0 +1,113 @@
import pdb
from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon
from PyQt5 import QtGui
from PyQtAds import QtAds
from actions.actions import ActionManager
from gui.layertree import LayerTree
from gui.mapcanvas import DoubleCanvas
from gui.messagebox import MessageBox
from gui.result import ResultTable
from utils import Settings
from utils.project import Project
class MainWindow(QMainWindow):
def __init__(self, parent=None, **kargs):
super().__init__(parent)
self.current_instance = kargs.get('current_instance', 0)
if self.current_instance > 0:
self.setWindowTitle(QApplication.applicationName() + ' ' + str(self.current_instance))
else:
self.setWindowTitle(QApplication.applicationName())
self.setWindowIcon(QIcon(":/icons/logo.svg"))
self.setAcceptDrops(True)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.set_toolbar()
self.set_pannels()
Project(self).connect(
self.double_map,
self.layer_tree,
self.message_box,
self.result_box)
self.action_manager = ActionManager(
self.double_map,
self.layer_tree,
self.follow_box,
self.result_box,
self.message_box, self)
self.action_manager.set_menus(self.menuBar())
self.action_manager.set_toolbar(self.toolbar)
self.action_manager.set_status_bar(self.statusBar())
self.action_manager.set_actions()
self.resize(*Settings.General().size)
def set_toolbar(self):
self.toolbar = self.addToolBar('Toolbar')
self.toolbar.setMovable(False)
self.toolbar.setFloatable(False)
self.toolbar.setIconSize(QSize(16, 16))
self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toolbar.setContextMenuPolicy(Qt.PreventContextMenu)
self.toolbar.setLayoutDirection(Qt.LeftToRight)
pass
def set_pannels(self):
self.dock_manager = QtAds.CDockManager(self)
self.dock_manager.setContextMenuPolicy(Qt.CustomContextMenu)
self.double_map = DoubleCanvas(self)
central_dock_widget = QtAds.CDockWidget(self.tr("Canvas"))
central_dock_widget.setWidget(self.double_map)
central_dock_area = self.dock_manager.setCentralWidget(central_dock_widget)
central_dock_area.setAllowedAreas(QtAds.DockWidgetArea.OuterDockAreas)
self.double_map.setContextMenuPolicy(Qt.CustomContextMenu)
self.layer_tree = LayerTree(self)
# self.layer_tree.setContextMenuPolicy(Qt.CustomContextMenu)
def set_docker_fixed(docker):
docker.setFeature(QtAds.ads.CDockWidget.DockWidgetFeature.DockWidgetClosable , False)
docker.setFeature(QtAds.ads.CDockWidget.DockWidgetFeature.DockWidgetMovable , False)
docker.setFeature(QtAds.ads.CDockWidget.DockWidgetFeature.DockWidgetFloatable , False)
self.layer_tree_dock = QtAds.CDockWidget(self.tr("图层树"), self)
self.layer_tree_dock.setWidget(self.layer_tree)
left_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, self.layer_tree_dock, central_dock_area)
self.left_arre = left_area
self.follow_dock = QtAds.CDockWidget(self.tr("流程"))
self.follow_box = QWidget(self)
self.follow_dock.setWidget(self.follow_box)
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.BottomDockWidgetArea, self.follow_dock, left_area)
self.result_dock = QtAds.CDockWidget(self.tr("结果"))
self.result_box = ResultTable(self)
self.result_dock.setWidget(self.result_box)
bottom_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.BottomDockWidgetArea, self.result_dock, central_dock_area)
self.message_dock = QtAds.CDockWidget(self.tr("消息"))
self.message_box = MessageBox(self, MessageBox.INFO)
self.message_dock.setWidget(self.message_box)
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.RightDockWidgetArea, self.message_dock, bottom_area)
# bottom_area.setCurrentDockWidget(self.result_dock)
self.bottom_area = bottom_area
set_docker_fixed(self.layer_tree_dock)
set_docker_fixed(self.follow_dock)
set_docker_fixed(self.result_dock)
set_docker_fixed(self.message_dock)
def closeEvent(self, event):
pass
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
Settings.General().size = (a0.size().width(), a0.size().height())
return super().resizeEvent(a0)

491
gui/mapcanvas.py Normal file
View File

@ -0,0 +1,491 @@
# from alg.utils import random_color
# from mul.mulgrubcut import GrabCut
import multiprocessing
# from alg.grubcut import grubcut
# from gui.layerselect import LayerSelect
# from gui.default import get_default_category_colors, get_default_category_keys
# from os import truncate
from pathlib import Path
from PyQt5.QtCore import QSettings, QUrl, pyqtSignal, Qt, QVariant
from PyQt5.QtWidgets import QMessageBox, QWidget, QHBoxLayout
from PyQt5.QtGui import QColor, QDragEnterEvent, QDropEvent
from qgis.core import QgsPointXY, QgsRasterLayer, QgsVectorLayer, QgsFeature, QgsGeometry, QgsCategorizedSymbolRenderer, QgsRendererCategory, QgsFillSymbol, QgsPalLayerSettings, QgsRuleBasedLabeling, QgsTextFormat
from qgis.gui import QgsMapCanvas
from qgis.core import QgsVectorLayerExporter, QgsVectorFileWriter, QgsProject, QgsField, QgsRasterFileWriter, QgsRasterPipe
import threading
import tempfile
import cv2
import os
class DoubleCanvas(QWidget):
corr_changed = pyqtSignal(str)
scale_changed = pyqtSignal(str)
def __init__(self, parent = None) -> None:
super().__init__(parent)
self.setAcceptDrops(False)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.mapcanva1 = CanvasWidget(self)
self.mapcanva2 = CanvasWidget(self)
self.mapcanva1.setCanvasColor(QColor(255, 255, 255))
self.mapcanva2.setCanvasColor(QColor(255, 255, 255))
self.mapcanva1.update_coordinates_text.connect(self.corr_changed)
self.mapcanva2.update_coordinates_text.connect(self.corr_changed)
self.mapcanva1.update_scale_text.connect(self.scale_changed)
self.mapcanva2.update_scale_text.connect(self.scale_changed)
layout = QHBoxLayout(self)
layout.addWidget(self.mapcanva1)
layout.addWidget(self.mapcanva2)
self.setLayout(layout)
class CanvasWidget(QgsMapCanvas):
update_coordinates_text = pyqtSignal(str)
update_scale_text = pyqtSignal(str)
def __init__(self, parent):
super().__init__(parent)
self.current_raster_layer = None
self.current_vector_layer = None
self.setCanvasColor(Qt.white)
self.enableAntiAliasing(True)
self.setAcceptDrops(False)
# coordinates updated
def coordinates2text(pt:QgsPointXY):
return self.update_coordinates_text.emit("X: {:.5f}, Y: {:.5f}".format(pt.x(), pt.y()))
self.xyCoordinates.connect(coordinates2text)
self.scaleChanged.connect(lambda _ : self.update_scale_text.emit("1 : {:.3f}".format(self.scale())))
self.total_f = 0
self.start_extract = False
self.label_pal = None
# self.result_layers = []
def dragEnterEvent(self, e:QDragEnterEvent) -> None:
'''
Can drag
'''
candidates = [".tif", ".tiff", ".jpg", ".jpeg", ".bmp", ".png"]
if e.mimeData().hasUrls():
if Path(e.mimeData().urls()[0].toLocalFile()).suffix in candidates:
e.accept()
return
e.ignore()
def dropEvent(self, e:QDropEvent) -> None:
'''
Drop image to the canvas
'''
url_path = e.mimeData().urls()[0]
image_path = QUrl(url_path).toLocalFile()
self.load_image(image_path)
def load_image(self, path) -> None:
if not Path(path).exists():
return
raster_layer = QgsRasterLayer(path, Path(path).name)
if not raster_layer.isValid():
print("栅格图层加载失败!")
raster_layer.file_path = path
# self.layers.insert(0, raster_layer)
# self.layers.insert(0, vector_layer)
# if self.current_raster_layer:
# del self.current_raster_layer
# if self.current_vector_layer:
# del self.current_vector_layer
QgsProject.instance().addMapLayer(raster_layer)
self.current_raster_layer = raster_layer
# self.current_vector_layer = vector_layer
self.setExtent(raster_layer.extent())
# self.setLayers([vector_layer, raster_layer])
self.zoomToFeatureExtent(raster_layer.extent())
self.have_current_image.emit(True)
def load_result_from_txt(self, path) -> None:
if not Path(path).exists():
return
# vector_layer = QgsVectorLayer("Polygon?field=category:string(20)&field=confidence:double", Path(path).name, "memory")
vector_layer = QgsVectorLayer("Polygon?field=category:string(20)&field=confidence:double&field=renderkey:string(32)&field=isman:boolean&field=isauto:boolean&field=label:string(64)", Path(path).name + ' outline', "memory")
if not vector_layer.isValid():
print("矢量图层加载失败!")
vector_layer.setLabelsEnabled(True)
lyr = QgsPalLayerSettings()
lyr.enabled = True
lyr.fieldName = 'label' # default in data sources
# lyr.textFont = self._TestFont
lyr.textNamedStyle = 'Medium'
text_format = QgsTextFormat()
text_format.color = QColor('#ffffff')
text_format.background().color = QColor('#000000')
text_format.buffer().setEnabled(True)
text_format.buffer().setSize(1)
text_format.buffer().setOpacity(0.5)
lyr.setFormat(text_format)
self.label_pal = lyr
root = QgsRuleBasedLabeling.Rule(QgsPalLayerSettings())
rule = QgsRuleBasedLabeling.Rule(lyr)
rule.setDescription('label')
root.appendChild(rule)
#Apply label configuration
rules = QgsRuleBasedLabeling(root)
vector_layer.setLabeling(rules)
vector_layer.triggerRepaint()
# lyr.writeToLayer(vector_layer)
vector_layer.setRenderer(self.__get_categorical_renderer("renderkey"))
QgsProject.instance().addMapLayer(vector_layer)
self.current_vector_layer = vector_layer
# provider = self.current_vector_layer.dataProvider()
# provider.truncate()
self.current_vector_layer.startEditing()
# objects = []
features = []
with open(path) as f:
for line in f.readlines():
item_data = line.split("\n")[0].split(" ")
if len(item_data) == 1 + 4 * 2:
cls_name = item_data[0]
item_data[2] = -1.0 * float(item_data[2])
item_data[4] = -1.0 * float(item_data[4])
item_data[6] = -1.0 * float(item_data[6])
item_data[8] = -1.0 * float(item_data[8])
wkt = "POLYGON (({} {}, {} {}, {} {}, {} {}))".format(*item_data[1:])
conf = 1.0
else:
cls_name = item_data[8]
# print(cls_name)
# print(cls_name[0])
# print(cls_name[0].isalpha())
if cls_name[0].isalpha():
item_data[1] = -1.0 * float(item_data[1])
item_data[3] = -1.0 * float(item_data[3])
item_data[5] = -1.0 * float(item_data[5])
item_data[7] = -1.0 * float(item_data[7])
conf = 1.0
wkt = "POLYGON (({} {}, {} {}, {} {}, {} {}))".format(*item_data[:8])
else:
cls_name = item_data[0]
conf = float(item_data[1])
item_data[3] = -1.0 * float(item_data[3])
item_data[5] = -1.0 * float(item_data[5])
item_data[7] = -1.0 * float(item_data[7])
item_data[9] = -1.0 * float(item_data[9])
wkt = "POLYGON (({} {}, {} {}, {} {}, {} {}))".format(*item_data[2:])
feat = QgsFeature(self.current_vector_layer.fields())
feat.setGeometry(QgsGeometry.fromWkt(wkt))
feat.setAttribute('category', cls_name)
feat.setAttribute('confidence', conf)
feat.setAttribute('renderkey', cls_name)
feat.setAttribute('isman', False)
feat.setAttribute('isauto', True)
feat.setAttribute('label', f'{ cls_name},{conf:.3f}')
features.append(feat)
# objects.append({
# "category": item_data[0],
# "confidence": item_data[1],
# "fid": feat.id()
# })
self.current_vector_layer.addFeatures(features)
self.current_vector_layer.commitChanges()
self.have_current_vector.emit(True)
self.layer_update()
def clear_vector(self):
if self.current_vector_layer is not None:
provider = self.current_vector_layer.dataProvider()
provider.truncate()
self.layer_update()
def change_current_vector_layer(self, vector_layer):
if self.current_vector_layer is not None:
self.current_vector_layer.removeSelection()
self.current_vector_layer = vector_layer
self.layer_update()
def layer_update(self):
if self.current_vector_layer is None:
self.object_updated.emit([])
return
self.current_vector_layer.updateExtents()
self.refresh()
objects = []
for feature in self.current_vector_layer.getFeatures():
objects.append({
"category": feature['category'],
"confidence": feature['confidence'],
"renderkey": feature['renderkey'],
'isman': feature['isman'],
'isauto': feature['isauto'],
"fid": feature.id()
})
self.object_updated.emit(objects)
def selectd_changed(self, items:list):
if len(items) == 0:
self.current_vector_layer.removeSelection()
else:
self.current_vector_layer.selectByIds(list(item['fid'] for item in items))
def item_change(self, items:list):
self.current_vector_layer.startEditing()
features = list(self.current_vector_layer.getFeatures())
for f in features:
has_f = False
for item in items:
if f.id() == item['fid']:
# f = QgsFeature(f)
has_f = True
f.setAttribute('category', item['category'])
f.setAttribute('confidence', item['confidence'])
f.setAttribute('renderkey', item['renderkey'])
f.setAttribute('isman', item['isman'])
f.setAttribute('isauto', item['isauto'])
self.current_vector_layer.updateFeature(f)
break
if has_f:
continue
self.current_vector_layer.deleteFeature(f.id())
self.current_vector_layer.commitChanges()
self.current_vector_layer.updateExtents()
# print(self.current_vector_layer.fields())
self.refresh()
def zoom_to_full_extent(self) -> None:
if self.current_raster_layer:
self.zoomToFeatureExtent(self.current_raster_layer.extent())
def __get_categorical_renderer(self, fieldname:str) -> QgsCategorizedSymbolRenderer:
settings = QSettings(self)
category_keys = settings.value("keys", get_default_category_keys())
category_colors = settings.value("colors", get_default_category_colors())
settings.beginGroup("Category")
if len(category_colors) < len(category_keys):
for _ in range(len(category_keys) - len(category_colors)):
category_colors.append(random_color())
settings.setValue('colors', category_colors)
settings.endGroup()
categorized_renderer = QgsCategorizedSymbolRenderer()
for key, color in zip(category_keys, category_colors):
fill_color = QColor(color)
fill_color.setAlphaF(0.3)
categorized_renderer.addCategory(\
QgsRendererCategory(
key,
QgsFillSymbol.createSimple(
{"color":fill_color.name(QColor.HexArgb),"outline_color":color, "outline_width":"1"}), ''))
categorized_renderer.setClassAttribute(fieldname)
return categorized_renderer
def export_to_raster(self, path) -> None:
if self.current_vector_layer is None:
return
def load_extract_result(self, res):
r = self.current_raster_layer
vector_layer = QgsVectorLayer("Polygon?field=category:string(20)&field=confidence:double&field=renderkey:string(32)&field=isman:boolean&field=isauto:boolean", Path(r.file_path).name + ' outline', "memory")
# vector_layer = QgsVectorLayer(tempfile)
if not vector_layer.isValid():
print("矢量图层加载失败!")
vector_layer.setRenderer(self.__get_categorical_renderer("renderkey"))
lyr = QgsPalLayerSettings()
lyr.enabled = True
lyr.fieldName = 'label' # default in data sources
# lyr.textFont = self._TestFont
lyr.textNamedStyle = 'Medium'
text_format = QgsTextFormat()
text_format.color = QColor('#ffffff')
text_format.background().color = QColor('#000000')
text_format.buffer().setEnabled(True)
text_format.buffer().setSize(1)
text_format.buffer().setOpacity(0.5)
lyr.setFormat(text_format)
self.label_pal = lyr
root = QgsRuleBasedLabeling.Rule(QgsPalLayerSettings())
rule = QgsRuleBasedLabeling.Rule(lyr)
rule.setDescription('label')
root.appendChild(rule)
#Apply label configuration
rules = QgsRuleBasedLabeling(root)
vector_layer.setLabeling(rules)
vector_layer.triggerRepaint()
vector_layer.startEditing()
features = []
for f in res:
pts = f[0]
prop = f[1]
# pts = grubcut(img_path, pts, False, True, False )
pts = list( f'{p[0]} {p[1]}' for p in pts )
wkt = f'POLYGON (( {",".join(pts)} ))'
# geometry = QgsGeometry.fromWkt(wkt)
feat = QgsFeature(vector_layer.fields())
feat.setGeometry(QgsGeometry.fromWkt(wkt))
feat.setAttribute('category', prop['category'])
feat.setAttribute('confidence', prop['confidence'])
feat.setAttribute('renderkey', prop['category'])
feat.setAttribute('isman', False)
feat.setAttribute('isauto', True)
features.append(feat)
vector_layer.addFeatures(features)
vector_layer.commitChanges()
QgsProject.instance().addMapLayer(vector_layer)
self.process_end.emit()
r.has_extract = True
self.start_extract = False
r.extract_layer = vector_layer
self.layer_update()
def run_thread(self, conn, pp):
all_ok = False
# print(pp.is_alive)
while pp.is_alive:
r = conn.recv()
# print(r)
if all_ok:
self.extract_end.emit(r)
break
if int(r) == self.total_f - 1:
all_ok = True
self.process_update.emit(r)
# print(conn.recv())
def grubcut(self, v, r):
# for f in v.getFeatures():
if self.start_extract:
return
self.current_raster_layer = r
img_path = r.file_path
if getattr(r, 'has_extract', False):
vector_layer = r.extract_layer
try:
QgsProject.instance().removeMapLayer(vector_layer)
except:
pass
# self.current_vector_layer = vector_layer
features = []
points = []
for f in v.getFeatures():
pts = f.geometry().vertices()
pts = list([ vr.x(), vr.y() ] for vr in pts)
points.append(pts)
features.append({
'category': f['category'],
'confidence': f['confidence']
})
self.total_f = len(points)
self.start_extract = True
self.process_start.emit([0, self.total_f])
parent_conn, child_conn = multiprocessing.Pipe()
t = GrabCut(child_conn, img_path, points, features)
p = threading.Thread(target=self.run_thread, args=(parent_conn,t))
t.start()
p.start()
def export_to(self, path, filter_name) -> None:
if filter_name == 'Shp (*.shp)':
if self.current_vector_layer is None:
return
ls = LayerSelect(self)
ls.show()
ls.exec()
if ls.result() == LayerSelect.OK:
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "ESRI Shapefile"
save_options.fileEncoding = "UTF-8"
transform_context = QgsProject.instance().transformContext()
error = QgsVectorFileWriter.writeAsVectorFormatV2(ls.value,
path,
transform_context,
save_options)
if error[0] == QgsVectorFileWriter.NoError:
print("又成功了!")
else:
print(error)
if filter_name == 'JPEG Images(*.jpg)':
file_name = path + '.tif'
extent = self.current_raster_layer.extent()
width, height = self.current_raster_layer.width(), self.current_raster_layer.height()
pipe = QgsRasterPipe()
provider = self.current_raster_layer.dataProvider()
pipe.set(provider.clone())
file_writer = QgsRasterFileWriter(file_name)
error = file_writer.writeRaster(pipe,
width,
height,
extent,
self.current_raster_layer.crs())
target_img = cv2.imread(file_name)
jpg_file_name = path + '.jpg'
cv2.imwrite(jpg_file_name, target_img)
os.remove(file_name)
if error == QgsRasterFileWriter.NoError:
QMessageBox.about(self, 'Export Files', '导出JPEG图像成功')
else:
QMessageBox.about(self, 'Export Files', '导出JPEG图像失败')
if filter_name == 'TIFF Images(*.tif)':
file_name = path + '.tif'
extent = self.current_raster_layer.extent()
width, height = self.current_raster_layer.width(), self.current_raster_layer.height()
pipe = QgsRasterPipe()
provider = self.current_raster_layer.dataProvider()
pipe.set(provider.clone())
file_writer = QgsRasterFileWriter(file_name)
error = file_writer.writeRaster(pipe,
width,
height,
extent,
self.current_raster_layer.crs())
if error == QgsRasterFileWriter.NoError:
QMessageBox.about(self, 'Export Files', '导出TIFF图像成功')
else:
QMessageBox.about(self, 'Export Files', '导出TIFF图像失败')
if filter_name == 'PNG Images(*.png)':
file_name = path + '.tif'
extent = self.current_raster_layer.extent()
width, height = self.current_raster_layer.width(), self.current_raster_layer.height()
pipe = QgsRasterPipe()
provider = self.current_raster_layer.dataProvider()
pipe.set(provider.clone())
file_writer = QgsRasterFileWriter(file_name)
error = file_writer.writeRaster(pipe,
width,
height,
extent,
self.current_raster_layer.crs())
target_img = cv2.imread(file_name)
jpg_file_name = path + '.png'
cv2.imwrite(jpg_file_name, target_img)
os.remove(file_name)
if error == QgsRasterFileWriter.NoError:
QMessageBox.about(self, 'Export Files', '导出PNG图像成功')
else:
QMessageBox.about(self, 'Export Files', '导出PNG图像失败')

70
gui/messagebox.py Normal file
View File

@ -0,0 +1,70 @@
from PyQt5.QtWidgets import QTextEdit
from PyQt5 import QtWidgets
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import Qt
from datetime import datetime, time
class MessageBox(QTextEdit):
INFO=0
WARNING=1
DEBUG=2
ERROR=3
def __init__(self, parent=None, level=0):
super().__init__(parent)
self.setReadOnly(True)
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.setStyleSheet("QTextEdit { background-color: #f0f0f0; }")
self.msg = ''
self.level = level
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.right_menu_show)
def right_menu_show(self, position):
rightMenu = QtWidgets.QMenu(self)
# QAction = QtWidgets.QAction(self.menuBar1)
action = QtWidgets.QAction('清空')
action.triggered.connect(self.clear)
rightMenu.addAction(action)
rightMenu.exec_(self.mapToGlobal(position))
def append(self, text):
self.msg += '<br/>' + text
self.setText(self.msg)
cursor = self.textCursor()
# QTextCursor
cursor.movePosition(QTextCursor.End)
self.setTextCursor(cursor)
def info(self, text):
if self.level <= self.INFO:
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.append('<span style="color:green">[INFO] %s</span> <br/>'%timestr + str(text))
# self.append(text)
# self.append(text)
def warning(self, text):
if self.level <= self.WARNING:
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.append('<span style="color:yellow">[WARNING] %s</span> <br/>'%timestr + str(text))
def debug(self, text):
if self.level <= self.DEBUG:
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.append('<span style="color:green">[DEBUG] %s</span> <br/>'%timestr + str(text))
# self.append(text)
# self.append(text)
def error(self, text):
if self.level <= self.ERROR:
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.append('<span style="color:red">[ERROR] %s</span> <br/>'%timestr + str(text))
# self.append(text)
# self.append(text)
def clear(self):
self.msg = ''
self.setText(self.msg)

72
gui/project.py Normal file
View File

@ -0,0 +1,72 @@
from PyQt5.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QLabel, QMessageBox
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from utils.setting import Settings
class Create(QDialog):
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.setWindowTitle('Create Project')
self.setWindowIcon(QIcon(":/icons/logo.svg"))
self.file = str(Settings.General().root)
self.name = '未命名'
self.max_memory = Settings.Project().max_memory
self.cell_size = Settings.Project().cell_size
file_label = QLabel('Project Dir:')
file_label.setFixedWidth(100)
file_input = QLineEdit()
file_input.setPlaceholderText('Project Dir')
file_input.setToolTip('Project Dir')
file_input.setReadOnly(True)
file_input.setText(self.file)
name_label = QLabel('Project Name:')
name_label.setFixedWidth(100)
name_input = QLineEdit()
name_input.setPlaceholderText('Project Name')
name_input.setToolTip('Project Name')
name_input_layout = QHBoxLayout()
name_input_layout.addWidget(name_label)
name_input_layout.addWidget(name_input)
file_input_layout = QHBoxLayout()
file_input_layout.addWidget(file_label)
file_input_layout.addWidget(file_input)
cell_size_label = QLabel('Cell Size:')
cell_size_label.setFixedWidth(100)
cell_size_x_label = QLabel('X:')
cell_size_y_label = QLabel('Y:')
cell_size_x_input = QLineEdit()
cell_size_y_input = QLineEdit()
cell_size_x_input.setPlaceholderText('Cell Size X')
cell_size_y_input.setPlaceholderText('Cell Size Y')
cell_size_x_input.setToolTip('Cell Size X')
cell_size_y_input.setToolTip('Cell Size Y')
cell_input_layout = QHBoxLayout()
cell_input_layout.addWidget(cell_size_label)
cell_input_layout.addWidget(cell_size_x_label)
cell_input_layout.addWidget(cell_size_x_input)
cell_input_layout.addWidget(cell_size_y_label)
cell_input_layout.addWidget(cell_size_y_input)
ok_button = QPushButton('OK')
cancel_button = QPushButton('Cancel')
button_layout = QHBoxLayout()
button_layout.setDirection(QHBoxLayout.RightToLeft)
button_layout.addWidget(ok_button, 0, Qt.AlignRight)
button_layout.addWidget(cancel_button, 0, Qt.AlignRight)
main_layout = QVBoxLayout()
main_layout.addLayout(file_input_layout)
main_layout.addLayout(name_input_layout)
main_layout.addLayout(cell_input_layout)
main_layout.addLayout(button_layout)
self.setLayout(main_layout)

41
gui/result.py Normal file
View File

@ -0,0 +1,41 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt,QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import (QTreeView, QTreeWidgetItem, QAbstractItemView, QHeaderView, QStyleFactory)
from utils.project import PairLayer
class ResultTable(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ResultTable, self).__init__(parent)
self.tree_view = QTreeView(self)
self.tree = QtWidgets.QTreeWidget(self)
self.tree.setColumnCount(1)
self.tree.setColumnWidth(0,150)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.root=QTreeWidgetItem(self.tree)
self.tree.setHeaderLabels(['图层'])
self.root.setText(0,'Root')
child1=QTreeWidgetItem()
child1.setText(0,'child1')
child1.setCheckState(0,Qt.Checked)
self.root.addChild(child1)
self.tree.expandAll()
self.tree.addTopLevelItem(self.root)
self.tree.clicked.connect(self.onClicked)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tree)
self.setLayout(layout)
def onClicked(self,index):
print(index.row())
def add_layer(self, layer:PairLayer):
pass

0
gui/setting.py Normal file
View File

1
icons/assessment.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621607588767" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="39677" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M288 160v32H160v704h704V192h-128V160h-64v32H352V160zM224 256h64v32h64V256h320v32h64V256h64v64H224z m0 128h576v448H224z m424.992 72.992L480 625.984l-72.992-72.96-46.016 45.984 96 96 23.008 21.984 23.008-21.984 192-192z" p-id="39678" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 638 B

1
icons/cancel.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1632402985895" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3426" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M509.92 475.968l74.032-74.032a16 16 0 0 1 22.624 0l11.312 11.312a16 16 0 0 1 0 22.64L543.84 509.92l74.032 74.032a16 16 0 0 1 0 22.624l-11.312 11.312a16 16 0 0 1-22.624 0L509.92 543.84l-74.032 74.032a16 16 0 0 1-22.64 0l-11.312-11.312a16 16 0 0 1 0-22.624l74.032-74.032-74.032-74.032a16 16 0 0 1 0-22.64l11.312-11.312a16 16 0 0 1 22.64 0l74.032 74.032z m0 319.856c157.904 0 285.92-128 285.92-285.92C795.84 352 667.808 224 509.92 224 352 224 224 352 224 509.92c0 157.904 128 285.92 285.92 285.92z m0 48C325.504 843.84 176 694.336 176 509.92 176 325.52 325.504 176 509.92 176c184.416 0 333.92 149.504 333.92 333.92 0 184.416-149.504 333.92-333.92 333.92z" p-id="3427" fill="#e6922d"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
icons/clear.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621607368815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="39116" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M604.992 131.008c-25.728 0-52.48 8.736-72.992 26.976v1.024L531.008 160 156.992 531.008c-38.72 38.72-38.496 101.856-1.984 142.976l0.992 1.024h0.992l192 192c38.752 38.72 101.888 38.496 143.008 1.984v-0.992L864 496c40.512-40.512 41.76-105.28 3.008-144l-192-192a97.888 97.888 0 0 0-70.016-28.992z m-0.992 64.992c10.24 0 19.744 2.752 26.016 8.992l192 192c12.352 12.384 14.08 36.896-3.008 54.016l-161.024 160.992-244.992-244.992 162.016-160.992 0.992-1.024c8.128-6.72 18.24-8.992 28-8.992z m-236.992 216.992l244.992 244.992-163.008 163.008c-0.352 0.256-0.64 0.768-0.992 1.024-16.512 13.76-41.888 12.096-54.016 0L203.04 632c-0.512-0.64-0.512-1.376-1.024-2.016-13.12-16.48-12-41.984 0-53.984z" p-id="39117" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
icons/edit.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621607314296" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="38929" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M448 128c-35.36 0-64 28.64-64 64v2.016L221.984 296A64.288 64.288 0 0 0 192 288c-35.36 0-64 28.64-64 64 0 23.616 12.864 43.872 32 55.008V776.96c-19.136 11.136-32 31.36-32 55.008 0 35.36 28.64 64 64 64 23.616 0 43.872-12.864 55.008-32H648.96c11.136 19.136 31.36 32 55.008 32 35.36 0 64-28.64 64-64 0-12.736-3.52-24.992-10.016-35.008L836 640a64.16 64.16 0 0 0 8-127.008l-60.992-182.976C793.12 318.624 800 304.352 800 288c0-35.36-28.64-64-64-64-16.384 0-30.624 6.88-42.016 16.992L511.04 179.008A64.384 64.384 0 0 0 448 128z m42.016 112l182.976 60c4.896 25.76 25.28 46.112 51.008 51.008l60.992 181.984A63.68 63.68 0 0 0 768 576c0 13.248 4.128 25.76 11.008 36L700 768a63.616 63.616 0 0 0-51.008 32H247.04A63.392 63.392 0 0 0 224 776.992V407.04c19.136-11.136 32-31.36 32-55.008v-2.016l162.016-101.984c8.96 4.736 19.104 8 29.984 8 16.128 0 30.72-6.112 42.016-16z" p-id="38930" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
icons/exit.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621608444143" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="63716" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M160 160v704h704V160z m64 64h576v576H224z m150.016 106.016l-45.024 44.992L465.984 512l-138.976 139.008 44.992 44.992 139.008-139.008 137.984 138.016 45.024-45.024L556 512l136-136-44.992-44.992-136 136z" p-id="63717" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 622 B

1
icons/export.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621607403815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="39303" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M192 128v768h640v-256l-64 64v128H256V192h512v128l64 64V128z m524.992 224L672 396.992 754.016 480h-309.024v64h308.992L672 627.008 716.992 672l138.016-136.992 21.984-23.008-21.984-23.008z" p-id="39304" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 606 B

1
icons/font.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1631612098602" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="53542" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M480 192L256 832h64l67.008-192h249.984L704 832h64L544 192z m32 91.008L614.016 576h-204z" p-id="53543" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 508 B

1
icons/full.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1604376212656" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17876" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M128 128v288h64V237.984L466.016 512 192 786.016V608H128v288h288v-64H237.984L512 557.984 786.016 832H608v64h288v-288h-64v178.016L557.984 512 832 237.984V416h64V128h-288v64h178.016L512 466.016 237.984 192H416V128z" p-id="17877" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 632 B

1
icons/load.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621592174373" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18094" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M64 160v704h896V160z m64 64h768v444.992l-168.992-169.984-23.008-23.008-144.992 144.992-184-185.984-23.008-23.008-224 224z m640 64a63.968 63.968 0 1 0 0 128 63.968 63.968 0 1 0 0-128z m-416 215.008L646.016 800H128v-72.992z m352 64l192 192V800h-159.008l-132.992-134.016z" p-id="18095" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 689 B

1
icons/logo.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621592112747" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17907" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M160 160v128h32v448H160v128h128v-32h448v32h128v-128h-32V288h32V160h-128v32H288V160z m128 96h448v32h32v448h-32v32H288v-32H256V288h32z m32 64v256h128v128h256v-256h-128v-128z m64 64h128v128h-128z m192 128h64v128h-128v-64h64z" p-id="17908" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 642 B

1
icons/model.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621607539796" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="39490" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 94.016L148.992 225.952 128 234.016v489.024l16.992 8.992 352 192 15.008 8 15.008-8 352-192 16.992-8.992V233.984l-20.992-8z m0 67.968l268 97.024L512 380.992 244 259.008zM512 224c-35.36 0-64 14.4-64 32s28.64 32 64 32 64-14.4 64-32-28.64-32-64-32zM192 304.992l288 131.008v406.016l-288-157.024z m640 0v380l-288 157.024V435.968zM404.992 448c-11.84 0-20.992 11.36-20.992 28 0 21.376 11.36 43.52 28 52.992 4.736 2.368 10.24 2.016 15.008 2.016 7.104 0 11.232-2.24 16-7.008 2.368-4.736 4.992-11.52 4.992-20.992 0-21.376-14.4-43.52-31.008-52.992-4.736-2.4-7.232-2.016-12-2.016z m236 32a30.272 30.272 0 0 0-8 3.008c-19.52 8.352-36 36-36 60.992 0 8.384-0.64 16.384 5.024 22.016 5.6 5.6 11.616 8.96 20 8.96 5.6 0 11.36-0.224 16.96-2.976 19.52-11.136 33.024-36.736 33.024-59.008 0-19.52-8.256-32.992-24.992-32.992-1.376 0-3.648-0.384-6.016 0z m-316.992 40.992c-11.872 0-20.992 11.392-20.992 28 0 21.376 11.36 42.496 28 52 4.736 2.368 9.248 3.008 13.984 3.008 7.136 0 12.256-2.24 16.992-7.008 2.4-4.736 5.024-12.48 5.024-21.984 0-21.376-14.4-42.496-31.008-52-4.736-2.368-7.264-2.016-12-2.016z m428.992 37.024c-2.368 0.352-5.248 0.608-8 1.984-19.52 8.384-36 36.992-36 62.016 0 8.352 0.384 16.352 6.016 21.984 5.632 5.632 10.624 8 18.976 8 5.632 0 11.392 0.736 17.024-2.016 19.52-11.104 32.992-36.736 32.992-58.976 0-19.52-8.256-33.024-24.992-33.024-1.376 0-3.648-0.352-6.016 0z m-508 30.976c-11.84 0-20.992 11.392-20.992 28 0 21.376 11.36 43.52 28 53.024 4.736 2.368 10.24 1.984 15.008 1.984 7.104 0 11.232-2.24 16-7.008 2.368-4.736 4.992-11.52 4.992-20.992 0-21.376-14.4-43.52-31.008-52.992-4.736-2.368-7.232-2.016-12-2.016z" p-id="39491" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

1
icons/ok.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1632402912710" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2494" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M512 1024a512 512 0 1 1 512-512 512.576 512.576 0 0 1-512 512z m0-960a448 448 0 1 0 448 448A448.512 448.512 0 0 0 512 64z m-34.848 630.4a32 32 0 0 1-22.688 9.6H454.4a32 32 0 0 1-22.624-9.376l-166.4-166.4A32 32 0 0 1 310.624 483.2l143.424 143.36 259.2-264.864a32 32 0 1 1 45.76 44.8z" p-id="2495" fill="#529834"></path></svg>

After

Width:  |  Height:  |  Size: 699 B

1
icons/outline.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1632464494060" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8621" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M291.3888 489.216a132.48 132.48 0 1 1 0.064-264.96 132.48 132.48 0 0 1-0.064 264.96z m-132.48 335.488V607.936a176.064 176.064 0 0 1 132.48-59.84c46.976 0 89.664 18.368 121.28 48.32a454.144 454.144 0 0 1 452.48-282.88v511.168H158.9728zM82.4928 295.168a41.216 41.216 0 1 1-82.368 0C0.0608 252.16 10.3008 210.56 29.7568 173.12a41.152 41.152 0 1 1 73.088 38.08 181.632 181.632 0 0 0-20.416 83.968z m102.976-163.84A41.152 41.152 0 1 1 150.0768 56.832 259.072 259.072 0 0 1 278.9728 32.704a41.216 41.216 0 0 1-5.696 82.304 175.68 175.68 0 0 0-87.872 16.384z m216.32 17.088a41.28 41.28 0 0 1 42.368-70.656 289.28 289.28 0 0 1 89.216 85.312 41.28 41.28 0 0 1-67.84 46.72c-17.28-25.024-38.4-46.08-63.808-61.376z m172.16 123.52a41.152 41.152 0 1 1-49.856-65.728 475.392 475.392 0 0 1 100.096-58.56 41.152 41.152 0 1 1 32.576 75.776 392.96 392.96 0 0 0-82.816 48.512z m212.992-81.984a41.216 41.216 0 0 1-9.728-81.92c31.936-3.776 65.792-5.568 102.4-5.568h5.12a41.216 41.216 0 0 1-0.192 82.432h-4.928c-33.472 0-64.128 1.664-92.672 5.056z m154.816 17.664a41.216 41.216 0 1 1 82.368 0v106.048a41.216 41.216 0 1 1-82.368 0V207.616z m0 249.6a41.216 41.216 0 1 1 82.368 0v106.048a41.216 41.216 0 1 1-82.368 0V457.216z m0 256a41.216 41.216 0 1 1 82.368 0v106.048a41.216 41.216 0 1 1-82.368 0v-106.048z m4.352 196.352a41.216 41.216 0 0 1 0 82.432h-105.92a41.216 41.216 0 0 1 0-82.432h105.92z m-247.168 0a41.216 41.216 0 0 1 0 82.432H592.9568a41.216 41.216 0 0 1 0-82.432h105.92z m-247.168 0a41.216 41.216 0 0 1 0 82.432H345.7888a41.216 41.216 0 0 1 0-82.432h105.92z m-247.168 0a41.216 41.216 0 0 1 0 82.432H98.6208a41.216 41.216 0 0 1 0-82.432h105.92zM82.4288 825.6A41.216 41.216 0 1 1 0.0608 825.6v-106.048a41.216 41.216 0 1 1 82.368 0v106.048z m0-249.6A41.216 41.216 0 1 1 0.0608 576V470.016a41.216 41.216 0 1 1 82.368 0v106.048z m0-248.576a41.216 41.216 0 1 1-82.368 0v-23.872a41.216 41.216 0 1 1 82.368 0v23.872z" fill="#548235" p-id="8622" data-spm-anchor-id="a313x.7781069.0.i16" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

1
icons/paint.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1631610702873" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="53289" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M160 128v256h576V128z m64 64h448v128H224z m544 32v64h32v136l-296.992 88.992-23.008 7.008V608h-64v288h192v-288h-64v-40l296.992-88.992 23.008-7.008V224z m-288 448h64v160h-64z" p-id="53290" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 593 B

1
icons/pan.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621606142680" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18655" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 64c-44.256 0-80.992 31.008-92 72C408.736 131.36 396.864 128 384 128c-52.64 0-96 43.36-96 96v344l-28-28a96.736 96.736 0 0 0-136 0 96.736 96.736 0 0 0 0 136l216.992 216.992C378.624 930.496 430.624 960 492 960H640c123.36 0 224-100.64 224-224V352c0-52.64-43.36-96-96-96a94.4 94.4 0 0 0-32 6.016V224c0-52.64-43.36-96-96-96-12.864 0-24.736 3.36-36 8C592.992 95.008 556.256 64 512 64z m0 64c18.112 0 32 13.888 32 32v320h64V224c0-18.112 13.888-32 32-32 18.112 0 32 13.888 32 32v256h64v-128c0-18.112 13.888-32 32-32 18.112 0 32 13.888 32 32v384c0 88.736-71.264 160-160 160h-148c-40.736 0-75.744-19.872-104.992-48.992l-218.016-216a32.448 32.448 0 0 1 0-46.016 32.448 32.448 0 0 1 46.016 0l81.984 83.008L352 723.008V224c0-18.112 13.888-32 32-32 18.112 0 32 13.888 32 32v256h64V160c0-18.112 13.888-32 32-32z" p-id="18656" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

67
icons/qt.svg Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="462pt"
height="339pt"
viewBox="0 0 462 339"
version="1.1"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="TheQtCompany_logo_2.svg">
<metadata
id="metadata20">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs18" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1536"
inkscape:window-height="801"
id="namedview16"
showgrid="false"
inkscape:zoom="1.1138643"
inkscape:cx="270.58047"
inkscape:cy="174.65092"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
fill="#548235"
d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z"
id="path6" />
<path
d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z"
id="path8"
fill="#ffffff" />
<path
d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z"
id="path10"
fill="#ffffff" />
<path
fill="#548235"
d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z"
id="path12" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

1
icons/settings.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1631608141807" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="53038" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M480 160c-41.376 0-76.64 27.008-90.016 64H128v64h262.016c13.344 36.992 48.608 64 89.984 64s76.64-27.008 90.016-64H896V224H569.984C556.64 187.008 521.408 160 480 160z m0 64c18.016 0 32 14.016 32 32 0 18.016-14.016 32-32 32-18.016 0-32-14.016-32-32 0-18.016 14.016-32 32-32z m224 192c-41.376 0-76.64 27.008-90.016 64H128v64h486.016c13.344 36.992 48.608 64 89.984 64s76.64-27.008 90.016-64H896v-64h-102.016c-13.344-36.992-48.608-64-89.984-64z m0 64c18.016 0 32 14.016 32 32 0 18.016-14.016 32-32 32-18.016 0-32-14.016-32-32 0-18.016 14.016-32 32-32z m-352 192c-41.376 0-76.64 27.008-90.016 64H128v64h134.016c13.344 36.992 48.608 64 89.984 64s76.64-27.008 90.016-64H896v-64H441.984c-13.344-36.992-48.608-64-89.984-64z m0 64c18.016 0 32 14.016 32 32 0 18.016-14.016 32-32 32-18.016 0-32-14.016-32-32 0-18.016 14.016-32 32-32z" p-id="53039" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

1
icons/start.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1604050740174" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1304" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M288 164.992v694.016l48.992-32L827.008 512 336.96 196.992z m64 117.024L708.992 512 352 742.016z" fill="#548235" p-id="1305"></path></svg>

After

Width:  |  Height:  |  Size: 514 B

1
icons/vector.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621607964079" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="63529" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M160 160v224h64v256H160v224h224v-64h256v64h224v-224h-64v-256h64V160h-224v64h-256V160H160z m64 64h96v96H224V224z m480 0h96v96h-96V224zM384 288h256v96h96v256h-96v96h-256v-96H288v-256h96V288zM224 704h96v96H224v-96z m480 0h96v96h-96v-96z" p-id="63530" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 654 B

1
icons/zoomin.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621606055767" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18281" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M608 96C431.616 96 288 239.616 288 416c0 76.64 26.88 146.88 72 202.016L104.992 872.96 151.04 919.04l254.976-255.008A317.504 317.504 0 0 0 608 736c176.384 0 320-143.616 320-320S784.384 96 608 96z m0 64c141.76 0 256 114.24 256 256s-114.24 256-256 256-256-114.24-256-256 114.24-256 256-256z m-32 128v96h-96v64h96v96h64v-96h96v-64h-96V288z" p-id="18282" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 756 B

1
icons/zoomout.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621606083458" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18468" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M608 96C431.616 96 288 239.616 288 416c0 76.64 26.88 146.88 72 202.016L104.992 872.96 151.04 919.04l254.976-255.008A317.504 317.504 0 0 0 608 736c176.384 0 320-143.616 320-320S784.384 96 608 96z m0 64c141.76 0 256 114.24 256 256s-114.24 256-256 256-256-114.24-256-256 114.24-256 256-256z m-128 224v64h256v-64z" p-id="18469" fill="#548235"></path></svg>

After

Width:  |  Height:  |  Size: 730 B

1
lic/license.lic Normal file
View File

@ -0,0 +1 @@
pGZJMmJtule8fwDCz4mnyHoQa7N6pl5GRdLqfoXREBqG4Xb1jbvgf7RmC8f1+sNpiCFSIt7NgvU362tKhB5UBXn/vUAadG1lOGC70dUhprGzBoqJN7VkAHkNGg0XjoE8H0SCVynr8To7ciwcnmK6HJXre6i+mBdTjACmKseTMlWp480XOt7uHysltORbTA3J

1
license.lic Normal file
View File

@ -0,0 +1 @@
pGZJMmJtule8fwDCz4mnyHoQa7N6pl5GRdLqfoXREBqG4Xb1jbvgf7RmC8f1+sNpiCFSIt7NgvU362tKhB5UBXn/vUAadG1lOGC70dUhprGzBoqJN7VkAHkNGg0XjoE8H0SCVynr8To7ciwcnmK6HJXre6i+mBdTjACmKseTMlWp480XOt7uHysltORbTA3J

0
log.txt Normal file
View File

9
main.py Normal file
View File

@ -0,0 +1,9 @@
from mul.mulstart import MulStart
import logging
logging.basicConfig(level=logging.INFO, filename='log.txt', filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
if __name__ == '__main__':
t = MulStart()
t.run()

32
mul/mulexport.py Normal file
View File

@ -0,0 +1,32 @@
import multiprocessing
from alg.txt_export_to import export_to_shp
import os
import glob
from qgis.core import *
class ExportToSHP(multiprocessing.Process):
def __init__(self, conn, result_path, output_dir):
super(ExportToSHP, self).__init__()
self.conn = conn
self.result_path = result_path
self.result_list = self.get_result_list()
self.output_dir = output_dir
os.makedirs(self.output_dir, exist_ok=True)
def get_result_list(self):
fl = list(glob.glob(os.path.join(self.result_path, '*.txt')))
return fl
def run(self):
# result = []
self.conn.send(len(self.result_list))
qgs = QgsApplication([], False)
QgsApplication.initQgis()
for i, p in enumerate(self.result_list):
o = os.path.basename(p)
o = os.path.splitext(o)[0]
r = export_to_shp(p, os.path.join(self.output_dir, o + '.shp'))
# result.append([r, self.feats[i]])
self.conn.send([i, r, o])
# self.conn.send(result)

19
mul/mulgrubcut.py Normal file
View File

@ -0,0 +1,19 @@
import multiprocessing
from alg.grubcut import grubcut
class GrabCut(multiprocessing.Process):
def __init__(self, conn, img_path, pts = [], feats = []):
super(GrabCut, self).__init__()
self.conn = conn
self.pts = pts
self.feats = feats
self.img_path = img_path
def run(self):
result = []
for i, p in enumerate(self.pts):
r = grubcut(self.img_path, p, False, True, False)
result.append([r, self.feats[i]])
self.conn.send(i)
self.conn.send(result)

63
mul/mulstart.py Normal file
View File

@ -0,0 +1,63 @@
import sys
import time
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QSplashScreen, QProgressBar, QStyleFactory, QMessageBox
from qgis.core import QgsApplication
# from qgis.core import
from gui.mainwindow import MainWindow
import multiprocessing
from gui import license
from utils.setting import Settings
class MulStart:
def __init__(self, **kargs) -> None:
super(MulStart, self).__init__()
self.kargs = kargs
def run(self):
QgsApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QgsApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
QgsApplication.setOrganizationName("西安理工大学-ImgSciGroup")
QgsApplication.setApplicationName("Easy Change Detection")
QgsApplication.setApplicationVersion("v0.0.1")
QgsApplication.setFont(QFont("Segoe UI", 10))
QgsApplication.setStyle(QStyleFactory.create("Fusion"))
# pyrcc5 res.qrc -o rc.py
import rc
app = QgsApplication([], True)
QgsApplication.initQgis()
while not Settings.General().license:
QMessageBox.warning(None, "Warning", "Please select a license file.")
if(license.License().exec_() == license.License.Accepted):
continue
else:
sys.exit(0)
# Create and display the splash screen
splash_pix = QPixmap(':/icons/splash.png')
splash = QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint)
progressBar = QProgressBar(splash)
progressBar.setMaximum(10)
progressBar.setTextVisible(False)
progressBar.setGeometry(46, splash_pix.height() - 60, splash_pix.width()-92, 10)
splash.show()
for i in range(1, 11):
progressBar.setValue(i)
t = time.time()
while time.time() < t + 0.05:
app.processEvents()
ex = MainWindow(**self.kargs)
# ex.canvas.load_image(r'data\100001678.jpg')
# ex.canvas.load_result_from_txt(r'data\100001678.txt')
# ex.showMaximized()
ex.show()
splash.finish(ex)
sys.exit(app.exec_())

26
res.qrc Normal file
View File

@ -0,0 +1,26 @@
<RCC>
<qresource prefix="/">
<file>icons/splash.png</file>
<file>icons/logo.svg</file>
<file>icons/load.svg</file>
<file>icons/settings.svg</file>
<file>icons/exit.svg</file>
<file>icons/vector.svg</file>
<file>icons/zoomin.svg</file>
<file>icons/zoomout.svg</file>
<file>icons/pan.svg</file>
<file>icons/full.svg</file>
<file>icons/start.svg</file>
<file>icons/clear.svg</file>
<file>icons/edit.svg</file>
<file>icons/export.svg</file>
<file>icons/model.svg</file>
<file>icons/assessment.svg</file>
<file>icons/paint.svg</file>
<file>icons/font.svg</file>
<file>icons/qt.svg</file>
<file>icons/ok.svg</file>
<file>icons/cancel.svg</file>
<file>icons/outline.svg</file>
</qresource>
</RCC>

132
test/tree.py Normal file
View File

@ -0,0 +1,132 @@
from PyQt5 import QtCore
from PyQt5.QtWidgets import QDesktopWidget
import sys
from PyQt5.QtWidgets import *
# import tree # tree.py文件
class myTreeWidget:
def __init__(self, objTree):
self.myTree = objTree
# 设置列数
self.myTree.setColumnCount(1)
# 设置树形控件头部的标题
self.myTree.setHeaderLabels(['机构列表'])
# 设置根节点
self.root = QTreeWidgetItem(self.myTree)
self.root.setText(0, '本单位')
# 设置树形控件的列的宽度
self.myTree.setColumnWidth(0, 100)
# 设置子节点1
child1 = QTreeWidgetItem(self.root)
child1.setText(0, '市场部')
self.root.addChild(child1)
# 设置子节点11
child11 = QTreeWidgetItem(child1)
child11.setText(0, '销售班')
# 设置子节点2
child2 = QTreeWidgetItem(self.root)
child2.setText(0, '财务部')
# 设置子节点21
child21 = QTreeWidgetItem(child2)
child21.setText(0, '财务一班')
# 加载根节点的所有属性与子控件
self.myTree.addTopLevelItem(self.root)
# TODO 优化2 给节点添加响应事件
self.myTree.clicked.connect(self.onClicked)
# 节点全部展开
self.myTree.expandAll()
def onClicked(self):
item = self.myTree.currentItem()
print('Key=%s' % (item.text(0)))
class MyPyQTMainForm(QMainWindow):
"""
主界面
"""
def __init__(self):
"""
初始化
"""
super(MyPyQTMainForm, self).__init__()
# self.setupUi(self)
# 创建树控件对象
layout = QVBoxLayout()
tree_widget = QTreeWidget()
# layout.addWidget(tree_widget)
# self.setLayout(layout)
self.layout().addWidget(tree_widget)
self.myTreeTest = myTreeWidget(tree_widget)
# self.myTreeTest = myTreeWidget(self.treeWidget)
def center(self):
"""
定义一个函数使得窗口居中显示
"""
# 获取屏幕坐标系
screen = QDesktopWidget().screenGeometry()
# 获取窗口坐标系
size = self.geometry()
newLeft = (screen.width() - size.width()) / 2
newTop = (screen.height() - size.height()) / 2
self.move(int(newLeft), int(newTop))
def addNode(self):
"""
添加节点
"""
print('--- addTreeNode ---')
item = self.myTreeTest.myTree.currentItem()
node = QTreeWidgetItem(item)
node.setText(0, '后勤部')
def deleteNode(self):
"""
删除节点
"""
print('--- delTreeNode ---')
item = self.myTreeTest.myTree.currentItem()
root = self.myTreeTest.myTree.invisibleRootItem()
for item in self.myTreeTest.myTree.selectedItems():
(item.parent() or root).removeChild(item)
def modifyNode(self):
"""
修改节点
"""
print('--- modifyTreeNode ---')
item = self.myTreeTest.myTree.currentItem()
item.setText(0, '办公室')
"""
主函数
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
myPyMainForm = MyPyQTMainForm()
# 主窗口显示在屏幕中间
myPyMainForm.center()
# 禁止最大化按钮
myPyMainForm.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint)
# 禁止拉伸窗口大小
myPyMainForm.setFixedSize(myPyMainForm.width(), myPyMainForm.height())
# 显示主界面
myPyMainForm.show()
sys.exit(app.exec_())

2
utils/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from .setting import Settings
from .license import LicenseHelper

143
utils/license.py Normal file
View File

@ -0,0 +1,143 @@
import base64
from Crypto.Cipher import AES
import uuid
import hashlib
import datetime
#
class AESHelper(object):
def __init__(self, password, iv):
self.password = bytes(password, encoding='utf-8')
self.iv = bytes(iv, encoding='utf-8')
def pkcs7padding(self, text):
"""
明文使用PKCS7填充
最终调用AES加密方法时传入的是一个byte数组要求是16的整数倍因此需要对明文进行处理
:param text: 待加密内容(明文)
:return:
"""
bs = AES.block_size # 16
length = len(text)
bytes_length = len(bytes(text, encoding='utf-8'))
# tipsutf-8编码时英文占1个byte而中文占3个byte
padding_size = length if(bytes_length == length) else bytes_length
padding = bs - padding_size % bs
# tipschr(padding)看与其它语言的约定,有的会使用'\0'
padding_text = chr(padding) * padding
return text + padding_text
def pkcs7unpadding(self, text):
"""
处理使用PKCS7填充过的数据
:param text: 解密后的字符串
:return:
"""
length = len(text)
unpadding = ord(text[length-1])
return text[0:length-unpadding]
def encrypt(self, content):
"""
AES加密
模式cbc
填充pkcs7
:param key: 密钥
:param content: 加密内容
:return:
"""
cipher = AES.new(self.password, AES.MODE_CBC, self.iv)
content_padding = self.pkcs7padding(content)
encrypt_bytes = cipher.encrypt(bytes(content_padding, encoding='utf-8'))
result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
return result
def decrypt(self, content):
"""
AES解密
模式cbc
去填充pkcs7
:param key:
:param content:
:return:
"""
cipher = AES.new(self.password, AES.MODE_CBC, self.iv)
encrypt_bytes = base64.b64decode(content)
decrypt_bytes = cipher.decrypt(encrypt_bytes)
result = str(decrypt_bytes, encoding='utf-8')
result = self.pkcs7unpadding(result)
return result
def get_aes():
# AES_SECRET和AES_IV分别为密钥和偏移量
aes_helper = AESHelper(
'ao234esorGFSFGubh#$^&@gihdfjl$@4',
'dergbdzbfdsdrt$g')
return aes_helper
class LicenseHelper(object):
def generate_license(self, end_date, mac_addr):
print("Received end_date: {}, mac_addr: {}".format(end_date, mac_addr))
psw = self.hash_msg('smartant' + str(mac_addr))
license_str = {}
license_str['mac'] = mac_addr
license_str['time_str'] = end_date
license_str['psw'] = psw
s = str(license_str)
licence_result = get_aes().encrypt(s)
return licence_result
def get_mac_address(self):
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
return ":".join([mac[e:e + 2] for e in range(0, 11, 2)])
def hash_msg(self, msg):
sha256 = hashlib.sha256()
sha256.update(msg.encode('utf-8'))
res = sha256.hexdigest()
return res
def read_license(self, license_result):
lic_msg = bytes(license_result, encoding="utf8")
license_str = get_aes().decrypt(lic_msg)
license_dic = eval(license_str)
return license_dic
def check_license_date(self, lic_date):
current_time = datetime.datetime.strftime(datetime.datetime.now() ,"%Y-%m-%d %H:%M:%S")
current_time_array = datetime.datetime.strptime(current_time,"%Y-%m-%d %H:%M:%S")
lic_date_array = datetime.datetime.strptime(lic_date, "%Y-%m-%d %H:%M:%S")
remain_days = lic_date_array - current_time_array
remain_days = remain_days.days
print('lic data:{}'.format(lic_date))
print('remain_days: {}'.format(remain_days))
if remain_days < 0 or remain_days == 0:
return False
else:
return True
def check_license_psw(self, psw):
mac_addr = self.get_mac_address()
hashed_msg = self.hash_msg('smartant' + str(mac_addr))
if psw == hashed_msg:
return True
else:
return False
if __name__ == '__main__':
lic = LicenseHelper().generate_license('2022-12-31 00:00:00', LicenseHelper().get_mac_address())
with open('license.lic', 'w') as f:
f.write(lic[::-1])
with open('license.lic', 'r') as f:
license_result = f.read()[::-1]
license_dic = LicenseHelper().read_license(license_result)
print(license_dic)

11
utils/menu.py Normal file
View File

@ -0,0 +1,11 @@
from functools import wraps
def as_menu(D, name, icon=None, shortcut=None, tip=None, checkable=False, signal=None, callback=None, enabled=True):
def func(f):
@wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
D.addMenu(name, icon, shortcut, tip, checkable, signal, callback, enabled, wrapper)
return f
return func

121
utils/project.py Normal file
View File

@ -0,0 +1,121 @@
import os
from pathlib import Path
from osgeo import gdal, gdal_array
from utils.setting import Settings
from qgis.core import QgsRasterLayer
from PyQt5.QtCore import QObject, pyqtSignal
def singleton(cls):
_instance = {}
def inner(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return inner
@singleton
class Project(QObject):
project_init = pyqtSignal(bool)
def __init__(self,
parent=None):
super().__init__(parent)
self.is_init = False
self.cell_size = Settings.Project().cell_size
self.max_memory = Settings.Project().max_memory
self.max_threads = Settings.Project().max_threads
self.root = Settings.General().root
def connect(self, pair_canvas,
layer_tree,
message_box,
result_table):
self.pair_canvas = pair_canvas
self.layer_tree = layer_tree
self.message_box = message_box
self.result_table = result_table
def setup(self, file=None):
self.is_init = True
if file is None:
self.file = Path(self.root)/'project'/'untitled.cdp'
dir_name = os.path.dirname(self.file)
if not os.path.exists(dir_name):
os.makedirs(dir_name, exist_ok=True)
if not self.file.exists():
f = self.file.open('w')
f.close()
else:
self.load()
# self.project_created.emit()
self.project_init.emit(True)
def save(self):
pass
def clear(self):
'''
clear all layers
'''
self.layer_tree.clear()
self.pair_canvas.clear()
self.message_box.clear()
self.result_table.clear()
def load(self):
pass
def add_layer(self, pth1, pth2):
player = PairLayer(pth1, pth2)
if player.check():
self.layer_tree.add_layer(player)
else:
self.message_box.show_message(player.msg)
class VectorLayer:
pass
class GridLayer:
pass
class PairLayer:
def __init__(self, pth1, pth2) -> None:
self.pth1 = pth1
self.pth2 = pth2
self.l1_name = os.path.basename(pth1)
self.l2_name = os.path.basename(pth2)
self.grid_layer = GridLayer()
self.msg = ''
def check(self):
if not os.path.exists(self.pth1):
self.msg = '图层1不存在'
return False
if not os.path.exists(self.pth2):
self.msg = '图层2不存在'
return False
ds1 = gdal.Open(self.pth1)
ds2 = gdal.Open(self.pth2)
if ds1 is None or ds2 is None:
self.msg = '图层打开失败'
return False
if ds1.RasterXSize != ds2.RasterXSize or ds1.RasterYSize != ds2.RasterYSize:
self.msg = '图层尺寸不一致'
return False
del ds1
del ds2
self.l1 = QgsRasterLayer(self.pth1, self.l1_name)
self.l2 = QgsRasterLayer(self.pth2, self.l2_name)
return True

109
utils/setting.py Normal file
View File

@ -0,0 +1,109 @@
from datetime import datetime
import os
from typing import Tuple
from PyQt5.QtCore import QSettings
from utils.license import LicenseHelper
class Settings(QSettings):
def __init__(self, key):
super().__init__()
self.key = key
def __enter__(self):
self.beginGroup(self.key)
return self
def __exit__(self, *args, **kargs):
self.endGroup()
class Project:
PRE= 'project'
@property
def cell_size(self) -> Tuple[int, int]:
with Settings(self.PRE) as s:
return s.value('cell_size', (100, 100))
@cell_size.setter
def cell_size(self, value:Tuple[int, int]):
with Settings(self.PRE) as s:
s.setValue('cell_size', value)
@property
def max_memory(self):
with Settings(self.PRE) as s:
return s.value('max_memory', 100)
@max_memory.setter
def max_memory(self, value):
with Settings(self.PRE) as s:
s.setValue('max_memory', value)
@property
def max_threads(self):
with Settings(self.PRE) as s:
return s.value('max_threads', 4)
@max_threads.setter
def max_threads(self, value):
with Settings(self.PRE) as s:
s.setValue('max_threads', value)
class General:
PRE='general'
@property
def size(self):
with Settings(Settings.General.PRE) as s:
return s.value('size', (800, 600))
@size.setter
def size(self, value):
with Settings(Settings.General.PRE) as s:
s.setValue('size', value)
@property
def end_date(self):
if not os.path.exists('lic/license.lic'):
return datetime.now()
with open('lic/license.lic', 'r') as f:
lic = f.read()[::-1]
lic_helper = LicenseHelper()
try:
lic_dic = lic_helper.read_license(lic)
if lic_helper.check_license_date(lic_dic['time_str']) and lic_helper.check_license_psw(lic_dic['psw']):
return lic_dic['time_str']
else:
return datetime.now()
except:
return datetime.now()
@property
def license(self):
if not os.path.exists('lic/license.lic'):
return False
with open('lic/license.lic', 'r') as f:
lic = f.read()[::-1]
lic_helper = LicenseHelper()
try:
lic_dic = lic_helper.read_license(lic)
if lic_helper.check_license_date(lic_dic['time_str']) and lic_helper.check_license_psw(lic_dic['psw']):
return True
else:
return False
except:
return False
@property
def root(self):
with Settings(Settings.General.PRE) as s:
return s.value('root', './')