Coverage for enpt/cli.py: 89%

81 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-03-07 11:39 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

4# EnPT, EnMAP Processing Tool - A Python package for pre-processing of EnMAP Level-1B data 

5# 

6# Copyright (C) 2018-2024 Karl Segl (GFZ Potsdam, segl@gfz-potsdam.de), Daniel Scheffler 

7# (GFZ Potsdam, danschef@gfz-potsdam.de), Niklas Bohn (GFZ Potsdam, nbohn@gfz-potsdam.de), 

8# Stephane Guillaso (GFZ Potsdam, stephane.guillaso@gfz-potsdam.de) 

9# 

10# This software was developed within the context of the EnMAP project supported 

11# by the DLR Space Administration with funds of the German Federal Ministry of 

12# Economic Affairs and Energy (on the basis of a decision by the German Bundestag: 

13# 50 EE 1529) and contributions from DLR, GFZ and OHB System AG. 

14# 

15# This program is free software: you can redistribute it and/or modify it under 

16# the terms of the GNU General Public License as published by the Free Software 

17# Foundation, either version 3 of the License, or (at your option) any later 

18# version. Please note the following exception: `EnPT` depends on tqdm, which 

19# is distributed under the Mozilla Public Licence (MPL) v2.0 except for the files 

20# "tqdm/_tqdm.py", "setup.py", "README.rst", "MANIFEST.in" and ".gitignore". 

21# Details can be found here: https://github.com/tqdm/tqdm/blob/master/LICENCE. 

22# 

23# This program is distributed in the hope that it will be useful, but WITHOUT 

24# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

25# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 

26# details. 

27# 

28# You should have received a copy of the GNU Lesser General Public License along 

29# with this program. If not, see <https://www.gnu.org/licenses/>. 

30 

31"""EnPT console argument parser.""" 

32 

33import argparse 

34import sys 

35 

36from enpt import __version__ 

37from enpt.options.config import EnPTConfig 

38from enpt.execution.controller import EnPT_Controller 

39 

40__author__ = 'Daniel Scheffler' 

41 

42 

43def get_enpt_argparser(): 

44 """Return argument parser for the 'enpt' program.""" 

45 

46 ########################################################## 

47 # CONFIGURE MAIN PARSER FOR THE EnPT PREPROCESSING CHAIN # 

48 ########################################################## 

49 

50 parser = argparse.ArgumentParser( 

51 prog='enpt', 

52 description='=' * 70 + '\n' + 'EnMAP Processing Tool console argument parser. ', 

53 epilog="use '>>> enpt -h' for detailed documentation and usage hints.") 

54 

55 add = parser.add_argument 

56 add('--version', action='version', version=__version__) 

57 

58 # NOTE: don't define any defaults here for parameters that are passed to EnPTConfig! 

59 # -> otherwise, we cannot distinguish between explicity given parameters and default values 

60 # => see docs in parsedArgs_to_user_opts() for explanation 

61 add('-jc', '--json_config', nargs='?', type=str, 

62 help='file path of a JSON file containing options. See here for an example: ' 

63 'https://git.gfz-potsdam.de/EnMAP/GFZ_Tools_EnMAP_BOX/' 

64 'EnPT/blob/main/enpt/options/options_default.json') 

65 add('--CPUs', type=int, default=None, 

66 help='number of CPU cores to be used for processing (default: "None" -> use all available)') 

67 add('-im', '--path_l1b_enmap_image', type=str, default=None, 

68 help='input path of the EnMAP L1B image to be processed ' 

69 '(zip-archive or root directory; must be given if not contained in --json-config.)') 

70 add('-imgap', '--path_l1b_enmap_image_gapfill', type=str, default=None, 

71 help='input path of an adjacent EnMAP L1B image to be used for gap-filling (zip-archive or root directory)') 

72 add('-dem', '--path_dem', type=str, default=None, 

73 help='input path of digital elevation model in map or sensor geometry; GDAL compatible file format ' 

74 '(must cover the EnMAP L1B data completely if given in map geometry or must have the same pixel ' 

75 'dimensions like the EnMAP L1B data if given in sensor geometry)') 

76 add('-dummyfmt', '--is_dummy_dataformat', type=_str2bool, default=False, nargs='?', const=True, 

77 help='Set to true in case of the preliminary, GFZ-internal dataformat as used for the Alpine test dataset. ' 

78 '(default: False. Note: This will be removed in future.)') 

