commit d5c57a2402816a46852a62e37cc9609f3fc45ff8 Author: copper Date: Tue May 3 13:16:39 2022 +0800 init diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0bb75f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.onnx filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed028b2 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..0d5dbc1 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,12 @@ +# 变化检测 +王铜 +CVEO团队 + +# 环境 +Python3 + PyQt + QGIS? + +# 当前依赖 +pyqt qgis gdal numpy + +# 打包方式 +comming soon \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/actions/actions.py b/actions/actions.py new file mode 100644 index 0000000..4b2d4d4 --- /dev/null +++ b/actions/actions.py @@ -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 \ No newline at end of file diff --git a/gui/about.py b/gui/about.py new file mode 100644 index 0000000..718dee8 --- /dev/null +++ b/gui/about.py @@ -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("

"+ QApplication.applicationName() + "

") + self.label.setAlignment(Qt.AlignCenter) + self.label.setStyleSheet("font-size: 20px;") + + self.label2 = QLabel("

Version: " + QApplication.applicationVersion() + "

") + self.label2.setAlignment(Qt.AlignCenter) + self.label2.setStyleSheet("font-size: 15px;") + + self.label3 = QLabel("

" + QApplication.organizationName() + "

") + self.label3.setAlignment(Qt.AlignCenter) + self.label3.setStyleSheet("font-size: 15px;") + + self.label4 = QLabel("

Copyright (c) 2020

