• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OGGM / oggm / 3836586642

pending completion
3836586642

Pull #1510

github

GitHub
Merge 2a19b5c72 into 8ab12c504
Pull Request #1510: Fix issue with TANDEM DEM and minor RGI7 issue

2 of 4 new or added lines in 2 files covered. (50.0%)

249 existing lines in 7 files now uncovered.

12099 of 13681 relevant lines covered (88.44%)

3.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.31
/oggm/cli/prepro_levels.py
1
"""Command line arguments to the oggm_prepro command
2

3
Type `$ oggm_prepro -h` for help
4

5
"""
6

7
# External modules
8
import os
1✔
9
import sys
1✔
10
import shutil
1✔
11
import argparse
1✔
12
import time
1✔
13
import logging
1✔
14
import pandas as pd
1✔
15
import numpy as np
1✔
16
import geopandas as gpd
1✔
17

18
# Locals
19
import oggm.cfg as cfg
1✔
20
from oggm import utils, workflow, tasks, GlacierDirectory
1✔
21
from oggm.core import gis
1✔
22
from oggm.exceptions import InvalidParamsError, InvalidDEMError
1✔
23

24
# Module logger
25
from oggm.utils import get_prepro_base_url, file_downloader
1✔
26

27
log = logging.getLogger(__name__)
1✔
28

29

30
@utils.entity_task(log)
1✔
31
def _rename_dem_folder(gdir, source=''):
1✔
32
    """Put the DEM files in a subfolder of the gdir.
33

34
    Parameters
35
    ----------
36
    gdir : GlacierDirectory
37
    source : str
38
        the DEM source
39
    """
40

41
    # open tif-file to check if it's worth it
UNCOV
42
    dem_f = gdir.get_filepath('dem')
×
UNCOV
43
    try:
×
UNCOV
44
        dem = gis.read_geotiff_dem(gdir)
×
45
    except IOError:
×
46
        # Error reading file, no problem - still, delete the file if needed
47
        if os.path.exists(dem_f):
×
48
            os.remove(dem_f)
×
49
        gdir.log('{},DEM SOURCE,{}'.format(gdir.rgi_id, source),
×
50
                 err=InvalidDEMError('File does not exist'))
51
        return
×
52

53
    # Check the DEM
UNCOV
54
    isfinite = np.isfinite(dem)
×
UNCOV
55
    if np.all(~isfinite) or (np.min(dem) == np.max(dem)):
×
56
        # Remove the file and return
57
        if os.path.exists(dem_f):
×
58
            os.remove(dem_f)
×
59
        gdir.log('{},DEM SOURCE,{}'.format(gdir.rgi_id, source),
×
60
                 err=InvalidDEMError('DEM does not contain more than one '
61
                                     'valid values.'))
62
        return
×
63

64
    # Create a source dir and move the files
UNCOV
65
    out = os.path.join(gdir.dir, source)
×
UNCOV
66
    utils.mkdir(out)
×
UNCOV
67
    for fname in ['dem', 'dem_source']:
×
UNCOV
68
        f = gdir.get_filepath(fname)
×
UNCOV
69
        os.rename(f, os.path.join(out, os.path.basename(f)))
×
70

71
    # log SUCCESS for this DEM source
UNCOV
72
    gdir.log('{},DEM SOURCE,{}'.format(gdir.rgi_id, source))
×
73

74

75
def run_prepro_levels(rgi_version=None, rgi_reg=None, border=None,
1✔
76
                      output_folder='', working_dir='', dem_source='',
77
                      is_test=False, test_ids=None, demo=False, test_rgidf=None,
78
                      test_intersects_file=None, test_topofile=None,
79
                      disable_mp=False, params_file=None, elev_bands=False,
80
                      match_regional_geodetic_mb=False,
81
                      match_geodetic_mb_per_glacier=False,
82
                      evolution_model='fl_sia',
83
                      centerlines_only=False, override_params=None,
84
                      add_consensus=False, start_level=None,
85
                      start_base_url=None, max_level=5, ref_tstars_base_url='',
86
                      logging_level='WORKFLOW', disable_dl_verify=False,
87
                      dynamic_spinup=False, err_dmdtda_scaling_factor=1,
88
                      dynamic_spinup_start_year=1979,
89
                      continue_on_error=True):
