123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- """
- ***************************************************************************
- OtbAlgorithm.py
- ---------------
- Date : June 2017
- Copyright : (C) 2017 by CS Systemes d'Information (CS SI)
- : (C) 2018 by Centre National d'Etudes et spatiales (CNES)
- Email : rashad dot kanavath at c-s fr, otb at c-s dot fr (CS SI)
- ***************************************************************************
- * *
- * 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 2 of the License, or *
- * (at your option) any later version. *
- * *
- ***************************************************************************
- """
- __author__ = 'Rashad Kanavath'
- __date__ = 'June 2017'
- __copyright__ = "(C) 2017,2018 by CS Systemes d'information (CS SI), Centre National d'Etudes et spatiales (CNES)"
- import os
- from qgis.PyQt.QtCore import QCoreApplication
- from qgis.PyQt.QtGui import QIcon
- from qgis.core import (Qgis,
- QgsMessageLog,
- QgsMapLayer,
- QgsApplication,
- QgsProcessingException,
- QgsProcessingAlgorithm,
- QgsProcessingParameterMultipleLayers,
- QgsProcessingParameterDefinition,
- QgsProcessingOutputLayerDefinition,
- QgsProcessingParameterCrs,
- QgsProcessingParameterString,
- QgsProcessingParameterRasterLayer,
- QgsProcessingParameterVectorLayer,
- QgsProcessingParameterBoolean,
- QgsProcessingParameterFile,
- QgsProcessingParameterNumber,
- QgsProcessingParameterRasterDestination,
- QgsProcessingParameterVectorDestination,
- QgsProcessingParameterEnum,
- QgsProcessingParameterBand,
- QgsProcessingParameterField,
- QgsProviderRegistry)
- from processing.core.parameters import getParameterFromString
- from .OtbChoiceWidget import OtbParameterChoice
- from .OtbUtils import OtbUtils
- class OtbAlgorithm(QgsProcessingAlgorithm):
- def __init__(self, group, name, descriptionfile, display_name='', groupId=''):
- super().__init__()
- self._name = name
- self._group = group
- self._display_name = display_name
- self._groupId = ''
- validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:'
- if not groupId:
- self._groupId = ''.join(c for c in self._group if c in validChars)
- self.pixelTypes = ['uint8', 'uint16', 'int16', 'uint32', 'int32',
- 'float', 'double', 'cint16', 'cint32', 'cfloat', 'cdouble']
- self._descriptionfile = descriptionfile
- self.defineCharacteristicsFromFile()
- def icon(self):
- return QgsApplication.getThemeIcon("/providerOtb.svg")
- def createInstance(self):
- return self.__class__(self._group, self._name, self._descriptionfile)
- def tr(self, string):
- return QCoreApplication.translate("OtbAlgorithm", string)
- def name(self):
- return self._name
- def displayName(self):
- return self._display_name
- def group(self):
- return self._group
- def groupId(self):
- return self._groupId
- def descriptionfile(self):
- return self._descriptionfile
- def initAlgorithm(self, config=None):
- pass
- # TODO: show version which is same as OtbAlgorithm rather than always latest.
- def helpUrl(self):
- return "https://www.orfeo-toolbox.org/CookBook/Applications/app_" + self.name() + ".html"
- def defineCharacteristicsFromFile(self):
- line = None
- try:
- with open(self._descriptionfile) as lines:
- line = lines.readline().strip('\n').strip()
- self._name = line.split('|')[0]
- self.appkey = self._name
- line = lines.readline().strip('\n').strip()
- self.doc = line
- self.i18n_doc = QCoreApplication.translate("OtbAlgorithm", self.doc)
- # self._name = self._name #+ " - " + self.doc
- self._display_name = self.tr(self._name)
- self.i18n_name = QCoreApplication.translate("OtbAlgorithm", self._name)
- line = lines.readline().strip('\n').strip()
- self._group = line
- self.i18n_group = QCoreApplication.translate("OtbAlgorithm", self._group)
- line = lines.readline().strip('\n').strip()
- while line != '':
- line = line.strip('\n').strip()
- if line.startswith('#'):
- line = lines.readline().strip('\n').strip()
- continue
- param = None
- if 'OTBParameterChoice' in line:
- tokens = line.split("|")
- params = [t if str(t) != str(None) else None for t in tokens[1:]]
- options = params[2].split(';')
- param = OtbParameterChoice(params[0], params[1], options, params[3], params[4])
- else:
- param = getParameterFromString(line, 'OtbAlgorithm')
- # if parameter is None, then move to next line and continue
- if param is None:
- line = lines.readline().strip('\n').strip()
- continue
- name = param.name()
- if '.' in name and len(name.split('.')) > 2:
- p = name.split('.')[:-2]
- group_key = '.'.join(p)
- group_value = name.split('.')[-2]
- metadata = param.metadata()
- metadata['group_key'] = group_key
- metadata['group_value'] = group_value
- param.setMetadata(metadata)
- # 'elev.dem.path', 'elev.dem', 'elev.dem.geoid', 'elev.geoid' are special!
- # Even though it is not typical for OTB to fix on parameter keys,
- # we current use below !hack! to set SRTM path and GEOID files
- # Future releases of OTB must follow this rule keep
- # compatibility or update this checklist.
- if name in ["elev.dem.path", "elev.dem"]:
- param.setDefaultValue(OtbUtils.srtmFolder())
- if name in ["elev.dem.geoid", "elev.geoid"]:
- param.setDefaultValue(OtbUtils.geoidFile())
- # outputpixeltype is a special parameter associated with raster output
- # reset list of options to 'self.pixelTypes'.
- if name == 'outputpixeltype':
- param.setOptions(self.pixelTypes)
- param.setDefaultValue(self.pixelTypes.index('float'))
- self.addParameter(param)
- # parameter is added now and we must move to next line
- line = lines.readline().strip('\n').strip()
- except BaseException as e:
- import traceback
- errmsg = "Could not open OTB algorithm from file: \n" + self._descriptionfile + "\nline=" + line + "\nError:\n" + traceback.format_exc()
- QgsMessageLog.logMessage(self.tr(errmsg), self.tr('Processing'), Qgis.Critical)
- raise e
- def preprocessParameters(self, parameters):
- valid_params = {}
- for k, v in parameters.items():
- param = self.parameterDefinition(k)
- # If parameterDefinition(k) return None, this is considered a invalid parameter.
- # just continue for loop
- if param is None:
- continue
- # Any other valid parameters have:
- # - empty or no metadata
- # - metadata without a 'group_key'
- # - metadata with 'group_key' and 'group_key' is not in list of parameters. see ParameterGroup in OTB
- # - metadata with 'group_key' and 'group_key' is a valid parameter and it's value == metadata()['group_value']
- if (
- 'group_key' in param.metadata()
- and param.metadata()['group_key'] not in parameters
- ):
- valid_params[k] = v
- elif 'group_key' not in param.metadata() or parameters[param.metadata()['group_key']] == param.metadata()[
- 'group_value']:
- valid_params[k] = v
- return valid_params
- def processAlgorithm(self, parameters, context, feedback):
- app_launcher_path = OtbUtils.getExecutableInPath(OtbUtils.otbFolder(), 'otbApplicationLauncherCommandLine')
- command = '"{}" {} {}'.format(app_launcher_path, self.name(), OtbUtils.appFolder())
- outputPixelType = None
- for k, v in parameters.items():
- # if value is None or en empty string for a parameter then we don't want to pass that value to OTB (see https://gitlab.orfeo-toolbox.org/orfeotoolbox/otb/-/issues/2130)
- if v == '' or v is None:
- continue
- # for 'outputpixeltype' parameter we find the pixeltype string from self.pixelTypes
- if k == 'outputpixeltype':
- pixel_type = self.pixelTypes[int(parameters['outputpixeltype'])]
- outputPixelType = None if pixel_type == 'float' else pixel_type
- continue
- param = self.parameterDefinition(k)
- if param.isDestination():
- continue
- if isinstance(param, QgsProcessingParameterEnum) and param.name() == "outputpixeltype":
- value = self.parameterAsEnum(parameters, param.name(), context)
- elif isinstance(param, QgsProcessingParameterEnum):
- value = " ".join(
- param.options()[i]
- for i in self.parameterAsEnums(
- parameters, param.name(), context
- )
- if i >= 0 and i < len(param.options())
- )
- elif isinstance(param, QgsProcessingParameterField):
- value = " ".join(self.parameterAsFields(parameters, param.name(), context))
- elif isinstance(param, QgsProcessingParameterBoolean):
- value = self.parameterAsBoolean(parameters, param.name(), context)
- elif isinstance(param, QgsProcessingParameterCrs):
- crsValue = self.parameterAsCrs(parameters, param.name(), context)
- authid = crsValue.authid()
- if authid.startswith('EPSG:'):
- value = authid.split('EPSG:')[1]
- else:
- raise QgsProcessingException(
- self.tr("Incorrect value for parameter '{}'. No EPSG code found in '{}'".format(
- param.name(),
- authid)))
- elif isinstance(param, QgsProcessingParameterFile):
- value = self.parameterAsFile(parameters, param.name(), context)
- elif isinstance(param, QgsProcessingParameterMultipleLayers):
- layers = self.parameterAsLayerList(parameters, param.name(), context)
- if layers is None or len(layers) == 0:
- continue
- value = ' '.join(
- '"{}"'.format(self.getLayerSource(param.name(), layer))
- for layer in layers
- )
- elif isinstance(param, QgsProcessingParameterNumber):
- if param.dataType() == QgsProcessingParameterNumber.Integer:
- value = self.parameterAsInt(parameters, param.name(), context)
- else:
- value = self.parameterAsDouble(parameters, param.name(), context)
- elif isinstance(param, (QgsProcessingParameterRasterLayer, QgsProcessingParameterVectorLayer)):
- value = '"{}"'.format(self.getLayerSource(param.name(), self.parameterAsLayer(parameters, param.name(), context)))
- elif isinstance(param, QgsProcessingParameterString):
- value = '"{}"'.format(self.parameterAsString(parameters, param.name(), context))
- elif isinstance(param, QgsProcessingParameterBand):
- value = ' '.join(
- '"Channel{}"'.format(index)
- for index in self.parameterAsInts(
- parameters, param.name(), context
- )
- )
- else:
- # Use whatever is given
- value = '"{}"'.format(parameters[param.name()])
- # Check if value is set in above if elif ladder and update command string
- if value and value is not None:
- command += ' -{} {}'.format(k, value)
- output_files = {}
- for out in self.destinationParameterDefinitions():
- filePath = self.parameterAsOutputLayer(parameters, out.name(), context)
- if filePath:
- output_files[out.name()] = filePath
- if outputPixelType is not None:
- command += ' -{} "{}" "{}"'.format(out.name(), filePath, outputPixelType)
- else:
- command += ' -{} "{}"'.format(out.name(), filePath)
- OtbUtils.executeOtb(command, feedback)
- return {
- o.name(): output_files[o.name()]
- for o in self.outputDefinitions()
- if o.name() in output_files
- }
- def getLayerSource(self, name, layer):
- providerName = layer.dataProvider().name()
- # TODO: add other provider support in OTB, eg: memory
- if providerName == 'gdal':
- return layer.source()
- elif providerName == 'ogr':
- # when a file contains several layer we pass only the file path to OTB
- # TODO make OTB able to take a layer index in this case
- uriElements = QgsProviderRegistry.instance().decodeUri("ogr", layer.source())
- if 'path' not in uriElements:
- raise QgsProcessingException(
- self.tr("Invalid layer source '{}'. Missing valid 'path' element".format(layer.source())))
- return uriElements['path']
- else:
- raise QgsProcessingException(
- self.tr("OTB currently support only gdal and ogr provider. Parameter '{}' uses '{}' provider".format(name, providerName)))
|