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

1# -*- coding: utf-8 -*- 

2 

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/>. 

24 

25"""This module provides the ExternalEnPTAlgorithm which is used in case EnPT is installed into separate environment.""" 

26 

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 ) 

36 

37from ._enpt_alg_base import _EnPTBaseAlgorithm 

38 

39 

40class ExternalEnPTAlgorithm(_EnPTBaseAlgorithm): 

41 # Input parameters 

42 P_conda_root = 'conda_root' 

43 

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

51 

52 super().initAlgorithm(configuration=configuration) 

53 

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? 

62 

63 @staticmethod 

64 def _locate_EnPT_Conda_environment(user_root): 

65 conda_rootdir = None 

66 

67 if user_root and os.path.exists(user_root): 

68 conda_rootdir = user_root 

69 

70 elif os.getenv('CONDA_ROOT') and os.path.exists(os.getenv('CONDA_ROOT')): 

71 conda_rootdir = os.getenv('CONDA_ROOT') 

72 

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, '..', '..')) 

76 

77 else: 

78 possPaths = [ 

79 'C:\\ProgramData\\Anaconda3', 

80 'C:\\Users\\%s\\Anaconda3' % os.getenv('username') 

81 ] if os.name == 'nt' else \ 

82 [] 

83 

84 for rootDir in possPaths: 

85 if os.path.exists(rootDir): 

86 conda_rootdir = rootDir 

87 

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.") 

91 

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') 

96 

97 if not os.path.exists(os.getenv('ENPT_PYENV_ACTIVATION')): 

98 raise FileNotFoundError(os.getenv('ENPT_PYENV_ACTIVATION')) 

99 

100 return conda_rootdir 

101 

102 @staticmethod 

103 def _is_enpt_environment_present(conda_rootdir): 

104 return os.path.exists(os.path.join(conda_rootdir, 'envs', 'enpt')) 

105 

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') 

115 

116 if os.path.isfile(p_exp): 

117 return p_exp 

118 

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 ' 

128 

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.') 

132 

133 @staticmethod 

134 def _prepare_enpt_environment() -> dict: 

135 os.environ['PYTHONUNBUFFERED'] = '1' 

136 os.environ['IS_ENPT_GUI_CALL'] = '1' 

137 

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"] 

144 

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'] 

154 

155 # import pprint 

156 # s = pprint.pformat(enpt_env) 

157 # with open('D:\\env.json', 'w') as fp: 

158 # fp.write(s) 

159 

160 return enpt_env 

161 

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) 

165 

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 } 

183 

184 parameters = self._get_preprocessed_parameters(parameters) 

185 

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) 

196 

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) 

201 

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.") 

206 

207 exitcode = self._run_cmd(f"{path_enpt_runscript} {keyval_str}", 

208 qgis_feedback=feedback, 

209 env=enpt_env) 

210 

211 return self._handle_results(parameters, feedback, exitcode)