|
|
"""Main plugin file - entry point for the plugin. |
|
|
|
|
|
Links the UI and the processing. |
|
|
|
|
|
Skeleton of this file was generate with the QGis plugin to create plugin skeleton - QGIS PluginBuilder |
|
|
""" |
|
|
|
|
|
import logging |
|
|
import traceback |
|
|
|
|
|
from qgis.core import Qgis, QgsApplication, QgsProject, QgsVectorLayer |
|
|
from qgis.gui import QgisInterface |
|
|
from qgis.PyQt.QtCore import QCoreApplication, Qt |
|
|
from qgis.PyQt.QtGui import QIcon |
|
|
from qgis.PyQt.QtWidgets import QAction, QMessageBox |
|
|
|
|
|
from deepness.common.defines import IS_DEBUG, PLUGIN_NAME |
|
|
from deepness.common.lazy_package_loader import LazyPackageLoader |
|
|
from deepness.common.processing_parameters.map_processing_parameters import MapProcessingParameters, ProcessedAreaType |
|
|
from deepness.common.processing_parameters.training_data_export_parameters import TrainingDataExportParameters |
|
|
from deepness.deepness_dockwidget import DeepnessDockWidget |
|
|
from deepness.dialogs.resizable_message_box import ResizableMessageBox |
|
|
from deepness.images.get_image_path import get_icon_path |
|
|
from deepness.processing.map_processor.map_processing_result import (MapProcessingResult, MapProcessingResultCanceled, |
|
|
MapProcessingResultFailed, |
|
|
MapProcessingResultSuccess) |
|
|
from deepness.processing.map_processor.map_processor_training_data_export import MapProcessorTrainingDataExport |
|
|
|
|
|
cv2 = LazyPackageLoader('cv2') |
|
|
|
|
|
|
|
|
class Deepness: |
|
|
""" QGIS Plugin Implementation - main class of the plugin. |
|
|
Creates the UI classes and processing models and links them together. |
|
|
""" |
|
|
|
|
|
def __init__(self, iface: QgisInterface): |
|
|
""" |
|
|
:param iface: An interface instance that will be passed to this class |
|
|
which provides the hook by which you can manipulate the QGIS |
|
|
application at run time. |
|
|
:type iface: QgsInterface |
|
|
""" |
|
|
self.iface = iface |
|
|
|
|
|
|
|
|
self.actions = [] |
|
|
self.menu = self.tr(u'&Deepness') |
|
|
|
|
|
self.toolbar = self.iface.addToolBar(u'Deepness') |
|
|
self.toolbar.setObjectName(u'Deepness') |
|
|
|
|
|
self.pluginIsActive = False |
|
|
self.dockwidget = None |
|
|
self._map_processor = None |
|
|
|
|
|
|
|
|
def tr(self, message): |
|
|
"""Get the translation for a string using Qt translation API. |
|
|
|
|
|
We implement this ourselves since we do not inherit QObject. |
|
|
|
|
|
:param message: String for translation. |
|
|
:type message: str, QString |
|
|
|
|
|
:returns: Translated version of message. |
|
|
:rtype: QString |
|
|
""" |
|
|
|
|
|
return QCoreApplication.translate('Deepness', message) |
|
|
|
|
|
def add_action( |
|
|
self, |
|
|
icon_path, |
|
|
text, |
|
|
callback, |
|
|
enabled_flag=True, |
|
|
add_to_menu=True, |
|
|
add_to_toolbar=True, |
|
|
status_tip=None, |
|
|
whats_this=None, |
|
|
parent=None): |
|
|
"""Add a toolbar icon to the toolbar. |
|
|
|
|
|
:param icon_path: Path to the icon for this action. Can be a resource |
|
|
path (e.g. ':/plugins/foo/bar.png') or a normal file system path. |
|
|
:type icon_path: str |
|
|
|
|
|
:param text: Text that should be shown in menu items for this action. |
|
|
:type text: str |
|
|
|
|
|
:param callback: Function to be called when the action is triggered. |
|
|
:type callback: function |
|
|
|
|
|
:param enabled_flag: A flag indicating if the action should be enabled |
|
|
by default. Defaults to True. |
|
|
:type enabled_flag: bool |
|
|
|
|
|
:param add_to_menu: Flag indicating whether the action should also |
|
|
be added to the menu. Defaults to True. |
|
|
:type add_to_menu: bool |
|
|
|
|
|
:param add_to_toolbar: Flag indicating whether the action should also |
|
|
be added to the toolbar. Defaults to True. |
|
|
:type add_to_toolbar: bool |
|
|
|
|
|
:param status_tip: Optional text to show in a popup when mouse pointer |
|
|
hovers over the action. |
|
|
:type status_tip: str |
|
|
|
|
|
:param parent: Parent widget for the new action. Defaults None. |
|
|
:type parent: QWidget |
|
|
|
|
|
:param whats_this: Optional text to show in the status bar when the |
|
|
mouse pointer hovers over the action. |
|
|
|
|
|
:returns: The action that was created. Note that the action is also |
|
|
added to self.actions list. |
|
|
:rtype: QAction |
|
|
""" |
|
|
|
|
|
icon = QIcon(icon_path) |
|
|
action = QAction(icon, text, parent) |
|
|
action.triggered.connect(callback) |
|
|
action.setEnabled(enabled_flag) |
|
|
|
|
|
if status_tip is not None: |
|
|
action.setStatusTip(status_tip) |
|
|
|
|
|
if whats_this is not None: |
|
|
action.setWhatsThis(whats_this) |
|
|
|
|
|
if add_to_toolbar: |
|
|
self.toolbar.addAction(action) |
|
|
|
|
|
if add_to_menu: |
|
|
self.iface.addPluginToMenu( |
|
|
self.menu, |
|
|
action) |
|
|
|
|
|
self.actions.append(action) |
|
|
|
|
|
return action |
|
|
|
|
|
def initGui(self): |
|
|
"""Create the menu entries and toolbar icons inside the QGIS GUI.""" |
|
|
|
|
|
icon_path = get_icon_path() |
|
|
self.add_action( |
|
|
icon_path, |
|
|
text=self.tr(u'Deepness'), |
|
|
callback=self.run, |
|
|
parent=self.iface.mainWindow()) |
|
|
|
|
|
if IS_DEBUG: |
|
|
self.run() |
|
|
|
|
|
def onClosePlugin(self): |
|
|
"""Cleanup necessary items here when plugin dockwidget is closed""" |
|
|
|
|
|
|
|
|
self.dockwidget.closingPlugin.disconnect(self.onClosePlugin) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.pluginIsActive = False |
|
|
|
|
|
def unload(self): |
|
|
"""Removes the plugin menu item and icon from QGIS GUI.""" |
|
|
|
|
|
for action in self.actions: |
|
|
self.iface.removePluginMenu( |
|
|
self.tr(u'&Deepness'), |
|
|
action) |
|
|
self.iface.removeToolBarIcon(action) |
|
|
|
|
|
del self.toolbar |
|
|
|
|
|
def _layers_changed(self, _): |
|
|
pass |
|
|
|
|
|
def run(self): |
|
|
"""Run method that loads and starts the plugin""" |
|
|
|
|
|
if not self.pluginIsActive: |
|
|
self.pluginIsActive = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.dockwidget is None: |
|
|
|
|
|
self.dockwidget = DeepnessDockWidget(self.iface) |
|
|
self._layers_changed(None) |
|
|
QgsProject.instance().layersAdded.connect(self._layers_changed) |
|
|
QgsProject.instance().layersRemoved.connect(self._layers_changed) |
|
|
|
|
|
|
|
|
self.dockwidget.closingPlugin.connect(self.onClosePlugin) |
|
|
self.dockwidget.run_model_inference_signal.connect(self._run_model_inference) |
|
|
self.dockwidget.run_training_data_export_signal.connect(self._run_training_data_export) |
|
|
|
|
|
self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dockwidget) |
|
|
self.dockwidget.show() |
|
|
|
|
|
def _are_map_processing_parameters_are_correct(self, params: MapProcessingParameters): |
|
|
if self._map_processor and self._map_processor.is_busy(): |
|
|
msg = "Error! Processing already in progress! Please wait or cancel previous task." |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Critical, duration=7) |
|
|
return False |
|
|
|
|
|
rlayer = QgsProject.instance().mapLayers()[params.input_layer_id] |
|
|
if rlayer is None: |
|
|
msg = "Error! Please select the layer to process first!" |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Critical, duration=7) |
|
|
return False |
|
|
|
|
|
if isinstance(rlayer, QgsVectorLayer): |
|
|
msg = "Error! Please select a raster layer (vector layer selected)" |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Critical, duration=7) |
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
def _display_processing_started_info(self): |
|
|
msg = "Processing in progress... Cool! It's tea time!" |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Info, duration=2) |
|
|
|
|
|
def _run_training_data_export(self, training_data_export_parameters: TrainingDataExportParameters): |
|
|
if not self._are_map_processing_parameters_are_correct(training_data_export_parameters): |
|
|
return |
|
|
|
|
|
vlayer = None |
|
|
|
|
|
rlayer = QgsProject.instance().mapLayers()[training_data_export_parameters.input_layer_id] |
|
|
if training_data_export_parameters.processed_area_type == ProcessedAreaType.FROM_POLYGONS: |
|
|
vlayer = QgsProject.instance().mapLayers()[training_data_export_parameters.mask_layer_id] |
|
|
|
|
|
self._map_processor = MapProcessorTrainingDataExport( |
|
|
rlayer=rlayer, |
|
|
vlayer_mask=vlayer, |
|
|
map_canvas=self.iface.mapCanvas(), |
|
|
params=training_data_export_parameters) |
|
|
self._map_processor.finished_signal.connect(self._map_processor_finished) |
|
|
self._map_processor.show_img_signal.connect(self._show_img) |
|
|
QgsApplication.taskManager().addTask(self._map_processor) |
|
|
self._display_processing_started_info() |
|
|
|
|
|
def _run_model_inference(self, params: MapProcessingParameters): |
|
|
from deepness.processing.models.model_types import ModelDefinition |
|
|
|
|
|
if not self._are_map_processing_parameters_are_correct(params): |
|
|
return |
|
|
|
|
|
vlayer = None |
|
|
|
|
|
rlayer = QgsProject.instance().mapLayers()[params.input_layer_id] |
|
|
if params.processed_area_type == ProcessedAreaType.FROM_POLYGONS: |
|
|
vlayer = QgsProject.instance().mapLayers()[params.mask_layer_id] |
|
|
|
|
|
model_definition = ModelDefinition.get_definition_for_params(params) |
|
|
map_processor_class = model_definition.map_processor_class |
|
|
|
|
|
self._map_processor = map_processor_class( |
|
|
rlayer=rlayer, |
|
|
vlayer_mask=vlayer, |
|
|
map_canvas=self.iface.mapCanvas(), |
|
|
params=params) |
|
|
self._map_processor.finished_signal.connect(self._map_processor_finished) |
|
|
self._map_processor.show_img_signal.connect(self._show_img) |
|
|
QgsApplication.taskManager().addTask(self._map_processor) |
|
|
self._display_processing_started_info() |
|
|
|
|
|
@staticmethod |
|
|
def _show_img(img_rgb, window_name: str): |
|
|
""" Helper function to show an image while developing and debugging the plugin """ |
|
|
|
|
|
|
|
|
img_bgr = img_rgb[..., ::-1] |
|
|
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) |
|
|
cv2.resizeWindow(window_name, 800, 800) |
|
|
cv2.imshow(window_name, img_bgr) |
|
|
cv2.waitKey(1) |
|
|
|
|
|
def _map_processor_finished(self, result: MapProcessingResult): |
|
|
""" Slot for finished processing of the ortophoto """ |
|
|
if isinstance(result, MapProcessingResultFailed): |
|
|
msg = f'Error! Processing error: "{result.message}"!' |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Critical, duration=14) |
|
|
if result.exception is not None: |
|
|
logging.error(msg) |
|
|
trace = '\n'.join(traceback.format_tb(result.exception.__traceback__)[-1:]) |
|
|
msg = f'{msg}\n\n\n' \ |
|
|
f'Details: ' \ |
|
|
f'{str(result.exception.__class__.__name__)} - {result.exception}\n' \ |
|
|
f'Last Traceback: \n' \ |
|
|
f'{trace}' |
|
|
QMessageBox.critical(self.dockwidget, "Unhandled exception", msg) |
|
|
elif isinstance(result, MapProcessingResultCanceled): |
|
|
msg = f'Info! Processing canceled by user!' |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Info, duration=7) |
|
|
elif isinstance(result, MapProcessingResultSuccess): |
|
|
msg = 'Processing finished!' |
|
|
self.iface.messageBar().pushMessage(PLUGIN_NAME, msg, level=Qgis.Success, duration=3) |
|
|
message_to_show = result.message |
|
|
|
|
|
msgBox = ResizableMessageBox(self.dockwidget) |
|
|
msgBox.setWindowTitle("Processing Result") |
|
|
msgBox.setText(message_to_show) |
|
|
msgBox.setStyleSheet("QLabel{min-width:800 px; font-size: 24px;} QPushButton{ width:250px; font-size: 18px; }") |
|
|
msgBox.exec() |
|
|
|
|
|
self._map_processor = None |
|
|
|