90
    """Generate the preprocessed OGGM glacier directories for this OGGM version
91

92
    Parameters
93
    ----------
94
    rgi_version : str
95
        the RGI version to use (defaults to cfg.PARAMS)
96
    rgi_reg : str
97
        the RGI region to process
98
    border : int
99
        the number of pixels at the maps border
100
    output_folder : str
101
        path to the output folder (where to put the preprocessed tar files)
102
    dem_source : str
103
        which DEM source to use: default, SOURCE_NAME or ALL
104
    working_dir : str
105
        path to the OGGM working directory
106
    ref_tstars_base_url : str
107
        url where to find the pre-calibrated reference tstar list.
108
        Required as of v1.4.
109
    params_file : str
110
        path to the OGGM parameter file (to override defaults)
111
    is_test : bool
112
        to test on a couple of glaciers only!
113
    test_ids : list
114
        if is_test: list of ids to process
115
    demo : bool
116
        to run the prepro for the list of demo glaciers
117
    test_rgidf : shapefile
118
        for testing purposes only
119
    test_intersects_file : shapefile
120
        for testing purposes only
121
    test_topofile : str
122
        for testing purposes only
123
    test_crudir : str
124
        for testing purposes only
125
    disable_mp : bool
126
        disable multiprocessing
127
    elev_bands : bool
128
        compute all flowlines based on the Huss&Hock 2015 method instead
129
        of the OGGM default, which is a mix of elev_bands and centerlines.
130
    centerlines_only : bool
131
        compute all flowlines based on the OGGM centerline(s) method instead
132
        of the OGGM default, which is a mix of elev_bands and centerlines.
133
    match_regional_geodetic_mb : str
134
        match the regional mass balance estimates at the regional level
135
        ('hugonnet': Hugonnet et al., 2020 or 'zemp': Zemp et al., 2019).
136
    match_geodetic_mb_per_glacier : str
137
        match the mass balance estimates at the glacier level
138
        (currently only 'hugonnet': Hugonnet et al., 2020).
139
    evolution_model : str
140
        which geometry evolution model to use: `fl_sia` (default),
141
        or `massredis` (mass redistribution curve).
142
    add_consensus : bool
143
        adds (reprojects) the consensus estimates thickness to the glacier
144
        directories. With elev_bands=True, the data will also be binned.
145
    start_level : int
146
        the pre-processed level to start from (default is to start from
147
        scratch). If set, you'll need to indicate start_base_url as well.
148
    start_base_url : str
149
        the pre-processed base-url to fetch the data from.
150
    max_level : int
151
        the maximum pre-processing level before stopping
152
    logging_level : str
153
        the logging level to use (DEBUG, INFO, WARNING, WORKFLOW)
154
    override_params : dict
155
        a dict of parameters to override.
156
    disable_dl_verify : bool
157
        disable the hash verification of OGGM downloads
158
    dynamic_spinup : str
159
        include a dynamic spinup matching 'area/dmdtda' OR 'volume/dmdtda' at
160
        the RGI-date
161
    err_dmdtda_scaling_factor : float
162
        scaling factor to reduce individual geodetic mass balance uncertainty
163
    dynamic_spinup_start_year : int
164
        if dynamic_spinup is set, define the starting year for the simulation.
165
        The default is 1979, unless the climate data starts later.
166
    continue_on_error : bool
167
        if True the workflow continues if a task raises an error. For operational
168
        runs it should be set to True (the default).
169
    """
170

171
    # Input check
172
    if max_level not in [1, 2, 3, 4, 5]:
1✔
173
        raise InvalidParamsError('max_level should be one of [1, 2, 3, 4, 5]')
×
174

175
    if start_level is not None:
1✔
176
        if start_level not in [0, 1, 2, 3, 4]:
1✔
177
            raise InvalidParamsError('start_level should be one of [0, 1, 2, 3, 4]')
×
178
        if start_level > 0 and start_base_url is None:
1✔
179
            raise InvalidParamsError('With start_level, please also indicate '
1✔
180
                                     'start_base_url')
181
    else:
182
        start_level = 0
1✔
183

184
    if match_regional_geodetic_mb and match_geodetic_mb_per_glacier:
1✔
185
        raise InvalidParamsError('match_regional_geodetic_mb incompatible with '
×
186
                                 'match_geodetic_mb_per_glacier!')
187

188
    if match_geodetic_mb_per_glacier and match_geodetic_mb_per_glacier != 'hugonnet':
1✔
189
        raise InvalidParamsError('Currently only `hugonnet` is available for '
×
190
                                 'match_geodetic_mb_per_glacier.')
191

192
    if evolution_model not in ['fl_sia', 'massredis']:
1✔
193
        raise InvalidParamsError('evolution_model should be one of '
×
194
                                 "['fl_sia', 'massredis'].")
195

196
    if dynamic_spinup:
1✔
197
        if dynamic_spinup not in ['area/dmdtda', 'volume/dmdtda']:
1✔
198
            raise InvalidParamsError(f"Dynamic spinup option '{dynamic_spinup}' "
×
199
                                     "not supported")
200

201
        if evolution_model == 'massredis':
1✔
202
            raise InvalidParamsError("Dynamic spinup is not working/tested"
×
203
                                     "with massredis!")
204

205
    # Time
206
    start = time.time()
1✔
207

208
    def _time_log():
1✔
209
        # Log util
210
        m, s = divmod(time.time() - start, 60)
1✔
211
        h, m = divmod(m, 60)
1✔
212
        log.workflow('OGGM prepro_levels is done! Time needed: '
1✔
213
                     '{:02d}:{:02d}:{:02d}'.format(int(h), int(m), int(s)))
214

215
    # Local paths
216
    if override_params is None:
1✔
217
        override_params = {}
1✔
218

219
    utils.mkdir(working_dir)
1✔
220
    override_params['working_dir'] = working_dir
1✔
221

222
    # Initialize OGGM and set up the run parameters
223
    cfg.initialize(file=params_file, params=override_params,
1✔
224
                   logging_level=logging_level,
225
                   future=True)
226

227
    if match_geodetic_mb_per_glacier and (cfg.PARAMS['hydro_month_nh'] != 1 or
1✔
228
                                          cfg.PARAMS['hydro_month_sh'] != 1):
229
        raise InvalidParamsError('We recommend to set hydro_month_nh and sh '
×
230
                                 'to 1 for the geodetic MB calibration per '
231
                                 'glacier.')
232

233
    # Use multiprocessing?
234
    cfg.PARAMS['use_multiprocessing'] = not disable_mp
1✔
235

236
    # How many grid points around the glacier?
237
    # Make it large if you expect your glaciers to grow large
238
    cfg.PARAMS['border'] = border
1✔
239

240
    # Set to True for operational runs
241
    cfg.PARAMS['continue_on_error'] = continue_on_error
1✔
242

243
    # Check for the integrity of the files OGGM downloads at run time
244
    # For large files (e.g. using a 1 tif DEM like ALASKA) calculating the hash
