Skip to content
Snippets Groups Projects
Commit b12c31c8 authored by paul.tresson_ird.fr's avatar paul.tresson_ird.fr
Browse files

add main code (untested yet)

parent 8b80f921
No related branches found
No related tags found
No related merge requests found
Showing
with 3441 additions and 0 deletions
import os
import inspect
cmd_folder = os.path.split(inspect.getfile(inspect.currentframe()))[0]
def classFactory(iface):
from .dialogs.packages_installer import packages_installer_dialog
packages_installer_dialog.check_required_packages_and_install_if_necessary(iface=iface)
from .iamap import IAMap
return IAMap(iface, cmd_folder)
import os
import numpy as np
from pathlib import Path
from typing import Dict, Any
import joblib
import rasterio
from rasterio import windows
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (Qgis,
QgsGeometry,
QgsProcessingParameterBoolean,
QgsProcessingParameterEnum,
QgsCoordinateTransform,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterFolderDestination,
QgsProcessingParameterBand,
QgsProcessingParameterNumber,
QgsProcessingParameterExtent,
QgsProcessingParameterCrs,
QgsProcessingParameterDefinition,
)
from sklearn.cluster import KMeans
import json
class ClusterAlgorithm(QgsProcessingAlgorithm):
"""
"""
INPUT = 'INPUT'
BANDS = 'BANDS'
EXTENT = 'EXTENT'
LOAD = 'LOAD'
OUTPUT = 'OUTPUT'
RESOLUTION = 'RESOLUTION'
CRS = 'CRS'
CLUSTERS = 'CLUSTERS'
SUBSET = 'SUBSET'
METHOD = 'METHOD'
SAVE_MODEL = 'SAVE_MODEL'
def initAlgorithm(self, config=None):
"""
Here we define the inputs and output of the algorithm, along
with some other properties.
"""
cwd = Path(__file__).parent.absolute()
self.addParameter(
QgsProcessingParameterRasterLayer(
name=self.INPUT,
description=self.tr(
'Input raster layer or image file path'),
defaultValue=os.path.join(cwd,'rasters','test.tif'),
),
)
self.addParameter(
QgsProcessingParameterBand(
name=self.BANDS,
description=self.tr(
'Selected Bands (defaults to all bands selected)'),
defaultValue=None,
parentLayerParameterName=self.INPUT,
optional=True,
allowMultiple=True,
)
)
crs_param = QgsProcessingParameterCrs(
name=self.CRS,
description=self.tr('Target CRS (default to original CRS)'),
optional=True,
)
res_param = QgsProcessingParameterNumber(
name=self.RESOLUTION,
description=self.tr(
'Target resolution in meters (default to native resolution)'),
type=QgsProcessingParameterNumber.Double,
optional=True,
minValue=0,
maxValue=100000
)
self.addParameter(
QgsProcessingParameterExtent(
name=self.EXTENT,
description=self.tr(
'Processing extent (default to the entire image)'),
optional=True
)
)
self.method_opt = ['K-means', '--Empty--']
self.addParameter (
QgsProcessingParameterEnum(
name = self.METHOD,
description = self.tr(
'Method for the dimension reduction'),
defaultValue = 0,
options = self.method_opt,
)
)
self.addParameter(
QgsProcessingParameterNumber(
name=self.CLUSTERS,
description=self.tr(
'Number of target clusters'),
type=QgsProcessingParameterNumber.Integer,
defaultValue = 5,
minValue=1,
maxValue=1024
)
)
subset_param = QgsProcessingParameterNumber(
name=self.SUBSET,
description=self.tr(
'Select a subset of random pixels of the image to fit transform'),
type=QgsProcessingParameterNumber.Integer,
defaultValue=None,
minValue=1,
maxValue=10_000,
optional=True,
)
save_param = QgsProcessingParameterBoolean(
self.SAVE_MODEL,
self.tr("Save projection model after fit."),
defaultValue=True
)
self.addParameter(
QgsProcessingParameterFolderDestination(
self.OUTPUT,
self.tr(
"Output directory (choose the location that the image features will be saved)"),
defaultValue=os.path.join(cwd,'features'),
)
)
for param in (crs_param, res_param, subset_param, save_param):
param.setFlags(
param.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(param)
def processAlgorithm(self, parameters, context, feedback):
"""
Here is where the processing itself takes place.
"""
self.process_options(parameters, context, feedback)
input_bands = [i_band -1 for i_band in self.selected_bands]
if self.method == 'K-means':
proj = KMeans(int(self.nclusters))
save_file = 'kmeans_cluster.pkl'
params = proj.get_params()
out_path = os.path.join(self.output_dir, save_file)
with rasterio.open(self.rlayer_path) as ds:
transform = ds.transform
crs = ds.crs
win = windows.from_bounds(
self.extent.xMinimum(),
self.extent.yMinimum(),
self.extent.xMaximum(),
self.extent.yMaximum(),
transform=transform
)
raster = ds.read(window=win)
transform = ds.window_transform(win)
raster = np.transpose(raster, (1,2,0))
raster = raster[:,:,input_bands]
feedback.pushInfo(f'{raster.shape}')
feedback.pushInfo(f'{raster.reshape(-1, raster.shape[0]).shape}')
if self.subset:
feedback.pushInfo(f'Using a random subset of {self.subset} pixels')
fit_raster = raster.reshape(-1, raster.shape[-1])
nsamples = fit_raster.shape[0]
# Generate random indices to select subset_size number of samples
np.random.seed(42)
random_indices = np.random.choice(nsamples, size=self.subset, replace=False)
fit_raster = fit_raster[random_indices,:]
feedback.pushInfo(f'Starting fit\n')
proj.fit(fit_raster)
if self.save_model:
joblib.dump(proj, out_path)
feedback.pushInfo(f'starting inference\n')
proj_img = proj.predict(raster.reshape(-1, raster.shape[-1]))
else:
proj_img = proj.fit_predict(raster.reshape(-1, raster.shape[-1]))
if self.save_model:
joblib.dump(proj, out_path)
proj_img = proj_img.reshape((raster.shape[0], raster.shape[1],-1))
height, width, channels = proj_img.shape
dst_path = os.path.join(self.output_dir,'cluster.tif')
params_file = os.path.join(self.output_dir, 'cluster_parameters.json')
if os.path.exists(dst_path):
i = 1
while True:
modified_output_file = os.path.join(self.output_dir, f"cluster_{i}.tif")
if not os.path.exists(modified_output_file):
dst_path = modified_output_file
break
i += 1
if os.path.exists(params_file):
i = 1
while True:
modified_output_file_params = os.path.join(self.output_dir, f"cluster_parameters_{i}.json")
if not os.path.exists(modified_output_file_params):
params_file = modified_output_file_params
break
i += 1
with rasterio.open(dst_path, 'w', driver='GTiff',
height=height, width=width, count=channels, dtype='int8',
crs=crs, transform=transform) as dst_ds:
dst_ds.write(np.transpose(proj_img, (2, 0, 1)))
with open(params_file, 'w') as f:
json.dump(params, f, indent=4)
feedback.pushInfo(f"Parameters saved to {params_file}")
parameters['OUTPUT_RASTER']=dst_path
return {'OUTPUT_RASTER':dst_path}
def process_options(self,parameters, context, feedback):
self.iPatch = 0
self.feature_dir = ""
feedback.pushInfo(
f'PARAMETERS :\n{parameters}')
feedback.pushInfo(
f'CONTEXT :\n{context}')
feedback.pushInfo(
f'FEEDBACK :\n{feedback}')
rlayer = self.parameterAsRasterLayer(
parameters, self.INPUT, context)
if rlayer is None:
raise QgsProcessingException(
self.invalidRasterError(parameters, self.INPUT))
self.selected_bands = self.parameterAsInts(
parameters, self.BANDS, context)
if len(self.selected_bands) == 0:
self.selected_bands = list(range(1, rlayer.bandCount()+1))
if max(self.selected_bands) > rlayer.bandCount():
raise QgsProcessingException(
self.tr("The chosen bands exceed the largest band number!")
)
self.nclusters = self.parameterAsInt(
parameters, self.CLUSTERS, context)
self.subset = self.parameterAsInt(
parameters, self.SUBSET, context)
method_idx = self.parameterAsEnum(
parameters, self.METHOD, context)
self.method = self.method_opt[method_idx]
res = self.parameterAsDouble(
parameters, self.RESOLUTION, context)
crs = self.parameterAsCrs(
parameters, self.CRS, context)
extent = self.parameterAsExtent(
parameters, self.EXTENT, context)
self.output_dir = self.parameterAsString(
parameters, self.OUTPUT, context)
self.save_model = self.parameterAsBoolean(
parameters, self.SAVE_MODEL, context)
rlayer_data_provider = rlayer.dataProvider()
# handle crs
if crs is None or not crs.isValid():
crs = rlayer.crs()
"""
feedback.pushInfo(
f'Layer CRS unit is {crs.mapUnits()}') # 0 for meters, 6 for degrees, 9 for unknown
feedback.pushInfo(
f'whether the CRS is a geographic CRS (using lat/lon coordinates) {crs.isGeographic()}')
if crs.mapUnits() == Qgis.DistanceUnit.Degrees:
crs = self.estimate_utm_crs(rlayer.extent())
# target crs should use meters as units
if crs.mapUnits() != Qgis.DistanceUnit.Meters:
feedback.pushInfo(
f'Layer CRS unit is {crs.mapUnits()}')
feedback.pushInfo(
f'whether the CRS is a geographic CRS (using lat/lon coordinates) {crs.isGeographic()}')
raise QgsProcessingException(
self.tr("Only support CRS with the units as meters")
)
"""
# 0 for meters, 6 for degrees, 9 for unknown
UNIT_METERS = 0
UNIT_DEGREES = 6
if rlayer.crs().mapUnits() == UNIT_DEGREES: # Qgis.DistanceUnit.Degrees:
layer_units = 'degrees'
else:
layer_units = 'meters'
# if res is not provided, get res info from rlayer
if np.isnan(res) or res == 0:
res = rlayer.rasterUnitsPerPixelX() # rasterUnitsPerPixelY() is negative
target_units = layer_units
else:
# when given res in meters by users, convert crs to utm if the original crs unit is degree
if crs.mapUnits() != UNIT_METERS: # Qgis.DistanceUnit.Meters:
if rlayer.crs().mapUnits() == UNIT_DEGREES: # Qgis.DistanceUnit.Degrees:
# estimate utm crs based on layer extent
crs = self.estimate_utm_crs(rlayer.extent())
else:
raise QgsProcessingException(
f"Resampling of image with the CRS of {crs.authid()} in meters is not supported.")
target_units = 'meters'
# else:
# res = (rlayer_extent.xMaximum() -
# rlayer_extent.xMinimum()) / rlayer.width()
self.res = res
# handle extent
if extent.isNull():
extent = rlayer.extent() # QgsProcessingUtils.combineLayerExtents(layers, crs, context)
extent_crs = rlayer.crs()
else:
if extent.isEmpty():
raise QgsProcessingException(
self.tr("The extent for processing can not be empty!"))
extent_crs = self.parameterAsExtentCrs(
parameters, self.EXTENT, context)
# if extent crs != target crs, convert it to target crs
if extent_crs != crs:
transform = QgsCoordinateTransform(
extent_crs, crs, context.transformContext())
# extent = transform.transformBoundingBox(extent)
# to ensure coverage of the transformed extent
# convert extent to polygon, transform polygon, then get boundingBox of the new polygon
extent_polygon = QgsGeometry.fromRect(extent)
extent_polygon.transform(transform)
extent = extent_polygon.boundingBox()
extent_crs = crs
# check intersects between extent and rlayer_extent
if rlayer.crs() != crs:
transform = QgsCoordinateTransform(
rlayer.crs(), crs, context.transformContext())
rlayer_extent = transform.transformBoundingBox(
rlayer.extent())
else:
rlayer_extent = rlayer.extent()
if not rlayer_extent.intersects(extent):
raise QgsProcessingException(
self.tr("The extent for processing is not intersected with the input image!"))
img_width_in_extent = round(
(extent.xMaximum() - extent.xMinimum())/self.res)
img_height_in_extent = round(
(extent.yMaximum() - extent.yMinimum())/self.res)
# Send some information to the user
feedback.pushInfo(
f'Layer path: {rlayer_data_provider.dataSourceUri()}')
# feedback.pushInfo(
# f'Layer band scale: {rlayer_data_provider.bandScale(self.selected_bands[0])}')
feedback.pushInfo(f'Layer name: {rlayer.name()}')
if rlayer.crs().authid():
feedback.pushInfo(f'Layer CRS: {rlayer.crs().authid()}')
else:
feedback.pushInfo(
f'Layer CRS in WKT format: {rlayer.crs().toWkt()}')
feedback.pushInfo(
f'Layer pixel size: {rlayer.rasterUnitsPerPixelX()}, {rlayer.rasterUnitsPerPixelY()} {layer_units}')
feedback.pushInfo(f'Bands selected: {self.selected_bands}')
if crs.authid():
feedback.pushInfo(f'Target CRS: {crs.authid()}')
else:
feedback.pushInfo(f'Target CRS in WKT format: {crs.toWkt()}')
feedback.pushInfo(f'Target resolution: {self.res} {target_units}')
feedback.pushInfo(
(f'Processing extent: minx:{extent.xMinimum():.6f}, maxx:{extent.xMaximum():.6f},'
f'miny:{extent.yMinimum():.6f}, maxy:{extent.yMaximum():.6f}'))
feedback.pushInfo(
(f'Processing image size: (width {img_width_in_extent}, '
f'height {img_height_in_extent})'))
self.rlayer_path = rlayer.dataProvider().dataSourceUri()
feedback.pushInfo(f'Selected bands: {self.selected_bands}')
## passing parameters to self once everything has been processed
self.extent = extent
self.rlayer = rlayer
self.crs = crs
# used to handle any thread-sensitive cleanup which is required by the algorithm.
def postProcessAlgorithm(self, context, feedback) -> Dict[str, Any]:
return {}
def tr(self, string):
"""
Returns a translatable string with the self.tr() function.
"""
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ClusterAlgorithm()
def name(self):
"""
Returns the algorithm name, used for identifying the algorithm. This
string should be fixed for the algorithm, and must not be localised.
The name should be unique within each provider. Names should contain
lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'cluster'
def displayName(self):
"""
Returns the translated algorithm name, which should be used for any
user-visible display of the algorithm name.
"""
return self.tr('Clustering')
def group(self):
"""
Returns the name of the group this algorithm belongs to. This string
should be localised.
"""
return self.tr('')
def groupId(self):
"""
Returns the unique ID of the group this algorithm belongs to. This
string should be fixed for the algorithm, and must not be localised.
The group id should be unique within each provider. Group id should
contain lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return ''
def shortHelpString(self):
"""
Returns a localised short helper string for the algorithm. This string
should provide a basic description about what the algorithm does and the
parameters and outputs associated with it..
"""
return self.tr("Cluster a raster.")
def icon(self):
return 'E'
"""
This QGIS plugin requires some Python packages to be installed and available.
This tool allows to install them in a local directory, if they are not installed yet.
"""
import importlib
import logging
import os
import subprocess
import sys
import traceback
import urllib
from dataclasses import dataclass
from pathlib import Path
from threading import Thread
from typing import List
from qgis.PyQt import QtCore, uic
from qgis.PyQt.QtCore import pyqtSignal
from qgis.PyQt.QtGui import QCloseEvent
from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QTextBrowser
# from deepness.common.defines import PLUGIN_NAME
PLUGIN_NAME = "iamap"
PYTHON_VERSION = sys.version_info
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PLUGIN_ROOT_DIR = os.path.realpath(os.path.abspath(os.path.join(SCRIPT_DIR, '..', '..')))
PACKAGES_INSTALL_DIR = os.path.join(PLUGIN_ROOT_DIR, f'python{PYTHON_VERSION.major}.{PYTHON_VERSION.minor}')
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'packages_installer_dialog.ui'))
_ERROR_COLOR = '#ff0000'
@dataclass
class PackageToInstall:
name: str
version: str
import_name: str # name while importing package
def __str__(self):
return f'{self.name}{self.version}'
# REQUIREMENTS_PATH = os.path.join(PLUGIN_ROOT_DIR, 'python_requirements/requirements.txt')
REQUIREMENTS_PATH = os.path.join(PLUGIN_ROOT_DIR, 'requirements.txt')
with open(REQUIREMENTS_PATH, 'r') as f:
raw_txt = f.read()
libraries_versions = {}
for line in raw_txt.split('\n'):
if line.startswith('#') or not line.strip():
continue
line = line.split(';')[0]
if '==' in line:
lib, version = line.split('==')
libraries_versions[lib] = '==' + version
elif '>=' in line:
lib, version = line.split('>=')
libraries_versions[lib] = '>=' + version
elif '<=' in line:
lib, version = line.split('<=')
libraries_versions[lib] = '<=' + version
else:
libraries_versions[line] = ''
packages_to_install = []
for lib, version in libraries_versions.items():
import_name = lib[:-1]
if lib == 'scikit-learn ':
import_name = 'sklearn'
if lib == 'umap-learn ':
import_name = 'umap'
packages_to_install.append(
PackageToInstall(
name=lib,
version=version,
import_name=import_name
)
)
# packages_to_install = [
# PackageToInstall(name='scikit-learn', version=libraries_versions['scikit-learn'], import_name='sklearn'),
# ]
if sys.platform == "linux" or sys.platform == "linux2":
# packages_to_install += [
# PackageToInstall(name='ime-gpu', version=libraries_versions['onnxruntime-gpu'], import_name='onnxruntime'),
# ]
PYTHON_EXECUTABLE_PATH = sys.executable
elif sys.platform == "darwin": # MacOS
# packages_to_install += [
# PackageToInstall(name='onnxruntime', version=libraries_versions['onnxruntime-gpu'], import_name='onnxruntime'),
# ]
PYTHON_EXECUTABLE_PATH = str(Path(sys.prefix) / 'bin' / 'python3') # sys.executable yields QGIS in macOS
elif sys.platform == "win32":
# packages_to_install += [
# PackageToInstall(name='onnxruntime', version=libraries_versions['onnxruntime-gpu'], import_name='onnxruntime'),
# ]
PYTHON_EXECUTABLE_PATH = 'python' # sys.executable yields QGis.exe in Windows
else:
raise Exception("Unsupported operating system!")
class PackagesInstallerDialog(QDialog, FORM_CLASS):
"""
Dialog witch controls the installation process of packages.
UI design defined in the `packages_installer_dialog.ui` file.
"""
signal_log_line = pyqtSignal(str) # we need to use signal because we cannot edit GUI from another thread
INSTALLATION_IN_PROGRESS = False # to make sure we will not start the installation twice
def __init__(self, iface, parent=None):
super(PackagesInstallerDialog, self).__init__(parent)
self.setupUi(self)
self.iface = iface
self.tb = self.textBrowser_log # type: QTextBrowser
self._create_connections()
self._setup_message()
self.aborted = False
self.thread = None
def move_to_top(self):
""" Move the window to the top.
Although if installed from plugin manager, the plugin manager will move itself to the top anyway.
"""
self.setWindowState((self.windowState() & ~QtCore.Qt.WindowMinimized) | QtCore.Qt.WindowActive)
if sys.platform == "linux" or sys.platform == "linux2":
pass
elif sys.platform == "darwin": # MacOS
self.raise_() # FIXME: this does not really work, the window is still behind the plugin manager
elif sys.platform == "win32":
self.activateWindow()
else:
raise Exception("Unsupported operating system!")
def _create_connections(self):
self.pushButton_close.clicked.connect(self.close)
self.pushButton_install_packages.clicked.connect(self._run_packages_installation)
self.signal_log_line.connect(self._log_line)
def _log_line(self, txt):
txt = txt \
.replace(' ', '&nbsp;&nbsp;') \
.replace('\n', '<br>')
self.tb.append(txt)
def log(self, txt):
self.signal_log_line.emit(txt)
def _setup_message(self) -> None:
self.log(f'<h2><span style="color: #000080;"><strong> '
f'Plugin {PLUGIN_NAME} - Packages installer </strong></span></h2> \n'
f'\n'
f'<b>This plugin requires the following Python packages to be installed:</b>')
for package in packages_to_install:
self.log(f'\t- {package.name}{package.version}')
self.log('\n\n'
f'If this packages are not installed in the global environment '
f'(or environment in which QGIS is started) '
f'you can install these packages in the local directory (which is included to the Python path).\n\n'
f'This Dialog does it for you! (Though you can still install these packages manually instead).\n'
f'<b>Please click "Install packages" button below to install them automatically, </b>'
f'or "Test and Close" if you installed them manually...\n')
def _run_packages_installation(self):
if self.INSTALLATION_IN_PROGRESS:
self.log(f'Error! Installation already in progress, cannot start again!')
return
self.aborted = False
self.INSTALLATION_IN_PROGRESS = True
self.thread = Thread(target=self._install_packages)
self.thread.start()
def _install_packages(self) -> None:
self.log('\n\n')
self.log('=' * 60)
self.log(f'<h3><b>Attempting to install required packages...</b></h3>')
os.makedirs(PACKAGES_INSTALL_DIR, exist_ok=True)
self._install_pip_if_necessary()
self.log(f'<h3><b>Attempting to install required packages...</b></h3>\n')
try:
self._pip_install_packages(packages_to_install)
except Exception as e:
msg = (f'\n <span style="color: {_ERROR_COLOR};"><b> '
f'Packages installation failed with exception: {e}!\n'
f'Please try to install the packages again. </b></span>'
f'\nCheck if there is no error related to system packages, '
f'which may be required to be installed by your system package manager, e.g. "apt". '
f'Copy errors from the stack above and google for possible solutions. '
f'Please report these as an issue on the plugin repository tracker!')
self.log(msg)
# finally, validate the installation, if there was no error so far...
self.log('\n\n <b>Installation of required packages finished. Validating installation...</b>')
self._check_packages_installation_and_log()
self.INSTALLATION_IN_PROGRESS = False
def reject(self) -> None:
self.close()
def closeEvent(self, event: QCloseEvent):
self.aborted = True
if self._check_packages_installation_and_log():
event.accept()
return
res = QMessageBox.question(self.iface.mainWindow(),
f'{PLUGIN_NAME} - skip installation?',
'Are you sure you want to abort the installation of the required python packages? '
'The plugin may not function correctly without them!',
QMessageBox.No, QMessageBox.Yes)
log_msg = 'User requested to close the dialog, but the packages are not installed correctly!\n'
if res == QMessageBox.Yes:
log_msg += 'And the user confirmed to close the dialog, knowing the risk!'
event.accept()
else:
log_msg += 'The user reconsidered their decision, and will try to install the packages again!'
event.ignore()
log_msg += '\n'
self.log(log_msg)
def _install_pip_if_necessary(self):
"""
Install pip if not present.
It happens e.g. in flatpak applications.
TODO - investigate whether we can also install pip in local directory
"""
self.log(f'<h4><b>Making sure pip is installed...</b></h4>')
if check_pip_installed():
self.log(f'<em>Pip is installed, skipping installation...</em>\n')
return
install_pip_command = [PYTHON_EXECUTABLE_PATH, '-m', 'ensurepip']
self.log(f'<em>Running command to install pip: \n $ {" ".join(install_pip_command)} </em>')
with subprocess.Popen(install_pip_command,
stdout=subprocess.PIPE,
universal_newlines=True,
stderr=subprocess.STDOUT,
env={'SETUPTOOLS_USE_DISTUTILS': 'stdlib'}) as process:
try:
self._do_process_output_logging(process)
except InterruptedError as e:
self.log(str(e))
return False
if process.returncode != 0:
msg = (f'<span style="color: {_ERROR_COLOR};"><b>'
f'pip installation failed! Consider installing it manually.'
f'<b></span>')
self.log(msg)
self.log('\n')
def _pip_install_packages(self, packages: List[PackageToInstall]) -> None:
cmd = [PYTHON_EXECUTABLE_PATH, '-m', 'pip', 'install', '-U', f'--target={PACKAGES_INSTALL_DIR}']
cmd_string = ' '.join(cmd)
for pck in packages:
cmd.append(f"{pck}")
cmd_string += f"{pck}"
self.log(f'<em>Running command: \n $ {cmd_string} </em>')
with subprocess.Popen(cmd,
stdout=subprocess.PIPE,
universal_newlines=True,
stderr=subprocess.STDOUT) as process:
self._do_process_output_logging(process)
if process.returncode != 0:
raise RuntimeError('Installation with pip failed')
msg = (f'\n<b>'
f'Packages installed correctly!'
f'<b>\n\n')
self.log(msg)
def _do_process_output_logging(self, process: subprocess.Popen) -> None:
"""
:param process: instance of 'subprocess.Popen'
"""
for stdout_line in iter(process.stdout.readline, ""):
if stdout_line.isspace():
continue
txt = f'<span style="color: #999999;">{stdout_line.rstrip(os.linesep)}</span>'
self.log(txt)
if self.aborted:
raise InterruptedError('Installation aborted by user')
def _check_packages_installation_and_log(self) -> bool:
packages_ok = are_packages_importable()
self.pushButton_install_packages.setEnabled(not packages_ok)
if packages_ok:
msg1 = f'All required packages are importable! You can close this window now!'
self.log(msg1)
return True
try:
import_packages()
raise Exception("Unexpected successful import of packages?!? It failed a moment ago, we shouldn't be here!")
except Exception:
msg_base = '<b>Python packages required by the plugin could not be loaded due to the following error:</b>'
logging.exception(msg_base)
tb = traceback.format_exc()
msg1 = (f'<span style="color: {_ERROR_COLOR};">'
f'{msg_base} \n '
f'{tb}\n\n'
f'<b>Please try installing the packages again.<b>'
f'</span>')
self.log(msg1)
return False
dialog = None
def import_package(package: PackageToInstall):
# print(package.import_name)
importlib.import_module(package.import_name)
def import_packages():
for package in packages_to_install:
import_package(package)
def are_packages_importable() -> bool:
try:
import_packages()
except Exception:
logging.exception(f'Python packages required by the plugin could not be loaded due to the following error:')
return False
return True
def check_pip_installed() -> bool:
try:
subprocess.check_output([PYTHON_EXECUTABLE_PATH, '-m', 'pip', '--version'])
return True
except subprocess.CalledProcessError:
return False
def check_required_packages_and_install_if_necessary(iface):
os.makedirs(PACKAGES_INSTALL_DIR, exist_ok=True)
if PACKAGES_INSTALL_DIR not in sys.path:
sys.path.append(PACKAGES_INSTALL_DIR) # TODO: check for a less intrusive way to do this
if are_packages_importable():
# if packages are importable we are fine, nothing more to do then
return
global dialog
dialog = PackagesInstallerDialog(iface)
dialog.setWindowModality(QtCore.Qt.WindowModal)
dialog.show()
dialog.move_to_top()
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PackagesInstallerDialog</class>
<widget class="QDialog" name="PackagesInstallerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>693</width>
<height>494</height>
</rect>
</property>
<property name="windowTitle">
<string>Featmap - Packages Installer Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTextBrowser" name="textBrowser_log"/>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_install_packages">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Install packages</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_close">
<property name="text">
<string>Test and Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
from qgis.PyQt.QtWidgets import QMessageBox, QTextEdit
class ResizableMessageBox(QMessageBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSizeGripEnabled(True)
def event(self, event):
if event.type() in (event.LayoutRequest, event.Resize):
if event.type() == event.Resize:
res = super().event(event)
else:
res = False
details = self.findChild(QTextEdit)
if details:
details.setMaximumSize(16777215, 16777215)
self.setMaximumSize(16777215, 16777215)
return res
return super().event(event)
This diff is collapsed.
import processing
from PyQt5.QtWidgets import (
QAction,
QToolBar,
QApplication,
QDialog
)
from PyQt5.QtCore import pyqtSignal, QObject
from qgis.core import QgsApplication
from qgis.gui import QgisInterface
from .provider import IAMapProvider
from .icons import QIcon_EncoderTool, QIcon_ReductionTool, QIcon_ClusterTool, QIcon_SimilarityTool, QIcon_RandomforestTool
class IAMap(QObject):
execute_iamap = pyqtSignal()
def __init__(self, iface: QgisInterface, cwd: str):
super().__init__()
self.iface = iface
self.cwd = cwd
def initProcessing(self):
self.provider = IAMapProvider()
QgsApplication.processingRegistry().addProvider(self.provider)
def initGui(self):
self.initProcessing()
self.toolbar: QToolBar = self.iface.addToolBar('IAMap Toolbar')
self.toolbar.setObjectName('IAMapToolbar')
self.toolbar.setToolTip('IAMap Toolbar')
self.actionEncoder = QAction(
QIcon_EncoderTool,
"Deep Learning Image Encoder",
self.iface.mainWindow()
)
self.actionReducer = QAction(
QIcon_ReductionTool,
"Reduce dimensions",
self.iface.mainWindow()
)
self.actionCluster = QAction(
QIcon_ClusterTool,
"Cluster raster",
self.iface.mainWindow()
)
self.actionSimilarity = QAction(
QIcon_SimilarityTool,
"Compute similarity",
self.iface.mainWindow()
)
self.actionRF = QAction(
QIcon_RandomforestTool,
"Use Random Forest algorithm",
self.iface.mainWindow()
)
self.actionEncoder.setObjectName("mActionEncoder")
self.actionReducer.setObjectName("mActionReducer")
self.actionCluster.setObjectName("mActionCluster")
self.actionSimilarity.setObjectName("mactionSimilarity")
self.actionRF.setObjectName("mactionRF")
self.actionEncoder.setToolTip(
"Encode a raster with a deep learning backbone")
self.actionReducer.setToolTip(
"Reduce raster dimensions")
self.actionCluster.setToolTip(
"Cluster raster")
self.actionSimilarity.setToolTip(
"Compute similarity")
self.actionRF.setToolTip(
"Use Random Forest ")
self.actionEncoder.triggered.connect(self.encodeImage)
self.actionReducer.triggered.connect(self.reduceImage)
self.actionCluster.triggered.connect(self.clusterImage)
self.actionSimilarity.triggered.connect(self.similarityImage)
self.actionRF.triggered.connect(self.rfImage)
self.toolbar.addAction(self.actionEncoder)
self.toolbar.addAction(self.actionReducer)
self.toolbar.addAction(self.actionCluster)
self.toolbar.addAction(self.actionSimilarity)
self.toolbar.addAction(self.actionRF)
def unload(self):
# self.wdg_select.setVisible(False)
self.iface.removeToolBarIcon(self.actionEncoder)
self.iface.removeToolBarIcon(self.actionReducer)
self.iface.removeToolBarIcon(self.actionCluster)
self.iface.removeToolBarIcon(self.actionSimilarity)
self.iface.removeToolBarIcon(self.actionRF)
del self.actionEncoder
del self.actionReducer
del self.actionCluster
del self.actionSimilarity
del self.actionRF
del self.toolbar
QgsApplication.processingRegistry().removeProvider(self.provider)
def encodeImage(self):
'''
'''
result = processing.execAlgorithmDialog('iamap:encoder', {})
print(result)
# Check if algorithm execution was successful
if result:
# Retrieve output parameters from the result dictionary
if 'OUTPUT_RASTER' in result:
output_raster_path = result['OUTPUT_RASTER']
# Add the output raster layer to the map canvas
self.iface.addRasterLayer(str(output_raster_path), 'merged features')
else:
# Handle missing or unexpected output
print('Output raster not found in algorithm result.')
else:
# Handle algorithm execution failure or cancellation
print('Algorithm execution was not successful.')
# processing.execAlgorithmDialog('', {})
# self.close_all_dialogs()
def reduceImage(self):
'''
'''
result = processing.execAlgorithmDialog('iamap:reduction', {})
print(result)
# Check if algorithm execution was successful
if result:
# Retrieve output parameters from the result dictionary
if 'OUTPUT_RASTER' in result:
output_raster_path = result['OUTPUT_RASTER']
# Add the output raster layer to the map canvas
self.iface.addRasterLayer(str(output_raster_path), 'reduced features')
else:
# Handle missing or unexpected output
print('Output raster not found in algorithm result.')
else:
# Handle algorithm execution failure or cancellation
print('Algorithm execution was not successful.')
# processing.execAlgorithmDialog('', {})
def clusterImage(self):
'''
'''
result = processing.execAlgorithmDialog('iamap:cluster', {})
print(result)
# Check if algorithm execution was successful
if result:
# Retrieve output parameters from the result dictionary
if 'OUTPUT_RASTER' in result:
output_raster_path = result['OUTPUT_RASTER']
# Add the output raster layer to the map canvas
self.iface.addRasterLayer(str(output_raster_path), 'clustering')
else:
# Handle missing or unexpected output
print('Output raster not found in algorithm result.')
else:
# Handle algorithm execution failure or cancellation
print('Algorithm execution was not successful.')
# processing.execAlgorithmDialog('', {})
def similarityImage(self):
'''
'''
result = processing.execAlgorithmDialog('iamap:similarity', {})
print(result)
# Check if algorithm execution was successful
if result:
# Retrieve output parameters from the result dictionary
if 'OUTPUT_RASTER' in result:
output_raster_path = result['OUTPUT_RASTER']
# Add the output raster layer to the map canvas
self.iface.addRasterLayer(str(output_raster_path), 'similarity map')
else:
# Handle missing or unexpected output
print('Output raster not found in algorithm result.')
else:
# Handle algorithm execution failure or cancellation
print('Algorithm execution was not successful.')
# processing.execAlgorithmDialog('', {})
def rfImage(self):
'''
'''
result = processing.execAlgorithmDialog('iamap:Random_forest', {})
print(result)
# Check if algorithm execution was successful
if result:
# Retrieve output parameters from the result dictionary
if 'OUTPUT_RASTER' in result:
output_raster_path = result['OUTPUT_RASTER']
# Add the output raster layer to the map canvas
self.iface.addRasterLayer(str(output_raster_path), 'random forest map')
else:
# Handle missing or unexpected output
print('Output raster not found in algorithm result.')
else:
# Handle algorithm execution failure or cancellation
print('Algorithm execution was not successful.')
# processing.execAlgorithmDialog('', {})
def close_all_dialogs(self):
# Get the main QGIS window (QgisInterface)
qgis_main_window = self.iface.mainWindow()
# Get all open dialogs associated with the main window
open_dialogs = qgis_main_window.findChildren(QDialog)
# Iterate through the open dialogs and close them
for dialog in open_dialogs:
# Check if the dialog is visible (to avoid closing hidden dialogs)
if dialog.isVisible():
# Close the dialog
dialog.close()
import os
from PyQt5.QtGui import QIcon
cwd = os.path.abspath(os.path.dirname(__file__))
# encoder_tool_path = os.path.join(cwd, 'encoder_tool.svg')
encoder_tool_path = os.path.join(cwd, 'network-base-solid-svgrepo-com.svg')
reduction_tool_path = os.path.join(cwd, 'chart-scatterplot-solid-svgrepo-com.svg')
cluster_tool_path = os.path.join(cwd, 'chart-scatter-3d-svgrepo-com.svg')
similarity_tool_path = os.path.join(cwd, 'scatter-plot-svgrepo-com.svg')
random_forest_tool_path = os.path.join(cwd, 'forest-svgrepo-com.svg')
QIcon_EncoderTool = QIcon(encoder_tool_path)
QIcon_ReductionTool = QIcon(reduction_tool_path)
QIcon_ClusterTool = QIcon(cluster_tool_path)
QIcon_SimilarityTool = QIcon(similarity_tool_path)
QIcon_RandomforestTool = QIcon(random_forest_tool_path)
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4V14M12 14L4 20M12 14L20 20M12 20H12.01M4 13H4.01M17 13H17.01M8 9H8.01M20 9H20.01M5 5H5.01M18 5H18.01" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>chart-data-sets-solid</title>
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
</g>
<g id="icons_Q2" data-name="icons Q2">
<circle cx="14" cy="12" r="2"/>
<circle cx="14" cy="22" r="2"/>
<circle cx="19" cy="17" r="2"/>
<circle cx="36" cy="34" r="2"/>
<circle cx="36" cy="24" r="2"/>
<circle cx="31" cy="29" r="2"/>
<path d="M41.7,20A2,2,0,0,0,44,18V6a2,2,0,0,0-2-2H30a2,2,0,0,0-2,2.3A2.1,2.1,0,0,0,30.1,8h7.1L8,37.2V6A2,2,0,0,0,4,6V44H42a2,2,0,0,0,0-4H10.8L40,10.8v7.1A2.1,2.1,0,0,0,41.7,20Z"/>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FF5252;stroke:#000000;stroke-width:6.390949e-04;stroke-miterlimit:10;}
.st1{fill:#1AA1DB;stroke:#000000;stroke-width:6.390949e-04;stroke-miterlimit:10;}
.st2{fill:#91C965;}
.st3{fill:#88BA5D;}
.st4{fill:#9BDC6E;}
.st5{fill:#3C8763;}
</style>
<g id="net" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
<g id="g16">
<path id="path4" class="st0" d="M5.5,24.7c-0.6,0-1.1-0.5-1.1-1.1v-4.3c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1v4.3
C6.6,24.2,6.1,24.7,5.5,24.7 M5.5,34.3c-0.6,0-1.1-0.5-1.1-1.1V30c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1v3.2
C6.6,33.8,6.1,34.3,5.5,34.3 M5.5,44.9c-0.6,0-1.1-0.5-1.1-1.1v-6.4c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1v6.4
C6.6,44.4,6.1,44.9,5.5,44.9"/>
<path id="path6" class="st0" d="M13.6,53.1c-0.2,0-0.3,0-0.5-0.1l-3.9-1.8c-0.5-0.3-0.8-0.9-0.5-1.4s0.9-0.8,1.4-0.5l3.9,1.8
c0.5,0.3,0.8,0.9,0.5,1.4C14.4,52.9,14,53.1,13.6,53.1 M21.3,56.8c-0.2,0-0.3,0-0.5-0.1L18,55.3c-0.5-0.3-0.8-0.9-0.5-1.4
s0.9-0.8,1.4-0.5l2.9,1.4c0.5,0.3,0.8,0.9,0.5,1.4C22.1,56.5,21.7,56.8,21.3,56.8 M27.9,59.8c-0.2,0-0.3,0-0.5-0.1l-2.7-1.3
c-0.5-0.3-0.8-0.9-0.5-1.4s0.9-0.8,1.4-0.5l2.7,1.3c0.5,0.3,0.8,0.9,0.5,1.4C28.7,59.6,28.3,59.8,27.9,59.8"/>
<path id="path8" class="st0" d="M58.8,24.7c-0.6,0-1.1-0.5-1.1-1.1v-4.3c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1v4.3
C59.8,24.2,59.4,24.7,58.8,24.7 M58.8,31.1c-0.6,0-1.1-0.5-1.1-1.1v-3.2c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1V30
C59.8,30.6,59.4,31.1,58.8,31.1 M58.8,38.5c-0.6,0-1.1-0.5-1.1-1.1v-3.2c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1v3.2
C59.8,38.1,59.4,38.5,58.8,38.5 M58.8,44.9c-0.6,0-1.1-0.5-1.1-1.1v-3.2c0-0.6,0.5-1.1,1.1-1.1s1.1,0.5,1.1,1.1v3.2
C59.8,44.4,59.4,44.9,58.8,44.9"/>
<path id="path10" class="st0" d="M50.6,53.1c-0.4,0-0.8-0.2-1-0.6c-0.3-0.5,0-1.2,0.5-1.4l3.9-1.8c0.5-0.3,1.2,0,1.4,0.5
c0.3,0.5,0,1.2-0.5,1.4L51.1,53C51,53.1,50.8,53.1,50.6,53.1 M42.9,56.8c-0.4,0-0.8-0.2-1-0.6c-0.3-0.5,0-1.2,0.5-1.4l2.9-1.4
c0.5-0.2,1.2,0,1.4,0.5c0.3,0.5,0,1.2-0.5,1.4l-2.9,1.4C43.2,56.7,43.1,56.8,42.9,56.8 M36.4,59.8c-0.4,0-0.8-0.2-1-0.6
c-0.3-0.5,0-1.2,0.5-1.4l2.7-1.3c0.5-0.2,1.2,0,1.4,0.5s0,1.2-0.5,1.4l-2.7,1.3C36.7,59.8,36.5,59.8,36.4,59.8"/>
<path id="path12" class="st0" d="M38.7,6.5c-0.1,0-0.3,0-0.4-0.1l-2.3-1c-0.5-0.2-0.8-0.8-0.6-1.4c0.2-0.5,0.8-0.8,1.4-0.6l2.3,1
c0.5,0.2,0.8,0.8,0.6,1.4C39.6,6.2,39.2,6.5,38.7,6.5 M45.6,9.3c-0.1,0-0.3,0-0.4-0.1l-3-1.2c-0.5-0.2-0.8-0.8-0.6-1.4
C41.8,6.1,42.4,5.8,43,6l3,1.2c0.5,0.2,0.8,0.8,0.6,1.4C46.5,9.1,46.1,9.3,45.6,9.3 M54.5,13c-0.1,0-0.3,0-0.4-0.1l-3.9-1.6
c-0.5-0.2-0.8-0.8-0.6-1.4c0.2-0.5,0.8-0.8,1.4-0.6l3.9,1.6c0.5,0.2,0.8,0.8,0.6,1.4C55.3,12.7,54.9,13,54.5,13"/>
<path id="path14" class="st0" d="M25.5,6.5c-0.4,0-0.8-0.2-1-0.7s0-1.2,0.6-1.4l2.3-1c0.5-0.2,1.2,0,1.4,0.6
c0.2,0.5,0,1.2-0.6,1.4l-2.3,1C25.8,6.4,25.7,6.5,25.5,6.5 M18.6,9.3c-0.4,0-0.8-0.2-1-0.7s0-1.2,0.6-1.4l3-1.2
c0.5-0.2,1.2,0,1.4,0.6c0.2,0.5,0,1.2-0.6,1.4l-3,1.2C18.9,9.3,18.8,9.3,18.6,9.3 M9.8,13c-0.4,0-0.8-0.2-1-0.7s0-1.2,0.6-1.4
l3.9-1.6c0.5-0.2,1.2,0,1.4,0.6c0.2,0.5,0,1.2-0.6,1.4l-3.9,1.6C10,12.9,9.9,13,9.8,13"/>
</g>
<g id="g32">
<path id="path24" class="st0" d="M16.2,23.6c-0.2,0-0.4-0.1-0.6-0.2l-6.4-4.3c-0.5-0.3-0.6-1-0.3-1.5s1-0.6,1.5-0.3l6.4,4.3
c0.5,0.3,0.6,1,0.3,1.5C16.8,23.4,16.5,23.6,16.2,23.6"/>
<path id="path26" class="st0" d="M48.1,23.6c-0.3,0-0.7-0.2-0.9-0.5c-0.3-0.5-0.2-1.2,0.3-1.5l6.4-4.3c0.5-0.3,1.2-0.2,1.5,0.3
s0.2,1.2-0.3,1.5l-6.4,4.3C48.5,23.6,48.3,23.6,48.1,23.6"/>
<path id="path28" class="st0" d="M54.5,46c-0.2,0-0.4-0.1-0.6-0.2l-6.4-4.3c-0.5-0.3-0.6-1-0.3-1.5s1-0.6,1.5-0.3l6.4,4.3
c0.5,0.3,0.6,1,0.3,1.5C55.2,45.8,54.8,46,54.5,46"/>
<path id="path30" class="st0" d="M9.8,46c-0.3,0-0.7-0.2-0.9-0.5C8.6,45,8.7,44.4,9.2,44l6.4-4.3c0.5-0.3,1.2-0.2,1.5,0.3
s0.2,1.2-0.3,1.5l-6.4,4.3C10.2,45.9,10,46,9.8,46"/>
</g>
<g id="g46">
<path id="path34" class="st1" d="M5.5,20.4c-2.9,0-5.3-2.4-5.3-5.3s2.4-5.3,5.3-5.3s5.3,2.4,5.3,5.3S8.4,20.4,5.5,20.4"/>
<path id="path36" class="st1" d="M32.1,10.8c-2.9,0-5.3-2.4-5.3-5.3s2.4-5.3,5.3-5.3s5.3,2.4,5.3,5.3S35.1,10.8,32.1,10.8"/>
<path id="path38" class="st1" d="M32.1,64.1c-2.9,0-5.3-2.4-5.3-5.3c0-2.9,2.4-5.3,5.3-5.3s5.3,2.4,5.3,5.3S35.1,64.1,32.1,64.1"
/>
<path id="path40" class="st1" d="M58.8,20.4c-2.9,0-5.3-2.4-5.3-5.3s2.4-5.3,5.3-5.3c2.9,0,5.3,2.4,5.3,5.3S61.7,20.4,58.8,20.4"
/>
<path id="path42" class="st1" d="M58.8,53.4c-2.9,0-5.3-2.4-5.3-5.3c0-2.9,2.4-5.3,5.3-5.3c2.9,0,5.3,2.4,5.3,5.3
S61.7,53.4,58.8,53.4"/>
<path id="path44" class="st1" d="M5.5,53.4c-2.9,0-5.3-2.4-5.3-5.3s2.4-5.3,5.3-5.3s5.3,2.4,5.3,5.3S8.4,53.4,5.5,53.4"/>
</g>
</g>
<g id="cube">
<g>
<polygon id="polygon18" class="st2" points="16.7,41.6 16.7,22.6 32.2,30.7 32.2,49.5 "/>
<polygon id="polygon20" class="st3" points="47.8,41.6 47.8,22.6 32.2,30.7 32.2,49.6 "/>
<polygon id="polygon22" class="st4" points="16.7,22.6 32.5,14.6 47.8,22.6 32.2,30.7 "/>
<path id="path4_00000163069267588332021340000012919285466319501952_" class="st5" d="M48.4,22.7C48.4,22.6,48.4,22.6,48.4,22.7
C48.4,22.6,48.4,22.6,48.4,22.7c0-0.1,0-0.1,0-0.2c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0
c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0l-15.5-8
c-0.2-0.1-0.4-0.1-0.6,0l-15.5,8c0,0,0,0,0,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0
c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0.1,0,0.1v18.8
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l5.2,2.6c0,0,0,0,0.1,0l5.1,2.6c0,0,0.1,0.1,0.2,0.1L32,50c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l5.1-2.6c0.1,0,0.1,0,0.2-0.1l5.1-2.6
c0,0,0,0,0.1,0l5.2-2.6c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0L48.4,22.7L48.4,22.7z M27.1,18l3.9,2l-3.9,2l-3.9-2
L27.1,18z M32.2,20.7l3.9,2l-3.9,2l-3.9-2L32.2,20.7z M41.3,20l-3.9,2l-3.9-2l3.9-2L41.3,20z M17.3,30.2l4,2v4.4l-4-2V30.2z
M22.5,32.9l4,2v4.4l-4-2V32.9z M31.6,36.2l-4-2V29l4,2V36.2z M32.8,31l4-2v5.2l-4,2V31z M38,28.3l4-2v5.2l-4,2V28.3z M32.2,29.9
l-3.8-2l3.8-2l3.8,2L32.2,29.9z M26.5,33.5l-4-2v-5.2l4,2V33.5z M27.7,35.5l4,2V42l-4-2V35.5z M32.8,37.5l4-2v4.4l-4,2V37.5z
M38,34.9l4-2v4.4l-4,2V34.9z M43.2,32.2l4-2v4.4l-4,2V32.2z M47.2,28.8l-4,2v-5.2l4-2V28.8z M37.4,27.3l-3.8-2l3.9-2l3.8,2
L37.4,27.3z M30.9,25.3l-3.8,2l-3.9-2l3.8-2L30.9,25.3z M21.3,30.9l-4-2v-5.2l4,2V30.9z M17.3,36l4,2v5.1l-4-2V36z M22.5,38.6l4,2
v5.1l-4-2V38.6z M27.7,41.3l4,2v5.1l-4-2V41.3z M32.8,43.3l4-2v5.1l-4,2V43.3z M38,40.7l4-2v5.1l-4,2V40.7z M43.2,38l4-2v5.1l-4,2
V38z M42.6,24.6l-3.8-2l3.9-2l3.8,2L42.6,24.6z M32.2,15.4l3.9,2l-3.9,2l-3.9-2L32.2,15.4z M21.9,20.7l3.9,2l-3.8,2l-3.9-2
L21.9,20.7z"/>
</g>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<style type="text/css">
.st0{fill:#000000;}
</style>
<g>
<path class="st0" d="M346.483,226.653c-58.176-75.765-90.498-181.813-90.498-181.813s-32.318,106.048-90.505,181.813
c0,0,26.66,16.09,41.21,7.569c0,0-14.55,65.341-79.995,151.514c58.176,18.923,101.81-12.328,101.81-12.328v93.75h21.025h12.916
h21.021v-93.75c0,0,43.642,31.25,101.817,12.328c-65.457-86.174-79.995-151.514-79.995-151.514
C319.826,242.743,346.483,226.653,346.483,226.653z"/>
<path class="st0" d="M160.886,307.087c-19.185-35.761-24.363-59.015-24.363-59.015c8.768,5.141,23.33-1.454,29.058-4.376
c1.522-0.84,2.417-1.379,2.417-1.379c-5.313-6.985-10.353-14.276-15.186-21.718c-34.855-54.482-53.972-117.26-53.972-117.26
s-24.711,81.041-69.23,138.977c0,0,20.361,12.283,31.542,5.756c0,0-11.181,49.956-61.151,115.88
c44.451,14.426,77.788-9.443,77.788-9.443v71.674h42.034v-71.674c0,0,3.035,2.151,8.415,4.759
C141.633,340.391,152.332,322.817,160.886,307.087z"/>
<path class="st0" d="M450.849,248.071c11.121,6.527,31.474-5.756,31.474-5.756c-44.454-57.936-69.155-138.977-69.155-138.977
s-19.125,62.778-54.05,117.26c-4.766,7.441-9.803,14.733-15.123,21.718c0,0,0.906,0.54,2.428,1.379
c5.725,2.922,20.29,9.517,29.058,4.376c0,0-5.178,23.328-24.442,59.09c8.566,15.655,19.331,33.303,32.723,52.106
c5.381-2.608,8.423-4.759,8.423-4.759v71.674h41.967v-71.674c0,0,33.394,23.869,77.848,9.443
C461.97,298.027,450.849,248.071,450.849,248.071z"/>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
</g>
<g id="Layer_7" data-name="Layer 7">
<g>
<path d="M25.5,25.7l-1.5,1-1.5-1a2.2,2.2,0,0,0-2.8.7,2.1,2.1,0,0,0,.7,2.8L23,30.7a1.8,1.8,0,0,0,2,0l2.6-1.5a2.1,2.1,0,0,0,.7-2.8A2.2,2.2,0,0,0,25.5,25.7Z"/>
<path d="M3,18.7l2.5,1.5a2.3,2.3,0,0,0,1.1.3,2.1,2.1,0,0,0,1.7-.9A2,2,0,0,0,7.8,17a2,2,0,0,0,.5-2.6,2.2,2.2,0,0,0-2.8-.7L3,15.3A1.9,1.9,0,0,0,2,17,2.1,2.1,0,0,0,3,18.7Z"/>
<path d="M12,14.2a1.9,1.9,0,0,0,1-.3l4.1-2.4a2,2,0,0,0,.6-2.7A1.9,1.9,0,0,0,15,8.1l-4.1,2.4a2,2,0,0,0-.6,2.7A1.8,1.8,0,0,0,12,14.2Z"/>
<path d="M22.5,8.3l1.5-1,1.5,1a2.2,2.2,0,0,0,1.1.2,2,2,0,0,0,1.7-.9,2.1,2.1,0,0,0-.7-2.8L25,3.3a1.8,1.8,0,0,0-2,0L20.4,4.8a2.1,2.1,0,0,0-.7,2.8A2.2,2.2,0,0,0,22.5,8.3Z"/>
<path d="M30.9,11.5,35,13.9a1.9,1.9,0,0,0,1,.3,1.8,1.8,0,0,0,1.7-1,2,2,0,0,0-.6-2.7L33,8.1a1.9,1.9,0,0,0-2.7.7A2,2,0,0,0,30.9,11.5Z"/>
<path d="M40.2,17a2,2,0,0,0-.5,2.6,2.1,2.1,0,0,0,1.7.9,2.3,2.3,0,0,0,1.1-.3L45,18.7A2.1,2.1,0,0,0,46,17a1.9,1.9,0,0,0-1-1.7l-2.5-1.6a2.2,2.2,0,0,0-2.8.7A2,2,0,0,0,40.2,17Z"/>
<path d="M10.9,22.5h0L15,24.9a1.9,1.9,0,0,0,1,.3,1.8,1.8,0,0,0,1.7-1,2,2,0,0,0-.6-2.7L13,19.1a1.9,1.9,0,0,0-2.7.6A2.1,2.1,0,0,0,10.9,22.5Z"/>
<path d="M32,25.2a1.9,1.9,0,0,0,1-.3l4.1-2.4a2.1,2.1,0,0,0,.6-2.8,1.9,1.9,0,0,0-2.7-.6l-4.1,2.4a2,2,0,0,0-.6,2.7A1.8,1.8,0,0,0,32,25.2Z"/>
<path d="M45,28.3l-6.2-3.8L24,33.2,9.2,24.5,3,28.3A1.9,1.9,0,0,0,2,30a2.1,2.1,0,0,0,1,1.7l20,12a1.8,1.8,0,0,0,2,0l20-12A2.1,2.1,0,0,0,46,30,1.9,1.9,0,0,0,45,28.3Z"/>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>network-platform</title>
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
<rect width="48" height="48" fill="none"/>
<rect width="48" height="48" fill="none"/>
</g>
<g id="icons_Q2" data-name="icons Q2">
<path d="M40,18a6.2,6.2,0,0,0-5.7,4H25.8a9.9,9.9,0,0,0-1.5-3.3l3.5-4.1A7.8,7.8,0,0,0,30,15a6,6,0,1,0-6-6,6.1,6.1,0,0,0,.8,3l-3.6,4.1A8.5,8.5,0,0,0,17,15l-1.7.2-1.2-2.8A6,6,0,1,0,10,14h.4l1.3,2.8a8.9,8.9,0,0,0,0,14.4L10.4,34H10a6,6,0,1,0,4.1,1.6l1.2-2.8L17,33a8.5,8.5,0,0,0,4.2-1.1L24.8,36a6.1,6.1,0,0,0-.8,3,6,6,0,1,0,6-6,7.8,7.8,0,0,0-2.2.4l-3.5-4.1A9.9,9.9,0,0,0,25.8,26h8.5A6.2,6.2,0,0,0,40,30a6,6,0,0,0,0-12ZM8,8a2,2,0,1,1,2,2A2,2,0,0,1,8,8Zm2,34a2,2,0,1,1,2-2A2,2,0,0,1,10,42ZM30,7a2,2,0,1,1-2,2A2,2,0,0,1,30,7ZM12,24a5,5,0,1,1,5,5A5,5,0,0,1,12,24ZM32,39a2,2,0,1,1-2-2A2,2,0,0,1,32,39Zm8-13a2,2,0,1,1,2-2A2,2,0,0,1,40,26Z"/>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>scatter-plot-outline-alerted</title>
<path class="clr-i-outline--alerted clr-i-outline-path-1--alerted" d="M 34 29 C 34 30.105 33.105 31 32 31 L 4 31 C 2.895 31 2 30.105 2 29 L 2 7 C 2 5.895 2.895 5 4 5 L 21.958 5 L 20.786 7 L 4 7 L 4 29 L 32 29 L 32 15.357 L 34 15.357 Z"></path><path class="clr-i-outline--alerted clr-i-outline-path-2--alerted" d="M 9.101 15.8 C 9.413 16.111 9.919 16.111 10.231 15.8 L 11.391 14.64 L 12.551 15.8 C 12.964 16.256 13.717 16.094 13.905 15.507 C 14.002 15.208 13.914 14.881 13.681 14.67 L 12.531 13.54 L 13.691 12.38 C 14.147 11.966 13.985 11.214 13.399 11.025 C 13.1 10.929 12.772 11.017 12.561 11.25 L 11.401 12.41 L 10.231 11.22 C 9.817 10.763 9.065 10.926 8.877 11.512 C 8.78 11.811 8.868 12.139 9.101 12.35 L 10.261 13.54 L 9.101 14.67 C 8.789 14.982 8.789 15.487 9.101 15.8 Z"></path><path class="clr-i-outline--alerted clr-i-outline-path-3--alerted" d="M 15.176 25.536 C 15.488 25.847 15.994 25.847 16.306 25.536 L 17.466 24.376 L 18.626 25.536 C 19.039 25.992 19.792 25.83 19.98 25.243 C 20.077 24.944 19.989 24.617 19.756 24.406 L 18.606 23.276 L 19.766 22.116 C 20.222 21.702 20.06 20.95 19.474 20.761 C 19.175 20.665 18.847 20.753 18.636 20.986 L 17.476 22.146 L 16.306 20.956 C 15.892 20.499 15.14 20.662 14.952 21.248 C 14.855 21.547 14.943 21.875 15.176 22.086 L 16.336 23.276 L 15.176 24.406 C 14.864 24.718 14.864 25.223 15.176 25.536 Z"></path><path class="clr-i-outline--alerted clr-i-outline-path-4--alerted" d="M 22.912 20.343 C 23.224 20.654 23.73 20.654 24.042 20.343 L 25.202 19.183 L 26.362 20.343 C 26.775 20.799 27.528 20.637 27.716 20.05 C 27.813 19.751 27.725 19.424 27.492 19.213 L 26.342 18.083 L 27.502 16.923 C 27.958 16.509 27.796 15.757 27.21 15.568 C 26.911 15.472 26.583 15.56 26.372 15.793 L 25.212 16.953 L 24.042 15.763 C 23.628 15.306 22.876 15.469 22.688 16.055 C 22.591 16.354 22.679 16.682 22.912 16.893 L 24.072 18.083 L 22.912 19.213 C 22.6 19.525 22.6 20.03 22.912 20.343 Z"></path><path class="clr-i-outline--alerted clr-i-outline-path-5--alerted clr-i-alert" d="M 26.854 1.144 L 21.134 11.004 C 20.579 11.818 21.114 12.928 22.097 13.001 C 22.142 13.005 22.188 13.006 22.234 13.004 L 33.684 13.004 C 34.669 13.036 35.319 11.991 34.855 11.122 C 34.834 11.081 34.81 11.042 34.784 11.004 L 29.064 1.144 C 28.57 0.299 27.348 0.299 26.854 1.144 Z"></path>
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>scatter-plot-line</title>
<path class="clr-i-outline clr-i-outline-path-1" d="M 32 5 L 4 5 C 2.895 5 2 5.895 2 7 L 2 29 C 2 30.105 2.895 31 4 31 L 32 31 C 33.105 31 34 30.105 34 29 L 34 7 C 34 5.895 33.105 5 32 5 Z M 4 29 L 4 7 L 32 7 L 32 29 Z"></path><path class="clr-i-outline clr-i-outline-path-2" d="M 9.101 15.8 C 9.413 16.111 9.919 16.111 10.231 15.8 L 11.391 14.64 L 12.551 15.8 C 12.964 16.256 13.717 16.094 13.905 15.507 C 14.002 15.208 13.914 14.881 13.681 14.67 L 12.531 13.54 L 13.691 12.38 C 14.147 11.966 13.985 11.214 13.399 11.025 C 13.1 10.929 12.772 11.017 12.561 11.25 L 11.401 12.41 L 10.231 11.22 C 9.817 10.763 9.065 10.926 8.877 11.512 C 8.78 11.811 8.868 12.139 9.101 12.35 L 10.261 13.54 L 9.101 14.67 C 8.789 14.982 8.789 15.487 9.101 15.8 Z"></path><path class="clr-i-outline clr-i-outline-path-3" d="M 15.176 25.536 C 15.488 25.847 15.994 25.847 16.306 25.536 L 17.466 24.376 L 18.626 25.536 C 19.039 25.992 19.792 25.83 19.98 25.243 C 20.077 24.944 19.989 24.617 19.756 24.406 L 18.606 23.276 L 19.766 22.116 C 20.222 21.702 20.06 20.95 19.474 20.761 C 19.175 20.665 18.847 20.753 18.636 20.986 L 17.476 22.146 L 16.306 20.956 C 15.892 20.499 15.14 20.662 14.952 21.248 C 14.855 21.547 14.943 21.875 15.176 22.086 L 16.336 23.276 L 15.176 24.406 C 14.864 24.718 14.864 25.223 15.176 25.536 Z"></path><path class="clr-i-outline clr-i-outline-path-4" d="M 22.912 20.343 C 23.224 20.654 23.73 20.654 24.042 20.343 L 25.202 19.183 L 26.362 20.343 C 26.775 20.799 27.528 20.637 27.716 20.05 C 27.813 19.751 27.725 19.424 27.492 19.213 L 26.342 18.083 L 27.502 16.923 C 27.958 16.509 27.796 15.757 27.21 15.568 C 26.911 15.472 26.583 15.56 26.372 15.793 L 25.212 16.953 L 24.042 15.763 C 23.628 15.306 22.876 15.469 22.688 16.055 C 22.591 16.354 22.679 16.682 22.912 16.893 L 24.072 18.083 L 22.912 19.213 C 22.6 19.525 22.6 20.03 22.912 20.343 Z"></path>
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
</svg>
\ No newline at end of file
from qgis.core import QgsProcessingProvider
from .encoder import EncoderAlgorithm
from .reduction import ReductionAlgorithm
from .clustering import ClusterAlgorithm
from .similarity import SimilarityAlgorithm
from .random_forest import RFAlgorithm
from .icons import QIcon_EncoderTool
class IAMapProvider(QgsProcessingProvider):
def loadAlgorithms(self, *args, **kwargs):
self.addAlgorithm(EncoderAlgorithm())
self.addAlgorithm(ReductionAlgorithm())
self.addAlgorithm(ClusterAlgorithm())
self.addAlgorithm(SimilarityAlgorithm())
self.addAlgorithm(RFAlgorithm())
# add additional algorithms here
# self.addAlgorithm(MyOtherAlgorithm())
def id(self, *args, **kwargs):
"""The ID of your plugin, used for identifying the provider.
This string should be a unique, short, character only string,
eg "qgis" or "gdal". This string should not be localised.
"""
return 'iamap'
def name(self, *args, **kwargs):
"""The human friendly name of your plugin in Processing.
This string should be as short as possible (e.g. "Lastools", not
"Lastools version 1.0.1 64-bit") and localised.
"""
return self.tr('IAMap')
def icon(self):
"""Should return a QIcon which is used for your provider inside
the Processing toolbox.
"""
return QIcon_EncoderTool
def longName(self) -> str:
return self.name()
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment