|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | +*************************************************************************** |
| 4 | + OtbAlgorithm.py |
| 5 | + --------------- |
| 6 | + Date : June 2017 |
| 7 | + Copyright : (C) 2017 by CS Systemes d'Information (CS SI) |
| 8 | + : (C) 2018 by Centre National d'Etudes et spatiales (CNES) |
| 9 | + Email : rashad dot kanavath at c-s fr, otb at c-s dot fr (CS SI) |
| 10 | +
|
| 11 | +*************************************************************************** |
| 12 | +* * |
| 13 | +* This program is free software; you can redistribute it and/or modify * |
| 14 | +* it under the terms of the GNU General Public License as published by * |
| 15 | +* the Free Software Foundation; either version 2 of the License, or * |
| 16 | +* (at your option) any later version. * |
| 17 | +* * |
| 18 | +*************************************************************************** |
| 19 | +""" |
| 20 | + |
| 21 | +__author__ = 'Rashad Kanavath' |
| 22 | +__date__ = 'June 2017' |
| 23 | +__copyright__ = "(C) 2017,2018 by CS Systemes d'information (CS SI), Centre National d'Etudes et spatiales (CNES)" |
| 24 | + |
| 25 | +# This will get replaced with a git SHA1 when you do a git archive |
| 26 | + |
| 27 | +__revision__ = '$Format:%H$' |
| 28 | + |
| 29 | +import os |
| 30 | + |
| 31 | +from qgis.PyQt.QtCore import QCoreApplication |
| 32 | +from qgis.PyQt.QtGui import QIcon |
| 33 | + |
| 34 | +from qgis.core import (Qgis, |
| 35 | + QgsMessageLog, |
| 36 | + QgsRasterLayer, |
| 37 | + QgsMapLayer, |
| 38 | + QgsApplication, |
| 39 | + QgsProcessingAlgorithm, |
| 40 | + QgsProcessingParameterMultipleLayers, |
| 41 | + QgsProcessingParameterDefinition, |
| 42 | + QgsProcessingOutputLayerDefinition, |
| 43 | + QgsProcessingParameterString, |
| 44 | + QgsProcessingParameterNumber, |
| 45 | + QgsProcessingParameterEnum) |
| 46 | + |
| 47 | +from processing.core.parameters import getParameterFromString |
| 48 | +from processing.algs.otb.OtbChoiceWidget import OtbParameterChoice |
| 49 | +from processing.algs.otb import OtbUtils |
| 50 | + |
| 51 | + |
| 52 | +class OtbAlgorithm(QgsProcessingAlgorithm): |
| 53 | + def __init__(self, group, name, descriptionfile, display_name='', groupId=''): |
| 54 | + super().__init__() |
| 55 | + self._name = name |
| 56 | + self._group = group |
| 57 | + self._display_name = display_name |
| 58 | + |
| 59 | + self._groupId = '' |
| 60 | + validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:' |
| 61 | + if not groupId: |
| 62 | + self._groupId = ''.join(c for c in self._group if c in validChars) |
| 63 | + |
| 64 | + self.pixelTypes = ['uint8', 'int', 'float', 'double'] |
| 65 | + self._descriptionfile = descriptionfile |
| 66 | + self.defineCharacteristicsFromFile() |
| 67 | + |
| 68 | + def icon(self): |
| 69 | + return QgsApplication.getThemeIcon("/providerOtb.png") |
| 70 | + |
| 71 | + def createInstance(self): |
| 72 | + return self.__class__(self._group, self._name, self._descriptionfile) |
| 73 | + |
| 74 | + def tr(self, string): |
| 75 | + return QCoreApplication.translate("OtbAlgorithm", string) |
| 76 | + |
| 77 | + def name(self): |
| 78 | + return self._name |
| 79 | + |
| 80 | + def displayName(self): |
| 81 | + return self._display_name |
| 82 | + |
| 83 | + def group(self): |
| 84 | + return self._group |
| 85 | + |
| 86 | + def groupId(self): |
| 87 | + return self._groupId |
| 88 | + |
| 89 | + def descriptionfile(self): |
| 90 | + return self._descriptionfile |
| 91 | + |
| 92 | + def initAlgorithm(self, config=None): |
| 93 | + pass |
| 94 | + |
| 95 | + #TODO: show version which is same as OtbAlgorithm rather than always latest. |
| 96 | + def helpUrl(self): |
| 97 | + return "https://www.orfeo-toolbox.org/CookBook/Applications/app_" + self.name() + ".html" |
| 98 | + |
| 99 | + def defineCharacteristicsFromFile(self): |
| 100 | + line = None |
| 101 | + try: |
| 102 | + with open(self._descriptionfile) as lines: |
| 103 | + line = lines.readline().strip('\n').strip() |
| 104 | + self._name = line.split('|')[0] |
| 105 | + self.appkey = self._name |
| 106 | + line = lines.readline().strip('\n').strip() |
| 107 | + self.doc = line |
| 108 | + self.i18n_doc = QCoreApplication.translate("OtbAlgorithm", self.doc) |
| 109 | + #self._name = self._name #+ " - " + self.doc |
| 110 | + self._display_name = self.tr(self._name) |
| 111 | + self.i18n_name = QCoreApplication.translate("OtbAlgorithm", self._name) |
| 112 | + |
| 113 | + line = lines.readline().strip('\n').strip() |
| 114 | + self._group = line |
| 115 | + self.i18n_group = QCoreApplication.translate("OtbAlgorithm", self._group) |
| 116 | + line = lines.readline().strip('\n').strip() |
| 117 | + while line != '': |
| 118 | + line = line.strip('\n').strip() |
| 119 | + if line.startswith('#'): |
| 120 | + line = lines.readline().strip('\n').strip() |
| 121 | + continue |
| 122 | + param = None |
| 123 | + if 'OTBParameterChoice' in line: |
| 124 | + tokens = line.split("|") |
| 125 | + params = [t if str(t) != str(None) else None for t in tokens[1:]] |
| 126 | + options = params[2].split(';') |
| 127 | + param = OtbParameterChoice(params[0], params[1], options, params[3], params[4]) |
| 128 | + else: |
| 129 | + param = getParameterFromString(line) |
| 130 | + |
| 131 | + #if parameter is None, then move to next line and continue |
| 132 | + if param is None: |
| 133 | + line = lines.readline().strip('\n').strip() |
| 134 | + continue |
| 135 | + |
| 136 | + name = param.name() |
| 137 | + if '.' in name and len(name.split('.')) > 2: |
| 138 | + p = name.split('.')[:-2] |
| 139 | + group_key = '.'.join(p) |
| 140 | + group_value = name.split('.')[-2] |
| 141 | + metadata = param.metadata() |
| 142 | + metadata['group_key'] = group_key |
| 143 | + metadata['group_value'] = group_value |
| 144 | + param.setMetadata(metadata) |
| 145 | + |
| 146 | + #'elev.dem.path', 'elev.dem', 'elev.dem.geoid', 'elev.geoid' are special! |
| 147 | + #Even though it is not typical for OTB to fix on parameter keys, |
| 148 | + #we current use below !hack! to set SRTM path and GEOID files |
| 149 | + #Future releases of OTB must follow this rule keep |
| 150 | + #compatibility or update this checklist. |
| 151 | + if name in ["elev.dem.path", "elev.dem"]: |
| 152 | + param.setDefaultValue(OtbUtils.srtmFolder()) |
| 153 | + if name in ["elev.dem.geoid", "elev.geoid"]: |
| 154 | + param.setDefaultValue(OtbUtils.geoidFile()) |
| 155 | + |
| 156 | + self.addParameter(param) |
| 157 | + #parameter is added now and we must move to next line |
| 158 | + line = lines.readline().strip('\n').strip() |
| 159 | + |
| 160 | + except BaseException as e: |
| 161 | + import traceback |
| 162 | + errmsg = "Could not open OTB algorithm from file: \n" + self._descriptionfile + "\nline=" + line + "\nError:\n" + traceback.format_exc() |
| 163 | + QgsMessageLog.logMessage(self.tr(errmsg), self.tr('Processing'), Qgis.Critical) |
| 164 | + raise e |
| 165 | + |
| 166 | + def preprocessParameters(self, parameters): |
| 167 | + valid_params = {} |
| 168 | + for k, v in parameters.items(): |
| 169 | + param = self.parameterDefinition(k) |
| 170 | + #If parameterDefinition(k) return None, this is considered a invalid parameter. |
| 171 | + #just continue for loop |
| 172 | + if param is None: |
| 173 | + continue |
| 174 | + |
| 175 | + #if name of parameter is 'pixtype', |
| 176 | + #it is considered valid if it has value other than float |
| 177 | + if k == 'outputpixeltype' and self.pixelTypes[int(v)] == 'float': |
| 178 | + continue |
| 179 | + # Any other valid parameters have: |
| 180 | + #- empty or no metadata |
| 181 | + #- metadata without a 'group_key' |
| 182 | + #- metadata with 'group_key' and 'group_key' is not in list of parameters. see ParameterGroup in OTB |
| 183 | + #- metadata with 'group_key' and 'group_key' is a valid parameter and it's value == metadata()['group_value'] |
| 184 | + if 'group_key' in param.metadata() and not param.metadata()['group_key'] in parameters: |
| 185 | + valid_params[k] = v |
| 186 | + elif not 'group_key' in param.metadata() or parameters[param.metadata()['group_key']] == param.metadata()['group_value']: |
| 187 | + valid_params[k] = v |
| 188 | + |
| 189 | + return valid_params |
| 190 | + |
| 191 | + def get_value(self, v): |
| 192 | + if isinstance(v, QgsMapLayer): |
| 193 | + return v.source() |
| 194 | + elif isinstance(v, QgsProcessingOutputLayerDefinition): |
| 195 | + return v.sink.staticValue() |
| 196 | + else: |
| 197 | + return str(v) |
| 198 | + |
| 199 | + def outputParameterName(self): |
| 200 | + with open(self._descriptionfile) as df: |
| 201 | + first_line = df.readline().strip() |
| 202 | + tokens = first_line.split("|") |
| 203 | + #params = [t if str(t) != str(None) else None for t in tokens[1:]] |
| 204 | + if len(tokens) == 2: |
| 205 | + return tokens[1] |
| 206 | + else: |
| 207 | + return '' |
| 208 | + |
| 209 | + def processAlgorithm(self, parameters, context, feedback): |
| 210 | + output_key = self.outputParameterName() |
| 211 | + otb_cli_file = OtbUtils.cliPath() |
| 212 | + command = '"{}" {} {}'.format(otb_cli_file, self.name(), OtbUtils.appFolder()) |
| 213 | + for k, v in parameters.items(): |
| 214 | + if k == 'outputpixeltype' or not v: |
| 215 | + continue |
| 216 | + |
| 217 | + if 'epsg' in k and v.startswith('EPSG:'): |
| 218 | + v = v.split('EPSG:')[1] |
| 219 | + |
| 220 | + if isinstance(v, str) and '\n' in v: |
| 221 | + v = v.split('\n') |
| 222 | + if isinstance(v, list): |
| 223 | + value = '' |
| 224 | + for i in list(filter(None, v)): |
| 225 | + value += '"{}" '.format(self.get_value(i)) |
| 226 | + else: |
| 227 | + value = '"{}"'.format(self.get_value(v)) |
| 228 | + |
| 229 | + if k == output_key and 'outputpixeltype' in parameters: |
| 230 | + output_pixel_type = self.pixelTypes[int(parameters['outputpixeltype'])] |
| 231 | + value = '"{}" "{}"'.format(value, output_pixel_type) |
| 232 | + |
| 233 | + command += ' -{} {}'.format(k, value) |
| 234 | + |
| 235 | + QgsMessageLog.logMessage(self.tr('cmd={}'.format(command)), self.tr('Processing'), Qgis.Info) |
| 236 | + if not os.path.exists(otb_cli_file) or not os.path.isfile(otb_cli_file): |
| 237 | + import errno |
| 238 | + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), otb_cli_file) |
| 239 | + |
| 240 | + OtbUtils.executeOtb(command, feedback) |
| 241 | + |
| 242 | + result = {} |
| 243 | + for out in self.destinationParameterDefinitions(): |
| 244 | + filePath = self.parameterAsOutputLayer(parameters, out.name(), context) |
| 245 | + result[out.name()] = filePath |
| 246 | + return result |
0 commit comments