245
    # takes a long time, so deactivating this can make sense
246
    cfg.PARAMS['dl_verify'] = not disable_dl_verify
1✔
247

248
    # Prepare the download of climate file to be shared across processes
249
    # TODO
250

251
    # Other things that make sense
252
    cfg.PARAMS['store_model_geometry'] = True
1✔
253

254
    # Log the parameters
255
    msg = '# OGGM Run parameters:'
1✔
256
    for k, v in cfg.PARAMS.items():
1✔
257
        if type(v) in [pd.DataFrame, dict]:
1✔
258
            continue
1✔
259
        msg += '\n    {}: {}'.format(k, v)
1✔
260
    log.workflow(msg)
1✔
261

262
    if rgi_version is None:
1✔
263
        rgi_version = cfg.PARAMS['rgi_version']
×
264
    output_base_dir = os.path.join(output_folder,
1✔
265
                                   'RGI{}'.format(rgi_version),
266
                                   'b_{:03d}'.format(border))
267

268
    # Add a package version file
269
    utils.mkdir(output_base_dir)
1✔
270
    opath = os.path.join(output_base_dir, 'package_versions.txt')
1✔
271
    with open(opath, 'w') as vfile:
1✔
272
        vfile.write(utils.show_versions(logger=log))
1✔
273

274
    if demo:
1✔
275
        rgidf = utils.get_rgi_glacier_entities(cfg.DATA['demo_glaciers'].index)
×
276
    elif test_rgidf is None:
1✔
277
        # Get the RGI file
278
        rgidf = gpd.read_file(utils.get_rgi_region_file(rgi_reg,
×
279
                                                        version=rgi_version))
280
        # We use intersects
281
        rgif = utils.get_rgi_intersects_region_file(rgi_reg,
×
282
                                                    version=rgi_version)
283
        cfg.set_intersects_db(rgif)
×
284

285
        # Some RGI input quality checks - this is based on visual checks
286
        # of large glaciers in the RGI
287
        ids_to_ice_cap = [
×
288
            'RGI60-05.10315',  # huge Greenland ice cap
289
            'RGI60-03.01466',  # strange thing next to Devon
290
            'RGI60-09.00918',  # Academy of sciences Ice cap
291
            'RGI60-09.00969',
292
            'RGI60-09.00958',
293
            'RGI60-09.00957',
294
        ]
295
        rgidf.loc[rgidf.RGIId.isin(ids_to_ice_cap), 'Form'] = '1'
×
296

297
        # In AA almost all large ice bodies are actually ice caps
298
        if rgi_reg == '19':
×
299
            rgidf.loc[rgidf.Area > 100, 'Form'] = '1'
×
300

301
        # For greenland we omit connectivity level 2
302
        if rgi_reg == '05':
×
303
            rgidf = rgidf.loc[rgidf['Connect'] != 2]
×
304
    else:
305
        rgidf = test_rgidf
1✔
306
        cfg.set_intersects_db(test_intersects_file)
1✔
307

308
    if is_test:
1✔
309
        if test_ids is not None:
1✔
310
            rgidf = rgidf.loc[rgidf.RGIId.isin(test_ids)]
1✔
311
        else:
312
            rgidf = rgidf.sample(4)
1✔
313

314
        if max_level > 2:
1✔
315
            # Also use ref tstars
316
            utils.apply_test_ref_tstars()
1✔
317

318
    if max_level > 2 and ref_tstars_base_url:
1✔
319
        workflow.download_ref_tstars(base_url=ref_tstars_base_url)
×
320

321
    log.workflow('Starting prepro run for RGI reg: {} '
1✔
322
                 'and border: {}'.format(rgi_reg, border))
323
    log.workflow('Number of glaciers: {}'.format(len(rgidf)))
1✔
324

325
    # L0 - go
326
    if start_level == 0:
1✔
327
        gdirs = workflow.init_glacier_directories(rgidf, reset=True, force=True)
1✔
328

329
        # Glacier stats
330
        sum_dir = os.path.join(output_base_dir, 'L0', 'summary')
1✔
331
        utils.mkdir(sum_dir)
1✔
332
        opath = os.path.join(sum_dir, 'glacier_statistics_{}.csv'.format(rgi_reg))
1✔
333
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
334

335
        # L0 OK - compress all in output directory
336
        log.workflow('L0 done. Writing to tar...')
1✔
337
        level_base_dir = os.path.join(output_base_dir, 'L0')
1✔
338
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
339
                                     base_dir=level_base_dir)
340
        utils.base_dir_to_tar(level_base_dir)
1✔
341
        if max_level == 0:
1✔
342
            _time_log()
×
343
            return
×
344
    else:
345
        gdirs = workflow.init_glacier_directories(rgidf, reset=True, force=True,
1✔
346
                                                  from_prepro_level=start_level,
347
                                                  prepro_border=border,
348
                                                  prepro_rgi_version=rgi_version,
349
                                                  prepro_base_url=start_base_url
350
                                                  )
351

352
    # L1 - Add dem files
353
    if start_level == 0:
1✔
354
        if test_topofile:
1✔
355
            cfg.PATHS['dem_file'] = test_topofile
1✔
356

357
        # Which DEM source?
358
        if dem_source.upper() == 'ALL':
1✔
359
            # This is the complex one, just do the job and leave
360
            log.workflow('Running prepro on ALL sources')
1✔
361
            for i, s in enumerate(utils.DEM_SOURCES):
1✔
362
                rs = i == 0
1✔
363
                log.workflow('Running prepro on sources: {}'.format(s))
1✔
364
                gdirs = workflow.init_glacier_directories(rgidf, reset=rs,
1✔
365
                                                          force=rs)
