Coverage for enpt_enmapboxapp/enpt_external_algorithm.py: 71%
94 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-03-05 19:40 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-03-05 19:40 +0000
1# -*- coding: utf-8 -*-
3# enpt_enmapboxapp, A QGIS EnMAPBox plugin providing a GUI for the EnMAP processing tools (EnPT)
4#
5# Copyright (C) 2018-2024 Daniel Scheffler (GFZ Potsdam, daniel.scheffler@gfz-potsdam.de)
6#
7# This software was developed within the context of the EnMAP project supported
8# by the DLR Space Administration with funds of the German Federal Ministry of
9# Economic Affairs and Energy (on the basis of a decision by the German Bundestag:
10# 50 EE 1529) and contributions from DLR, GFZ and OHB System AG.
11#
12# This program is free software: you can redistribute it and/or modify it under
13# the terms of the GNU Lesser General Public License as published by the Free
14# Software Foundation, either version 3 of the License, or (at your option) any
15# later version.
16#
17# This program is distributed in the hope that it will be useful, but WITHOUT
18# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
20# details.
21#
22# You should have received a copy of the GNU Lesser General Public License along
23# with this program. If not, see <https://www.gnu.org/licenses/>.
25"""This module provides the ExternalEnPTAlgorithm which is used in case EnPT is installed into separate environment."""
27import os
28from subprocess import check_output, CalledProcessError
29from glob import glob
30from qgis.core import \
31 (QgsProcessingContext,
32 QgsProcessingFeedback,
33 QgsProcessingParameterFile,
34 NULL
35 )
37from ._enpt_alg_base import _EnPTBaseAlgorithm
40class ExternalEnPTAlgorithm(_EnPTBaseAlgorithm):
41 # Input parameters
42 P_conda_root = 'conda_root'
44 def initAlgorithm(self, configuration=None):
45 self.addParameter(QgsProcessingParameterFile(
46 name=self.P_conda_root,
47 description='Conda root directory (which contains the EnPT Python environment in a subdirectory)',
48 behavior=QgsProcessingParameterFile.Folder,
49 defaultValue=self._get_default_conda_root(),
50 optional=True))
52 super().initAlgorithm(configuration=configuration)
54 @staticmethod
55 def _get_default_conda_root():
56 if os.getenv('CONDA_ROOT') and os.path.exists(os.getenv('CONDA_ROOT')):
57 return os.getenv('CONDA_ROOT')
58 elif os.name == 'nt':
59 return 'C:\\ProgramData\\Anaconda3'
60 else:
61 return '' # FIXME is there a default location in Linux/OSX?
63 @staticmethod
64 def _locate_EnPT_Conda_environment(user_root):
65 conda_rootdir = None
67 if user_root and os.path.exists(user_root):
68 conda_rootdir = user_root
70 elif os.getenv('CONDA_ROOT') and os.path.exists(os.getenv('CONDA_ROOT')):
71 conda_rootdir = os.getenv('CONDA_ROOT')
73 elif os.getenv('CONDA_EXE') and os.path.exists(os.getenv('CONDA_EXE')):
74 p = os.getenv('CONDA_EXE')
75 conda_rootdir = os.path.abspath(os.path.join(p, '..', '..'))
77 else:
78 possPaths = [
79 'C:\\ProgramData\\Anaconda3',
80 'C:\\Users\\%s\\Anaconda3' % os.getenv('username')
81 ] if os.name == 'nt' else \
82 []
84 for rootDir in possPaths:
85 if os.path.exists(rootDir):
86 conda_rootdir = rootDir
88 if not conda_rootdir:
89 raise NotADirectoryError("No valid Conda root directory given - "
90 "neither via the GUI, nor via the 'CONDA_ROOT' environment variable.")
92 # set ENPT_PYENV_ACTIVATION environment variable
93 os.environ['ENPT_PYENV_ACTIVATION'] = \
94 os.path.join(conda_rootdir, 'Scripts', 'activate.bat') if os.name == 'nt' else \
95 os.path.join(conda_rootdir, 'bin', 'activate')
97 if not os.path.exists(os.getenv('ENPT_PYENV_ACTIVATION')):
98 raise FileNotFoundError(os.getenv('ENPT_PYENV_ACTIVATION'))
100 return conda_rootdir
102 @staticmethod
103 def _is_enpt_environment_present(conda_rootdir):
104 return os.path.exists(os.path.join(conda_rootdir, 'envs', 'enpt'))
106 @staticmethod
107 def _locate_enpt_run_script(conda_rootdir=None):
108 if conda_rootdir:
109 if os.name == 'nt':
110 # Windows
111 p_exp = os.path.join(conda_rootdir, 'envs', 'enpt', 'Scripts', 'enpt_run_cmd.bat')
112 else:
113 # Linux / OSX
114 p_exp = os.path.join(conda_rootdir, 'envs', 'enpt', 'bin', 'enpt_run_cmd.sh')
116 if os.path.isfile(p_exp):
117 return p_exp
119 try:
120 if os.name == 'nt':
121 # Windows
122 return check_output('where enpt_run_cmd.bat', shell=True).decode('UTF-8').strip()
123 # return "D:\\Daten\\Code\\python\\enpt_enmapboxapp\\bin\\enpt_run_cmd.bat"
124 else:
125 # Linux / OSX
126 return check_output('which enpt_run_cmd.sh', shell=True).decode('UTF-8').strip()
127 # return 'enpt_run_cmd.sh '
129 except CalledProcessError:
130 raise EnvironmentError('The EnPT run script could not be found. Please make sure, that enpt_enmapboxapp '
131 'is correctly installed into your QGIS Python environment.')
133 @staticmethod
134 def _prepare_enpt_environment() -> dict:
135 os.environ['PYTHONUNBUFFERED'] = '1'
136 os.environ['IS_ENPT_GUI_CALL'] = '1'
138 enpt_env = os.environ.copy()
139 enpt_env["PATH"] = ';'.join([i for i in enpt_env["PATH"].split(';') if 'OSGEO' not in i]) # actually not needed
140 if "PYTHONHOME" in enpt_env.keys():
141 del enpt_env["PYTHONHOME"]
142 if "PYTHONPATH" in enpt_env.keys():
143 del enpt_env["PYTHONPATH"]
145 # FIXME is this needed?
146 enpt_env['IPYTHONENABLE'] = 'True'
147 enpt_env['PROMPT'] = '$P$G'
148 enpt_env['PYTHONDONTWRITEBYTECODE'] = '1'
149 enpt_env['PYTHONIOENCODING'] = 'UTF-8'
150 enpt_env['TEAMCITY_VERSION'] = 'LOCAL'
151 enpt_env['O4W_QT_DOC'] = 'C:/OSGEO4~3/apps/Qt5/doc'
152 if 'SESSIONNAME' in enpt_env.keys():
153 del enpt_env['SESSIONNAME']
155 # import pprint
156 # s = pprint.pformat(enpt_env)
157 # with open('D:\\env.json', 'w') as fp:
158 # fp.write(s)
160 return enpt_env
162 def processAlgorithm(self, parameters: dict, context: QgsProcessingContext, feedback: QgsProcessingFeedback):
163 conda_root = self._locate_EnPT_Conda_environment(parameters[self.P_conda_root])
164 feedback.pushInfo('Found Conda installation at %s.' % conda_root)
166 if self._is_enpt_environment_present(conda_root):
167 feedback.pushInfo("The Conda installation contains the 'enpt' environment as expected.")
168 else:
169 envs = list(sorted([i.split(os.sep)[-2] for i in glob(os.path.join(conda_root, 'envs', '*') + os.sep)]))
170 feedback.reportError(
171 "The Conda installation has no environment called 'enpt'. Existing environments are named %s. Please "
172 "follow the EnPT installation instructions to install the EnMAP processing tool backend code "
173 "(see https://enmap.git-pages.gfz-potsdam.de/GFZ_Tools_EnMAP_BOX/EnPT/doc/installation.html). "
174 "This is needed to run EnPT from this GUI." % ', '.join(envs)
175 )
176 return {
177 'success': False,
178 self.P_OUTPUT_RASTER: '',
179 # self.P_OUTPUT_VECTOR: parameters[self.P_OUTPUT_RASTER],
180 # self.P_OUTPUT_FILE: parameters[self.P_OUTPUT_RASTER],
181 self.P_OUTPUT_FOLDER: ''
182 }
184 parameters = self._get_preprocessed_parameters(parameters)
186 # print parameters and console call to log
187 # for key in sorted(parameters):
188 # feedback.pushInfo('{} = {}'.format(key, repr(parameters[key])))
189 keyval_str = ' '.join(['--{} {}'.format(key, parameters[key])
190 for key in sorted(parameters)
191 if parameters[key] not in [None, NULL, 'NULL', '']])
192 print(parameters)
193 print(keyval_str + '\n\n')
194 feedback.pushInfo("\nCalling EnPT with the following command:\n"
195 "enpt %s\n\n" % keyval_str)
197 # prepare environment for subprocess
198 enpt_env = self._prepare_enpt_environment()
199 path_enpt_runscript = self._locate_enpt_run_script(conda_root)
200 print('RUNSCRIPT: ' + path_enpt_runscript)
202 # run EnPT in subprocess that activates the EnPT Conda environment
203 feedback.pushDebugInfo('Using %s to start EnPT.' % path_enpt_runscript)
204 feedback.pushInfo("The log messages of the EnMAP processing tool are written to the *.log file "
205 "in the specified output folder.")
207 exitcode = self._run_cmd(f"{path_enpt_runscript} {keyval_str}",
208 qgis_feedback=feedback,
209 env=enpt_env)
211 return self._handle_results(parameters, feedback, exitcode)