79 add('-ele', '--average_elevation', type=int, default=0, 

80 help='average elevation in meters above sea level; may be provided if no DEM is available; ' 

81 'ignored if DEM is given') 

82 add('-od', '--output_dir', type=str, default=None, 

83 help='output directory where processed data and log files are saved') 

84 add('-of', '--output_format', type=str, default='GTiff', 

85 help="file format of all raster output files ('GTiff': GeoTIFF, 'ENVI': ENVI BSQ; default: 'ENVI')") 

86 add('-ointlv', '--output_interleave', type=str, default='pixel', 

87 help="raster data interleaving type ('band', 'line', 'pixel'; default: 'pixel')") 

88 add('-ond', '--output_nodata_value', type=int, default=-32768, 

89 help="output no-data/background value (should be within the integer 16-bit range)") 

90 add('-wd', '--working_dir', type=str, default=None, 

91 help='directory to be used for temporary files') 

92 add('-nla', '--n_lines_to_append', type=int, default=None, 

93 help='number of lines to be added to the main image [if None, use the whole imgap]. Requires --imgap to be set') 

94 add('-dbb', '--drop_bad_bands', type=_str2bool, default=True, 

95 help='if set to True (default), the water absorption bands between 1358 and 1453 nm as well as between 1814 ' 

96 'and 1961 nm are excluded from processing and will not be contained in the L2A product') 

97 add('-dpb', '--disable_progress_bars', type=_str2bool, default=False, nargs='?', const=True, 

98 help='whether to disable all progress bars during processing') 

99 add('--path_earthSunDist', type=str, default=None, 

100 help='input path of the earth sun distance model') 

101 add('--path_solar_irr', type=str, default=None, 

102 help='input path of the solar irradiance model') 

103 add('--scale_factor_toa_ref', type=int, default=None, 

104 help='scale factor to be applied to TOA reflectance result') 

105 add('--enable_keystone_correction', type=_str2bool, default=False, nargs='?', const=True, 

106 help='Enable keystone correction') 

107 add('--enable_vnir_swir_coreg', type=_str2bool, default=False, nargs='?', const=True, 

108 help='Enable VNIR/SWIR co-registration') 

109 add('--path_reference_image', type=str, default=None, 

110 help='Reference image for co-registration.') 

111 add('--polymer_root', type=str, default=None, 

112 help='Polymer root directory (that contains the subdirectory for ancillary data)') 

113 add('--enable_ac', type=_str2bool, default=True, nargs='?', const=True, 

114 help="Enable atmospheric correction using SICOR algorithm (default: True). If False, the L2A output contains " 

115 "top-of-atmosphere reflectance") 

116 add('--mode_ac', type=str, default=None, nargs='?', 

117 help="3 modes to determine which atmospheric correction is applied at which surfaces (default: land): " 

118 "('land', water', 'combined')") 

119 add('--polymer_additional_results', type=_str2bool, default=True, nargs='?', const=True, 

120 help="Enable the generation of additional results when running ACwater/POLYMER (default: True)") 

121 add('--auto_download_ecmwf', type=_str2bool, default=True, nargs='?', const=True, 

122 help='Automatically download ECMWF AUX data when running Polymer atmospheric correction for water surfaces') 

123 add('--scale_factor_boa_ref', type=int, default=10000, 

124 help='Scale factor to be applied to BOA reflectance result') 

125 add('--threads', type=int, default=-1, 

126 help='Number of threads in ACwater Polymer: 0 for single thread; < 0 for as many as there are CPUs; ' 

127 'and > 0 gives the number of threads') 

128 add('--blocksize', type=int, default=100, 

129 help='Block size in ACwater Polymer') 

130 add('--run_smile_P', type=_str2bool, default=False, nargs='?', const=True, 

131 help='Enable extra smile detection and correction (provider smile coefficients are ignored)') 

132 add('--run_deadpix_P', type=_str2bool, default=False, nargs='?', const=True, 

133 help='Enable dead pixel correction') 

134 add('--deadpix_P_algorithm', type=str, default="spatial", 

135 help="Algorithm for dead pixel correction ('spectral' or 'spatial')") 

136 add('--deadpix_P_interp_spectral', type=str, default="linear", 

137 help="Spectral interpolation algorithm to be used during dead pixel correction " 

138 "('linear', 'quadratic', 'cubic')") 