366
                workflow.execute_entity_task(tasks.define_glacier_region, gdirs,
1✔
367
                                             source=s)
368
                workflow.execute_entity_task(_rename_dem_folder, gdirs, source=s)
1✔
369

370
            # make a GeoTiff mask of the glacier, choose any source
371
            workflow.execute_entity_task(gis.rasterio_glacier_mask,
1✔
372
                                         gdirs, source='ALL')
373

374
            # Compress all in output directory
375
            level_base_dir = os.path.join(output_base_dir, 'L1')
1✔
376
            workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
377
                                         base_dir=level_base_dir)
378
            utils.base_dir_to_tar(level_base_dir)
1✔
379

380
            _time_log()
1✔
381
            return
1✔
382

383
        # Force a given source
384
        source = dem_source.upper() if dem_source else None
1✔
385

386
        # L1 - go
387
        workflow.execute_entity_task(tasks.define_glacier_region, gdirs,
1✔
388
                                     source=source)
389

390
        # Glacier stats
391
        sum_dir = os.path.join(output_base_dir, 'L1', 'summary')
1✔
392
        utils.mkdir(sum_dir)
1✔
393
        opath = os.path.join(sum_dir, 'glacier_statistics_{}.csv'.format(rgi_reg))
1✔
394
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
395

396
        # L1 OK - compress all in output directory
397
        log.workflow('L1 done. Writing to tar...')
1✔
398
        level_base_dir = os.path.join(output_base_dir, 'L1')
1✔
399
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
400
                                     base_dir=level_base_dir)
401
        utils.base_dir_to_tar(level_base_dir)
1✔
402
        if max_level == 1:
1✔
403
            _time_log()
×
404
            return
×
405

406
    # L2 - Tasks
407
    if start_level <= 1:
1✔
408
        # Check which glaciers will be processed as what
409
        if elev_bands:
1✔
410
            gdirs_band = gdirs
1✔
411
            gdirs_cent = []
1✔
412
        elif centerlines_only:
1✔
413
            gdirs_band = []
×
414
            gdirs_cent = gdirs
×
415
        else:
416
            # Default is to centerlines_only, but it used to be a mix
417
            # (e.g. bands for ice caps, etc)
418
            # I still keep this logic here in case we want to mix again
419
            gdirs_band = []
1✔
420
            gdirs_cent = gdirs
1✔
421

422
        log.workflow('Start flowline processing with: '
1✔
423
                     'N centerline type: {}, '
424
                     'N elev bands type: {}.'
425
                     ''.format(len(gdirs_cent), len(gdirs_band)))
426

427
        # HH2015 method
428
        workflow.execute_entity_task(tasks.simple_glacier_masks, gdirs_band)
1✔
429

430
        # Centerlines OGGM
431
        workflow.execute_entity_task(tasks.glacier_masks, gdirs_cent)
1✔
432

433
        if add_consensus:
1✔
434
            from oggm.shop.bedtopo import add_consensus_thickness
×
435
            workflow.execute_entity_task(add_consensus_thickness, gdirs_band)
×
436
            workflow.execute_entity_task(add_consensus_thickness, gdirs_cent)
×
437

438
            # Elev bands with var data
439
            vn = 'consensus_ice_thickness'
×
440
            workflow.execute_entity_task(tasks.elevation_band_flowline,
×
441
                                         gdirs_band, bin_variables=vn)
442
            workflow.execute_entity_task(tasks.fixed_dx_elevation_band_flowline,
×
443
                                         gdirs_band, bin_variables=vn)
444
        else:
445
            # HH2015 method without it
446
            task_list = [
1✔
447
                tasks.elevation_band_flowline,
448
                tasks.fixed_dx_elevation_band_flowline,
449
            ]
450
            for task in task_list:
1✔
451
                workflow.execute_entity_task(task, gdirs_band)
1✔
452

453
        # Centerlines OGGM
454
        task_list = [
1✔
455
            tasks.compute_centerlines,
456
            tasks.initialize_flowlines,
457
            tasks.catchment_area,
458
            tasks.catchment_intersections,
459
            tasks.catchment_width_geom,
460
            tasks.catchment_width_correction,
461
        ]
462
        for task in task_list:
1✔
463
            workflow.execute_entity_task(task, gdirs_cent)
1✔
464

465
        # Same for all glaciers
466
        if border >= 20:
1✔
467
            task_list = [
1✔
468
                tasks.compute_downstream_line,
469
                tasks.compute_downstream_bedshape,
470
            ]
471
            for task in task_list:
1✔
472
                workflow.execute_entity_task(task, gdirs)
1✔
473
        else:
474
            log.workflow('L2: for map border values < 20, wont compute '
×
475
                         'downstream lines.')
476

477
        # Glacier stats
478
        sum_dir = os.path.join(output_base_dir, 'L2', 'summary')
1✔
479
        utils.mkdir(sum_dir)
1✔
480
        opath = os.path.join(sum_dir, 'glacier_statistics_{}.csv'.format(rgi_reg))
1✔
481
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
482

483
        # And for level 2: shapes
484
        if len(gdirs_cent) > 0:
1✔
485
            opath = os.path.join(sum_dir, f'centerlines_{rgi_reg}.shp')
1✔
486
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
487
                                             path=opath)
488
            opath = os.path.join(sum_dir, f'centerlines_smoothed_{rgi_reg}.shp')
1✔
489
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
490
                                             ensure_exterior_match=True,
491
                                             simplify_line=0.5,
492
                                             corner_cutting=5,
493
                                             path=opath)
