OTBAlgorithm.py 10.8 KB
Newer Older
volaya's avatar
volaya committed
1
2
3
4
# -*- coding: utf-8 -*-
"""
***************************************************************************
    OTBAlgorithm.py
Rashad Kanavath's avatar
Rashad Kanavath committed
5
6
7
8
9
10
    ---------------
    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)

volaya's avatar
volaya committed
11
12
13
14
15
16
17
18
19
20
***************************************************************************
*                                                                         *
*   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.                                   *
*                                                                         *
***************************************************************************
"""

Rashad Kanavath's avatar
Rashad Kanavath committed
21
22
23
24
__author__ = 'Rashad Kanavath'
__date__ = 'June 2017'
__copyright__ = "(C) 2017,2018 by CS Systemes d'information (CS SI), Centre National d'Etudes et spatiales (CNES)"

volaya's avatar
volaya committed
25
# This will get replaced with a git SHA1 when you do a git archive
Rashad Kanavath's avatar
Rashad Kanavath committed
26

volaya's avatar
volaya committed
27
28
29
__revision__ = '$Format:%H$'

import os
30
pluginPath = os.path.split(os.path.dirname(__file__))[0]
volaya's avatar
volaya committed
31

Rashad Kanavath's avatar
Rashad Kanavath committed
32
33
from qgis.PyQt.QtCore import QCoreApplication
from qgis.PyQt.QtGui import QIcon
34

35
36
37
38
39
40
41
42
43
44
45
from qgis.core import (Qgis,
    QgsMessageLog,
    QgsRasterLayer,
    QgsMapLayer,
    QgsProcessingAlgorithm,
    QgsProcessingParameterMultipleLayers,
    QgsProcessingParameterDefinition,
    QgsProcessingOutputLayerDefinition,
    QgsProcessingParameterString,
    QgsProcessingParameterNumber,
    QgsProcessingParameterEnum)
volaya's avatar
volaya committed
46

47
from processing.core.parameters import getParameterFromString
volaya's avatar
volaya committed
48

Rashad Kanavath's avatar
Rashad Kanavath committed
49
from otb.OTBChoiceWidget import OTBParameterChoice
50
from otb import OTBUtils
volaya's avatar
volaya committed
51

52
class OTBAlgorithm(QgsProcessingAlgorithm):
Rashad Kanavath's avatar
Rashad Kanavath committed
53
54
55
56
57
    def __init__(self, group, name, descriptionfile, display_name='', groupId=''):
        super().__init__()
        self._name = name
        self._group = group
        self._display_name = display_name
58

Rashad Kanavath's avatar
Rashad Kanavath committed
59
60
61
62
        self._groupId = ''
        validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:'
        if not groupId:
            self._groupId = ''.join(c for c in self._group if c in validChars)
63
64

        self.pixelTypes = ['unit8','int','float','double']
Rashad Kanavath's avatar
Rashad Kanavath committed
65
        self._descriptionfile = descriptionfile
66
        self.defineCharacteristicsFromFile()
volaya's avatar
volaya committed
67

Rashad Kanavath's avatar
Rashad Kanavath committed
68
69
70
71
72
73
74
    # def canExecute( self, msg=None):
    #     if len(self.parameterDefinitions()) > 0:
    #         return True, None
    #     try:
    #         self.defineCharacteristicsFromFile()
    #     except BaseException as e:
    #         return False, None
75

Rashad Kanavath's avatar
Rashad Kanavath committed
76
    #     return True, None
Rashad Kanavath's avatar
Rashad Kanavath committed
77

78
    # TODO: check if this method is really required.
79
80
81
82
    def checkParameterValues(self, parameters, context):
        for key, value in parameters.items():
            param = self.parameterDefinition(key)
            if not param.checkValueIsAcceptable(value, context):
83
                return False, "Incorrect parameter value for {}".format(param.name())
84
85
        return True, None

Rashad Kanavath's avatar
Rashad Kanavath committed
86
    def icon(self):
87
        return QIcon(os.path.join(pluginPath, 'otb', 'otb.png'))
88

Rashad Kanavath's avatar
Rashad Kanavath committed
89
90
91
92
93
94
95
96
    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
97

Rashad Kanavath's avatar
Rashad Kanavath committed
98
99
    def displayName(self):
        return self._display_name
100

Rashad Kanavath's avatar
Rashad Kanavath committed
101
102
103
104
105
106
107
108
109
110
111
112
    def group(self):
        return self._group

    def groupId(self):
        return self._groupId

    def descriptionfile(self):
        return self._descriptionfile

    def initAlgorithm(self, config=None):
        pass

113
114
115
    #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"
116

volaya's avatar
volaya committed
117
    def defineCharacteristicsFromFile(self):