139 add('--deadpix_P_interp_spatial', type=str, default="linear", 

140 help="Spatial interpolation algorithm to be used during dead pixel correction " 

141 "('linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic')") 

142 add('--ortho_resampAlg', type=str, default='bilinear', 

143 help="Ortho-rectification resampling algorithm ('nearest', 'bilinear', 'gauss', 'cubic', 'cubic_spline', " 

144 "'lanczos', 'average', 'mode', 'max', 'min', 'med', 'q1', 'q3')") 

145 add('--vswir_overlap_algorithm', type=str, default='swir_only', 

146 help="Algorithm specifying how to deal with the spectral bands in the VNIR/SWIR spectral overlap region " 

147 "('order_by_wvl', 'average', 'vnir_only', 'swir_only')") 

148 add('-tgtprj', '--target_projection_type', type=str, default='UTM', 

149 help="Projection type of the raster output files ('UTM', 'Geographic') (default: 'UTM')") 

150 add('-tgtepsg', '--target_epsg', type=int, default=None, 

151 help="Custom EPSG code of the target projection (overrides target_projection_type)") 

152 add('-tgtgrid', '--target_coord_grid', nargs=4, type=float, default=None, 

153 help="Custom target coordinate grid where is output is resampled to ([x0, x1, y0, y1], e.g., [0, 30, 0, 30])") 

154 

155 # link parser to run function 

156 parser.set_defaults(func=run_job) 

157 

158 return parser 

159 

160 

161def parsedArgs_to_user_opts(cli_args: argparse.Namespace) -> dict: 

162 """Convert argparse Namespace object to dictionary of explicitly given parameters. 

163 

164 NOTE: All options that have not been given explicitly (None values) are removed. Reason: EnPTConfig prefers 

165 directly passed arguments against those that are passed withi a JSON config file. 

166 So, e.g., if CPUs=None (default), the 'CPUs' parameter given within a JSON config file would be overridden. 

167 

168 => only override JSON configuration if parameters are explicitly given (e.g., CPUs is set to 10) 

169 => if json_opts are given: default options are overridden with the options in the JSON config. 

170 

171 :param cli_args: options as parsed by the argparse.ArgumentParser 

172 """ 

173 # convert argparse Namespace object to dictionary 

174 opts = {k: v for k, v in vars(cli_args).items() if not k.startswith('_') and k != 'func'} 

175 

176 # activate absolute coreg if a reference image is given (the argparser does not separate these two options) 

177 if opts['path_reference_image']: 

178 opts['enable_absolute_coreg'] = True 

179 

180 # remove those options that have not been given explicitly (None values) 

181 user_opts = dict() 

182 for k, v in opts.items(): 

183 # values are None if they are not given by the user -> don't pass to set_config 

184 if v is None: 

185 continue 

186 else: 

187 user_opts.update({k: v}) 

188 

189 return user_opts 

190 

191 

192def get_config(cli_args: argparse.Namespace): 

193 return EnPTConfig(**parsedArgs_to_user_opts(cli_args)) 

194 

195 

196def run_job(config: EnPTConfig): 

197 CTR = EnPT_Controller(config) 

198 CTR.run_all_processors() 

199 

200 

201def _str2bool(v): 

202 """Convert string parameter to bool. 

203 

204 From: https://stackoverflow.com/a/43357954/2952871 

205 

206 :param v: 

207 :return: 

208 """ 

209 if isinstance(v, bool): 

210 return v 

211 if v.lower() in ('yes', 'true', 't', 'y', '1'): 

212 return True 

213 elif v.lower() in ('no', 'false', 'f', 'n', '0'): 

214 return False 

215 else: 

216 raise argparse.ArgumentTypeError('Boolean value expected.') 

217 

218 

219def main(parsed_args: argparse.Namespace = None) -> int: 

220 """Run the argument parser and forward the arguments to the linked functions. 

221 

222 :param parsed_args: argparse.Namespace instance of already parsed arguments 

223 (allows to call main() from test_cli_parser.py while passing specific arguments) 

224 

225 :return: exitcode (0: all fine, >=1: failed) 

226 """ 

227 if not parsed_args: 

228 parsed_args = get_enpt_argparser().parse_args() # type: argparse.Namespace 

229 

230 parsed_args.func(get_config(parsed_args)) 

231 

232 print('\nready.') 

233 

234 return 0 

235 

236 

237if __name__ == '__main__': 

238 sys.exit(main()) # pragma: no cover