494
            opath = os.path.join(sum_dir, f'flowlines_{rgi_reg}.shp')
1✔
495
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
496
                                             flowlines_output=True,
497
                                             path=opath)
498
            opath = os.path.join(sum_dir, f'geom_widths_{rgi_reg}.shp')
1✔
499
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
500
                                             geometrical_widths_output=True,
501
                                             path=opath)
502
            opath = os.path.join(sum_dir, f'widths_{rgi_reg}.shp')
1✔
503
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
504
                                             corrected_widths_output=True,
505
                                             path=opath)
506

507
        # L2 OK - compress all in output directory
508
        log.workflow('L2 done. Writing to tar...')
1✔
509
        level_base_dir = os.path.join(output_base_dir, 'L2')
1✔
510
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
511
                                     base_dir=level_base_dir)
512
        utils.base_dir_to_tar(level_base_dir)
1✔
513
        if max_level == 2:
1✔
514
            _time_log()
×
515
            return
×
516

517
    # L3 - Tasks
518
    if start_level <= 2:
1✔
519
        sum_dir = os.path.join(output_base_dir, 'L3', 'summary')
1✔
520
        utils.mkdir(sum_dir)
1✔
521

522
        # Climate
523
        workflow.execute_entity_task(tasks.process_climate_data, gdirs)
1✔
524

525
        if cfg.PARAMS['climate_qc_months'] > 0:
1✔
526
            workflow.execute_entity_task(tasks.historical_climate_qc, gdirs)
×
527

528
        if match_geodetic_mb_per_glacier:
1✔
529
            utils.get_geodetic_mb_dataframe()  # Small optim to avoid concurrency
1✔
530
            workflow.execute_entity_task(tasks.mu_star_calibration_from_geodetic_mb, gdirs)
1✔
531
            workflow.execute_entity_task(tasks.apparent_mb_from_any_mb, gdirs)
1✔
532
        else:
533
            workflow.execute_entity_task(tasks.local_t_star, gdirs)
1✔
534
            workflow.execute_entity_task(tasks.mu_star_calibration, gdirs)
1✔
535

536
        # Inversion: we match the consensus
537
        filter = border >= 20
1✔
538
        workflow.calibrate_inversion_from_consensus(gdirs,
1✔
539
                                                    apply_fs_on_mismatch=True,
540
                                                    error_on_mismatch=False,
541
                                                    filter_inversion_output=filter)
542

543
        # Do we want to match geodetic estimates?
544
        # This affects only the bias so we can actually do this *after*
545
        # the inversion, but we really want to take calving into account here
546
        if match_regional_geodetic_mb:
1✔
547
            opath = os.path.join(sum_dir, 'fixed_geometry_mass_balance_'
1✔
548
                                          'before_match_{}.csv'.format(rgi_reg))
549
            utils.compile_fixed_geometry_mass_balance(gdirs, path=opath)
1✔
550
            workflow.match_regional_geodetic_mb(gdirs, rgi_reg=rgi_reg,
1✔
551
                                                dataset=match_regional_geodetic_mb)
552

553
        # We get ready for modelling
554
        if border >= 20:
1✔
555
            workflow.execute_entity_task(tasks.init_present_time_glacier, gdirs)
1✔
556
        else:
557
            log.workflow('L3: for map border values < 20, wont initialize glaciers '
×
558
                         'for the run.')
559
        # Glacier stats
560
        opath = os.path.join(sum_dir, 'glacier_statistics_{}.csv'.format(rgi_reg))
1✔
561
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
562
        opath = os.path.join(sum_dir, 'climate_statistics_{}.csv'.format(rgi_reg))
1✔
563
        utils.compile_climate_statistics(gdirs, path=opath)
1✔
564
        opath = os.path.join(sum_dir, 'fixed_geometry_mass_balance_{}.csv'.format(rgi_reg))
1✔
565
        utils.compile_fixed_geometry_mass_balance(gdirs, path=opath)
1✔
566

567
        # L3 OK - compress all in output directory
568
        log.workflow('L3 done. Writing to tar...')
1✔
569
        level_base_dir = os.path.join(output_base_dir, 'L3')
1✔
570
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
571
                                     base_dir=level_base_dir)
572
        utils.base_dir_to_tar(level_base_dir)
1✔
573
        if max_level == 3:
1✔
574
            _time_log()
×
575
            return
×
576
        if border < 20:
1✔
577
            log.workflow('L3: for map border values < 20, wont compute L4 and L5.')
×
578
            _time_log()
×
579
            return
×
580

581
        # is needed to copy some files for L4 and L5
582
        sum_dir_L3 = sum_dir
1✔
583

584
    # L4 - Tasks (add historical runs (old default) and dynamic spinup runs)
585
    if start_level <= 3:
1✔
586
        sum_dir = os.path.join(output_base_dir, 'L4', 'summary')
1✔
587
        utils.mkdir(sum_dir)
1✔
588

589
        # Copy L3 files for consistency
590
        for bn in ['glacier_statistics', 'climate_statistics',
1✔
591
                   'fixed_geometry_mass_balance']:
592
            if start_level <= 2:
1✔
593
                ipath = os.path.join(sum_dir_L3, bn + '_{}.csv'.format(rgi_reg))
1✔
594
            else:
595
                ipath = file_downloader(os.path.join(
×
596
                    get_prepro_base_url(base_url=start_base_url,
597
                                        rgi_version=rgi_version, border=border,
598
                                        prepro_level=start_level), 'summary',
599
                    bn + '_{}.csv'.format(rgi_reg)))
600

601
            opath = os.path.join(sum_dir, bn + '_{}.csv'.format(rgi_reg))