118
        line = None
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
        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)
Rashad Kanavath's avatar
Rashad Kanavath committed
134
                line = lines.readline().strip('\n').strip()
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
                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)

                    #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())

                    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
Rashad Kanavath's avatar
Rashad Kanavath committed
183
184

    def preprocessParameters(self, parameters):
185
        valid_params = {}        
Rashad Kanavath's avatar
Rashad Kanavath committed
186
        for k,v in parameters.items():
187
            param = self.parameterDefinition(k)
188
189
190
            #If parameterDefinition(k) return None, this is considered a invalid parameter.
            #just continue for loop
            if param is None:
191
                continue
192
193
194
195
196
197
198
199
200
201
202
203
204

            #if name of parameter is 'pixtype',
            #it is considered valid if it has value other than float
            if k == 'outputpixeltype' and self.pixelTypes[int(v)] == 'float':
                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 not param.metadata()['group_key'] in parameters:
                valid_params[k] = v
            elif not 'group_key' in param.metadata() or parameters[param.metadata()['group_key']] == param.metadata()['group_value']:
205
                valid_params[k] = v
206

207
        return valid_params
Rashad Kanavath's avatar
Rashad Kanavath committed
208
209
210

    def get_value(self, v):
        if isinstance(v, QgsMapLayer):
211
            return v.source()
Rashad Kanavath's avatar
Rashad Kanavath committed
212
213
        elif isinstance(v, QgsProcessingOutputLayerDefinition):
            return v.sink.staticValue()
volaya's avatar
volaya committed
214
        else:
Rashad Kanavath's avatar
Rashad Kanavath committed
215
            return str(v)
216

217
    def outputParameterName(self):
218
219
220
        with open(self._descriptionfile) as df:
            first_line = df.readline().strip()
            tokens = first_line.split("|")
221
222
223
            #params = [t if str(t) != str(None) else None for t in tokens[1:]]
            if len(tokens) == 2:
                return tokens[1]
224
            else:
225
226
                return ''

Rashad Kanavath's avatar
Rashad Kanavath committed
227
    def processAlgorithm(self, parameters, context, feedback):
228
        output_key = self.outputParameterName()
229
        otb_cli_file  = OTBUtils.cliPath()
230
        command = '"{}" {} {}'.format(otb_cli_file, self.name(), OTBUtils.appFolder())
Rashad Kanavath's avatar
Rashad Kanavath committed
231
        for k, v in parameters.items():
Rashad Kanavath's avatar
Rashad Kanavath committed
232
            if k == 'outputpixeltype' or not v:
233
                continue
Rashad Kanavath's avatar
Rashad Kanavath committed
234

Rashad Kanavath's avatar
Rashad Kanavath committed
235
236
237
            if 'epsg' in k and v.startswith('EPSG:'):
                v = v.split('EPSG:')[1]

238
239
            if isinstance(v, str) and '\n' in v:
                v = v.split('\n')
Rashad Kanavath's avatar
Rashad Kanavath committed
240
            if isinstance(v, list):
Rashad Kanavath's avatar
Rashad Kanavath committed
241
                value = ''
242
                for i in list(filter(None, v)):
Rashad Kanavath's avatar
Rashad Kanavath committed
243
                    value += '"{}" '.format(self.get_value(i))
Rashad Kanavath's avatar
Rashad Kanavath committed
244
            else:
Rashad Kanavath's avatar
Rashad Kanavath committed
245
                value = '"{}"'.format(self.get_value(v))
246
247

            if k == output_key and 'outputpixeltype' in parameters:
Rashad Kanavath's avatar
Rashad Kanavath committed
248
249
                output_pixel_type = self.pixelTypes[int(parameters['outputpixeltype'])]
                value = '"{}" "{}"'.format(value, output_pixel_type)
250

Rashad Kanavath's avatar
Rashad Kanavath committed
251
            command += ' -{} {}'.format(k, value)
Rashad Kanavath's avatar
Rashad Kanavath committed
252

253
        QgsMessageLog.logMessage(self.tr('cmd={}'.format(command)), self.tr('Processing'), Qgis.Info)
254
255
256
257
        if not os.path.exists(otb_cli_file) or not os.path.isfile(otb_cli_file):
            import errno
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), otb_cli_file)

Rashad Kanavath's avatar
Rashad Kanavath committed
258
        OTBUtils.executeOtb(command, feedback)
259

Rashad Kanavath's avatar
Rashad Kanavath committed
260
        result = {}
261
262
263
        for out in self.destinationParameterDefinitions():
            filePath = self.parameterAsOutputLayer(parameters, out.name(), context)
            result[out.name()] = filePath
Rashad Kanavath's avatar
Rashad Kanavath committed
264
        return result