") + 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) diff --git a/gui/layertree.py b/gui/layertree.py new file mode 100644 index 0000000..347b545 --- /dev/null +++ b/gui/layertree.py @@ -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)) + diff --git a/gui/license.py b/gui/license.py new file mode 100644 index 0000000..31b161d --- /dev/null +++ b/gui/license.py @@ -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() \ No newline at end of file diff --git a/gui/mainwindow.py b/gui/mainwindow.py new file mode 100644 index 0000000..d2ec7d3 --- /dev/null +++ b/gui/mainwindow.py @@ -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) \ No newline at end of file diff --git a/gui/mapcanvas.py b/gui/mapcanvas.py new file mode 100644 index 0000000..0a51fcf --- /dev/null +++ b/gui/mapcanvas.py @@ -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图像失败!') \ No newline at end of file diff --git a/gui/messagebox.py b/gui/messagebox.py new file mode 100644 index 0000000..56da67e --- /dev/null +++ b/gui/messagebox.py @@ -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 += '
' + 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('[INFO] %s
'%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('[WARNING] %s
'%timestr + str(text)) + + def debug(self, text): + if self.level <= self.DEBUG: + timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.append('[DEBUG] %s
'%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('[ERROR] %s
'%timestr + str(text)) + # self.append(text) + # self.append(text) + + def clear(self): + self.msg = '' + self.setText(self.msg) + \ No newline at end of file diff --git a/gui/project.py b/gui/project.py new file mode 100644 index 0000000..9d28ae5 --- /dev/null +++ b/gui/project.py @@ -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) + diff --git a/gui/result.py b/gui/result.py new file mode 100644 index 0000000..81fd948 --- /dev/null +++ b/gui/result.py @@ -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 diff --git a/gui/setting.py b/gui/setting.py new file mode 100644 index 0000000..e69de29 diff --git a/icons/assessment.svg b/icons/assessment.svg new file mode 100644 index 0000000..7c9bd45 --- /dev/null +++ b/icons/assessment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/cancel.svg b/icons/cancel.svg new file mode 100644 index 0000000..f5be325 --- /dev/null +++ b/icons/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/clear.svg b/icons/clear.svg new file mode 100644 index 0000000..72bc3ec --- /dev/null +++ b/icons/clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/edit.svg b/icons/edit.svg new file mode 100644 index 0000000..2c23b9e --- /dev/null +++ b/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/exit.svg b/icons/exit.svg new file mode 100644 index 0000000..6008f00 --- /dev/null +++ b/icons/exit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/export.svg b/icons/export.svg new file mode 100644 index 0000000..359844e --- /dev/null +++ b/icons/export.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/font.svg b/icons/font.svg new file mode 100644 index 0000000..f92ba9d --- /dev/null +++ b/icons/font.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/full.svg b/icons/full.svg new file mode 100644 index 0000000..6be915f --- /dev/null +++ b/icons/full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/load.svg b/icons/load.svg new file mode 100644 index 0000000..5968cdd --- /dev/null +++ b/icons/load.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/logo.svg b/icons/logo.svg new file mode 100644 index 0000000..4ecf1b3 --- /dev/null +++ b/icons/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/model.svg b/icons/model.svg new file mode 100644 index 0000000..128a1d8 --- /dev/null +++ b/icons/model.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/ok.svg b/icons/ok.svg new file mode 100644 index 0000000..58e528c --- /dev/null +++ b/icons/ok.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/outline.svg b/icons/outline.svg new file mode 100644 index 0000000..0d6e64b --- /dev/null +++ b/icons/outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/paint.svg b/icons/paint.svg new file mode 100644 index 0000000..041f3e8 --- /dev/null +++ b/icons/paint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/pan.svg b/icons/pan.svg new file mode 100644 index 0000000..15759f0 --- /dev/null +++ b/icons/pan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/qt.svg b/icons/qt.svg new file mode 100644 index 0000000..d5aab41 --- /dev/null +++ b/icons/qt.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/icons/settings.svg b/icons/settings.svg new file mode 100644 index 0000000..ea7e8e8 --- /dev/null +++ b/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/splash.png b/icons/splash.png new file mode 100644 index 0000000..82266d9 Binary files /dev/null and b/icons/splash.png differ diff --git a/icons/start.svg b/icons/start.svg new file mode 100644 index 0000000..19160dd --- /dev/null +++ b/icons/start.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/vector.svg b/icons/vector.svg new file mode 100644 index 0000000..4f88c95 --- /dev/null +++ b/icons/vector.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/zoomin.svg b/icons/zoomin.svg new file mode 100644 index 0000000..f830872 --- /dev/null +++ b/icons/zoomin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/zoomout.svg b/icons/zoomout.svg new file mode 100644 index 0000000..aa18703 --- /dev/null +++ b/icons/zoomout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lic/license.lic b/lic/license.lic new file mode 100644 index 0000000..175477c --- /dev/null +++ b/lic/license.lic @@ -0,0 +1 @@ +pGZJMmJtule8fwDCz4mnyHoQa7N6pl5GRdLqfoXREBqG4Xb1jbvgf7RmC8f1+sNpiCFSIt7NgvU362tKhB5UBXn/vUAadG1lOGC70dUhprGzBoqJN7VkAHkNGg0XjoE8H0SCVynr8To7ciwcnmK6HJXre6i+mBdTjACmKseTMlWp480XOt7uHysltORbTA3J \ No newline at end of file diff --git a/license.lic b/license.lic new file mode 100644 index 0000000..175477c --- /dev/null +++ b/license.lic @@ -0,0 +1 @@ +pGZJMmJtule8fwDCz4mnyHoQa7N6pl5GRdLqfoXREBqG4Xb1jbvgf7RmC8f1+sNpiCFSIt7NgvU362tKhB5UBXn/vUAadG1lOGC70dUhprGzBoqJN7VkAHkNGg0XjoE8H0SCVynr8To7ciwcnmK6HJXre6i+mBdTjACmKseTMlWp480XOt7uHysltORbTA3J \ No newline at end of file diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..549e839 --- /dev/null +++ b/main.py @@ -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() \ No newline at end of file diff --git a/mul/mulexport.py b/mul/mulexport.py new file mode 100644 index 0000000..f05d1fa --- /dev/null +++ b/mul/mulexport.py @@ -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) + diff --git a/mul/mulgrubcut.py b/mul/mulgrubcut.py new file mode 100644 index 0000000..181a884 --- /dev/null +++ b/mul/mulgrubcut.py @@ -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) + diff --git a/mul/mulstart.py b/mul/mulstart.py new file mode 100644 index 0000000..a132671 --- /dev/null +++ b/mul/mulstart.py @@ -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_()) + \ No newline at end of file diff --git a/res.qrc b/res.qrc new file mode 100644 index 0000000..07a812c --- /dev/null +++ b/res.qrc @@ -0,0 +1,26 @@ + + + icons/splash.png + icons/logo.svg + icons/load.svg + icons/settings.svg + icons/exit.svg + icons/vector.svg + icons/zoomin.svg + icons/zoomout.svg + icons/pan.svg + icons/full.svg + icons/start.svg + icons/clear.svg + icons/edit.svg + icons/export.svg + icons/model.svg + icons/assessment.svg + icons/paint.svg + icons/font.svg + icons/qt.svg + icons/ok.svg + icons/cancel.svg + icons/outline.svg + + diff --git a/test/tree.py b/test/tree.py new file mode 100644 index 0000000..2106dde --- /dev/null +++ b/test/tree.py @@ -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_()) \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..267231c --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,2 @@ +from .setting import Settings +from .license import LicenseHelper \ No newline at end of file diff --git a/utils/license.py b/utils/license.py new file mode 100644 index 0000000..c731d90 --- /dev/null +++ b/utils/license.py @@ -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')) + # tips:utf-8编码时,英文占1个byte,而中文占3个byte + padding_size = length if(bytes_length == length) else bytes_length + padding = bs - padding_size % bs + # tips:chr(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) + + \ No newline at end of file diff --git a/utils/menu.py b/utils/menu.py new file mode 100644 index 0000000..fc0eefb --- /dev/null +++ b/utils/menu.py @@ -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 \ No newline at end of file diff --git a/utils/project.py b/utils/project.py new file mode 100644 index 0000000..59a0e73 --- /dev/null +++ b/utils/project.py @@ -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 \ No newline at end of file diff --git a/utils/setting.py b/utils/setting.py new file mode 100644 index 0000000..3dd71a1 --- /dev/null +++ b/utils/setting.py @@ -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', './') + \ No newline at end of file