1✔
602
            shutil.copyfile(ipath, opath)
1✔
603

604
        # Get end date. The first gdir might have blown up, try some others
605
        i = 0
1✔
606
        while True:
1✔
607
            if i >= len(gdirs):
1✔
608
                raise RuntimeError('Found no valid glaciers!')
×
609
            try:
1✔
610
                y0 = gdirs[i].get_climate_info()['baseline_hydro_yr_0']
1✔
611
                # One adds 1 because the run ends at the end of the year
612
                ye = gdirs[i].get_climate_info()['baseline_hydro_yr_1'] + 1
1✔
613
                break
1✔
614
            except BaseException:
×
615
                i += 1
×
616

617
        # Which model?
618
        if evolution_model == 'massredis':
1✔
619
            from oggm.core.flowline import MassRedistributionCurveModel
1✔
620
            evolution_model = MassRedistributionCurveModel
1✔
621
        else:
622
            from oggm.core.flowline import FluxBasedModel
1✔
623
            evolution_model = FluxBasedModel
1✔
624

625
        # conduct historical run before dynamic mu calibration (for comparisons
626
        # to old default behavior)
627
        workflow.execute_entity_task(tasks.run_from_climate_data, gdirs,
1✔
628
                                     min_ys=y0, ye=ye,
629
                                     evolution_model=evolution_model,
630
                                     output_filesuffix='_historical')
631
        # Now compile the output
632
        opath = os.path.join(sum_dir, f'historical_run_output_{rgi_reg}.nc')
1✔
633
        utils.compile_run_output(gdirs, path=opath, input_filesuffix='_historical')
1✔
634

635
        # conduct dynamic spinup if wanted
636
        if dynamic_spinup:
1✔
637
            if y0 > dynamic_spinup_start_year:
1✔
638
                dynamic_spinup_start_year = y0
×
639

640
            minimise_for = dynamic_spinup.split('/')[0]
1✔
641

642
            used_max_mu_star = cfg.PARAMS['max_mu_star']
1✔
643
            if cfg.PARAMS['max_mu_star'] > 1000.:
1✔
644
                log.warning("For the dynamic calibration of mu_star the upper "
×
645
                            "limit is set to 1000! Current "
646
                            "cfg.PARAMS['max_mu_star'] = "
647
                            f"{cfg.PARAMS['max_mu_star']}.")
648
                used_max_mu_star = 1000.
×
649

650
            workflow.execute_entity_task(
1✔
651
                tasks.run_dynamic_mu_star_calibration, gdirs,
652
                err_dmdtda_scaling_factor=err_dmdtda_scaling_factor,
653
                ys=dynamic_spinup_start_year, ye=ye,
654
                max_mu_star=used_max_mu_star,
655
                kwargs_run_function={'evolution_model': evolution_model,
656
                                     'minimise_for': minimise_for},
657
                ignore_errors=True,
658
                kwargs_fallback_function={'evolution_model': evolution_model,
659
                                          'minimise_for': minimise_for},
660
                output_filesuffix='_spinup_historical',)
661
            # Now compile the output
662
            opath = os.path.join(sum_dir, f'spinup_historical_run_output_{rgi_reg}.nc')
1✔
663
            utils.compile_run_output(gdirs, path=opath,
1✔
664
                                     input_filesuffix='_spinup_historical')
665

666
        # Glacier statistics we recompute here for error analysis
667
        opath = os.path.join(sum_dir, 'glacier_statistics_{}.csv'.format(rgi_reg))
1✔
668
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
669

670
        # Add the extended files
671
        pf = os.path.join(sum_dir, 'historical_run_output_{}.nc'.format(rgi_reg))
1✔
672
        mf = os.path.join(sum_dir, 'fixed_geometry_mass_balance_{}.csv'.format(rgi_reg))
1✔
673
        # This is crucial - extending calving only possible with L3 data!!!
674
        if start_level < 3:
1✔
675
            sf = os.path.join(sum_dir_L3, 'glacier_statistics_{}.csv'.format(rgi_reg))
1✔
676
        else:
677
            sf = file_downloader(os.path.join(
×
678
                get_prepro_base_url(base_url=start_base_url,
679
                                    rgi_version=rgi_version, border=border,
680
                                    prepro_level=start_level), 'summary',
681
                'glacier_statistics_{}.csv'.format(rgi_reg)))
682
        opath = os.path.join(sum_dir, 'historical_run_output_extended_{}.nc'.format(rgi_reg))
1✔
683
        utils.extend_past_climate_run(past_run_file=pf,
1✔
684
                                      fixed_geometry_mb_file=mf,
685
                                      glacier_statistics_file=sf,
686
                                      path=opath)
687

688
        # L4 OK - compress all in output directory
689
        log.workflow('L4 done. Writing to tar...')
1✔
690
        level_base_dir = os.path.join(output_base_dir, 'L4')
1✔
691
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
692
                                     base_dir=level_base_dir)
693
        utils.base_dir_to_tar(level_base_dir)
1✔
694

695
        sum_dir_L4 = sum_dir
1✔
696

697
        if max_level == 4:
1✔
698
            _time_log()
×
699
            return
×
700

701
    # L5 - No tasks: make the dirs small
702
    sum_dir = os.path.join(output_base_dir, 'L5', 'summary')
1✔
703
    utils.mkdir(sum_dir)
1✔
704

705
    # Copy L4 files for consistency
706
    files_to_copy = ['glacier_statistics', 'climate_statistics',
1✔
707
                     'fixed_geometry_mass_balance', 'historical_run_output',
708
                     'historical_run_output_extended']
709
    files_suffixes = ['csv', 'csv', 'csv', 'nc', 'nc']
1✔
710
    if dynamic_spinup:
1✔
711
        files_to_copy.append('spinup_historical_run_output')
1✔
712
        files_suffixes.append('nc')
1✔
713
    for bn, suffix in zip(files_to_copy, files_suffixes):
1✔
714
        if start_level <= 3:
1✔
715
            ipath = os.path.join(sum_dir_L4, bn + f'_{rgi_reg}.{suffix}')
1✔
716
        else:
717
            ipath = file_downloader(os.path.join(
×
718
                get_prepro_base_url(base_url=start_base_url,
719
                                    rgi_version=rgi_version, border=border,
720
                                    prepro_level=start_level), 'summary',
721
                bn + f'_{rgi_reg}.{suffix}'))
722
        opath = os.path.join(sum_dir, bn + f'_{rgi_reg}.{suffix}')
1✔
723
        shutil.copyfile(ipath, opath)
1✔
724

725
    # Copy mini data to new dir
726
    mini_base_dir = os.path.join(working_dir, 'mini_perglacier',
1✔
727
                                 'RGI{}'.format(rgi_version),
728
                                 'b_{:03d}'.format(border))
729
    mini_gdirs = workflow.execute_entity_task(tasks.copy_to_basedir, gdirs,
1✔
730
                                              base_dir=mini_base_dir,
731
                                              setup='run/spinup')
732

733
    # L5 OK - compress all in output directory
734
    log.workflow('L5 done. Writing to tar...')
1✔
735
    level_base_dir = os.path.join(output_base_dir, 'L5')
1✔
736
    workflow.execute_entity_task(utils.gdir_to_tar, mini_gdirs, delete=False,
1✔
737
                                 base_dir=level_base_dir)
738
    utils.base_dir_to_tar(level_base_dir)
1✔
739

740
    _time_log()
1✔
741

742

743
def parse_args(args):
1✔
744
    """Check input arguments and env variables"""
745

746
    # CLI args
747
    description = ('Generate the preprocessed OGGM glacier directories for '
1✔
748
                   'this OGGM version.')
749
    parser = argparse.ArgumentParser(description=description)
1✔
750
    parser.add_argument('--map-border', type=int,
1✔
751
                        help='the size of the map border. Is required if '
752
                             '$OGGM_MAP_BORDER is not set.')
753
    parser.add_argument('--rgi-reg', type=str,
1✔
754
                        help='the rgi region to process. Is required if '
755
                             '$OGGM_RGI_REG is not set.')
756
    parser.add_argument('--rgi-version', type=str,
1✔
757
                        help='the RGI version to use. Defaults to the OGGM '
758
                             'default.')
759
    parser.add_argument('--start-level', type=int, default=0,
1✔
760
                        help='the pre-processed level to start from (default '
761
                             'is to start from 0). If set, you will need to '
762
                             'indicate --start-base-url as well.')
763
    parser.add_argument('--start-base-url', type=str,
1✔
764
                        help='the pre-processed base-url to fetch the data '
765
                             'from when starting from level > 0.')
766
    parser.add_argument('--max-level', type=int, default=5,
1✔
767
                        help='the maximum level you want to run the '
768
                             'pre-processing for (1, 2, 3, 4 or 5).')
769
    parser.add_argument('--working-dir', type=str,
1✔
770
                        help='path to the directory where to write the '
771
                             'output. Defaults to current directory or '
772
                             '$OGGM_WORKDIR.')
773
    parser.add_argument('--params-file', type=str,
1✔
774
                        help='path to the OGGM parameter file to use in place '
775
                             'of the default one.')
776
    parser.add_argument('--ref-tstars-base-url', type=str,
1✔
777
                        help='the url where to find the pre-calibrated '
778
                             'reference tstar list. Required as of v1.4.')
779
    parser.add_argument('--output', type=str,
1✔
780
                        help='path to the directory where to write the '
781
                             'output. Defaults to current directory or '
782
                             '$OGGM_OUTDIR.')
783
    parser.add_argument('--logging-level', type=str, default='WORKFLOW',
1✔
784
                        help='the logging level to use (DEBUG, INFO, WARNING, '
785
                             'WORKFLOW).')
786
    parser.add_argument('--elev-bands', nargs='?', const=True, default=False,
1✔
787
                        help='compute the flowlines based on the Huss&Hock '
788
                             '2015 method instead of the OGGM default, which is '
789
                             'a mix of elev_bands and centerlines.')
790
    parser.add_argument('--centerlines-only', nargs='?', const=True, default=False,
1✔
791
                        help='compute the flowlines based on the OGGM '
792
                             'centerline(s) method instead of the OGGM '
793
                             'default, which is a mix of elev_bands and '
794
                             'centerlines.')
795
    parser.add_argument('--match-regional-geodetic-mb', type=str, default='',
1✔
796
                        help='match regional SMB values to geodetic estimates '
797
                             '(currently hugonnet: Hugonnet et al., 2020, or '
798
                             'zemp: Zemp et al, 2019) '
799
                             'by shifting the SMB residual.')
800
    parser.add_argument('--match-geodetic-mb-per-glacier', type=str, default='',
1✔
801
                        help='match SMB values to geodetic estimates '
802
                             '(currently hugonnet: Hugonnet et al., '
803
                             '2020 only.')
804
    parser.add_argument('--evolution-model', type=str, default='fl_sia',
1✔
805
                        help='which geometry evolution model to use: '
806
                             '`fl_sia` (default), or `massredis` (mass '
807
                             'redistribution curve).')
808
    parser.add_argument('--dem-source', type=str, default='',
1✔
809
                        help='which DEM source to use. Possible options are '
810
                             'the name of a specific DEM (e.g. RAMP, SRTM...) '
811
                             'or ALL, in which case all available DEMs will '
812
                             'be processed and adjoined with a suffix at the '
813
                             'end of the file name. The ALL option is only '
814
                             'compatible with level 1 folders, after which '
815
                             'the processing will stop. The default is to use '
816
                             'the default OGGM DEM.')
817
    parser.add_argument('--add-consensus', nargs='?', const=True, default=False,
1✔
818
                        help='adds (reprojects) the consensus estimates '
819
                             'thickness to the glacier directories. '
820
                             'With --elev-bands, the data will also be '
821
                             'binned.')
822
    parser.add_argument('--demo', nargs='?', const=True, default=False,
1✔
823
                        help='if you want to run the prepro for the '
824
                             'list of demo glaciers.')
825
    parser.add_argument('--test', nargs='?', const=True, default=False,
1✔
826
                        help='if you want to do a test on a couple of '
827
                             'glaciers first.')
828
    parser.add_argument('--test-ids', nargs='+',
1✔
829
                        help='if --test, specify the RGI ids to run separated '
830
                             'by a space (default: 4 randomly selected).')
831
    parser.add_argument('--disable-dl-verify', nargs='?', const=True,
1✔
832
                        default=False,
833
                        help='if used OGGM downloads will not be verified '
834
                             'against a hash sum.')
835
    parser.add_argument('--disable-mp', nargs='?', const=True, default=False,
1✔
836
                        help='if you want to disable multiprocessing.')
837
    parser.add_argument('--dynamic-spinup', type=str, default='',
1✔
838
                        help="include a dynamic spinup for matching glacier area "
839
                             "('area/dmdtda') OR volume ('volume/dmdtda') at "
840
                             "the RGI-date, AND mass-change from Hugonnet "
841
                             "in the period 2000-2019 (dynamic mu* "
842
                             "calibration).")
843
    parser.add_argument('--err-dmdtda-scaling-factor', type=float, default=1,
1✔
844
                        help="scaling factor to account for correlated "
845
                             "uncertainties of geodetic mass balance "
846
                             "observations when looking at regional scale. "
847
                             "Should be smaller or equal 1.")
848
    parser.add_argument('--dynamic-spinup-start-year', type=int, default=1979,
1✔
849
                        help="if --dynamic-spinup is set, define the starting"
850
                             "year for the simulation. The default is 1979, "
851
                             "unless the climate data starts later.")
852

853
    args = parser.parse_args(args)
1✔
854

855
    # Check input
856
    rgi_reg = args.rgi_reg
1✔
857
    if args.demo:
1✔
858
        rgi_reg = 0
1✔
859
    if not rgi_reg and not args.demo:
1✔
860
        rgi_reg = os.environ.get('OGGM_RGI_REG', None)
1✔
861
        if rgi_reg is None:
1✔
862
            raise InvalidParamsError('--rgi-reg is required!')
1✔
863
    rgi_reg = '{:02}'.format(int(rgi_reg))
1✔
864
    ok_regs = ['{:02}'.format(int(r)) for r in range(1, 20)]
1✔
865
    if not args.demo and rgi_reg not in ok_regs:
1✔
866
        raise InvalidParamsError('--rgi-reg should range from 01 to 19!')
×
867

868
    rgi_version = args.rgi_version
1✔
869

870
    border = args.map_border
1✔
871
    if not border:
1✔
872
        border = os.environ.get('OGGM_MAP_BORDER', None)
1✔
873
        if border is None:
1✔
874
            raise InvalidParamsError('--map-border is required!')
1✔
875

876
    working_dir = args.working_dir
1✔
877
    if not working_dir:
1✔
878
        working_dir = os.environ.get('OGGM_WORKDIR', '')
1✔
879

880
    output_folder = args.output
1✔
881
    if not output_folder:
1✔
882
        output_folder = os.environ.get('OGGM_OUTDIR', '')
1✔
883

884
    border = int(border)
1✔
885
    output_folder = os.path.abspath(output_folder)
1✔
886
    working_dir = os.path.abspath(working_dir)
1✔
887

888
    dynamic_spinup = False if args.dynamic_spinup == '' else args.dynamic_spinup
1✔
889

890
    # All good
891
    return dict(rgi_version=rgi_version, rgi_reg=rgi_reg,
1✔
892
                border=border, output_folder=output_folder,
893
                working_dir=working_dir, params_file=args.params_file,
894
                is_test=args.test, test_ids=args.test_ids,
895
                demo=args.demo, dem_source=args.dem_source,
896
                start_level=args.start_level, start_base_url=args.start_base_url,
897
                max_level=args.max_level, disable_mp=args.disable_mp,
898
                logging_level=args.logging_level, elev_bands=args.elev_bands,
899
                centerlines_only=args.centerlines_only,
900
                match_regional_geodetic_mb=args.match_regional_geodetic_mb,
901
                match_geodetic_mb_per_glacier=args.match_geodetic_mb_per_glacier,
902
                add_consensus=args.add_consensus,
903
                disable_dl_verify=args.disable_dl_verify,
904
                ref_tstars_base_url=args.ref_tstars_base_url,
905
                evolution_model=args.evolution_model,
906
                dynamic_spinup=dynamic_spinup,
907
                err_dmdtda_scaling_factor=args.err_dmdtda_scaling_factor,
908
                dynamic_spinup_start_year=args.dynamic_spinup_start_year,
909
                )
910

911

912
def main():
1✔
913
    """Script entry point"""
914

915
    run_prepro_levels(**parse_args(sys.argv[1:]))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc