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

OGGM / oggm / 30524401593

30 Jul 2026 07:52AM UTC coverage: 84.749%. First build
30524401593

Pull #1908

github

web-flow
Merge e0ce02050 into 180c7c28f
Pull Request #1908: feat: read and write zarr methods to replace read_pickle

4440 of 6148 branches covered (72.22%)

637 of 745 new or added lines in 13 files covered. (85.5%)

14537 of 17153 relevant lines covered (84.75%)

4.16 hits per line

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

69.47
/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
# Standard libraries
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 json
1✔
15
import importlib
1✔
16
from pathlib import Path
1✔
17

18
# External modules
19
import pandas as pd
1✔
20
import numpy as np
1✔
21
import geopandas as gpd
1✔
22

23
# Locals
24
import oggm.cfg as cfg
1✔
25
from oggm import utils, workflow, tasks, GlacierDirectory
1✔
26
from oggm.core import gis
1✔
27
from oggm.core.massbalance import MonthlyTIModel, SfcTypeTIModel
1✔
28
from oggm.exceptions import InvalidParamsError, InvalidDEMError, InvalidWorkflowError
1✔
29

30
# Module logger
31
from oggm.utils import get_prepro_base_url, file_downloader
1✔
32

33
log = logging.getLogger(__name__)
1✔
34

35

36
@utils.entity_task(log)
1✔
37
def _rename_dem_folder(gdir, source=''):
1✔
38
    """Put the DEM files in a subfolder of the gdir.
39

40
    Parameters
41
    ----------
42
    gdir : GlacierDirectory
43
    source : str
44
        the DEM source
45
    """
46

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

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

70
    # Create a source dir and move the files
71
    out = os.path.join(gdir.dir, source)
×
72
    utils.mkdir(out)
×
73
    for fname in ['dem', 'dem_source']:
×
74
        f = gdir.get_filepath(fname)
×
75
        os.rename(f, os.path.join(out, os.path.basename(f)))
×
76

77
    # log SUCCESS for this DEM source
78
    gdir.log('{},DEM SOURCE,{}'.format(gdir.rgi_id, source))
×
79

80

81
@utils.entity_task(log)
1✔
82
def _move_hypsometry_to_dem_folder(gdir, source=''):
1✔
83
    """Move the hypsometry file to the DEM source folder if it exists.
84

85
    Parameters
86
    ----------
87
    gdir : GlacierDirectory
88
    source : str
89
        the DEM source
90
    """
91

92
    hypso_f = gdir.get_filepath('hypsometry')
×
93
    if not os.path.exists(hypso_f):
×
94
        return
×
95

96
    out = os.path.join(gdir.dir, source)
×
97
    if not os.path.exists(out):
×
98
        raise InvalidWorkflowError('We should not be there')
×
99
    os.rename(hypso_f, os.path.join(out, os.path.basename(hypso_f)))
×
100

101

102
def run_prepro_levels(rgi_version=None, rgi_reg=None, border=None,
1✔
103
                      output_folder='', working_dir='', dem_source='',
104
                      is_test=False, test_ids=None, rgi_file=None,
105
                      intersects_file=None, test_topofile=None,
106
                      disable_mp=False, params_file=None,
107
                      elev_bands=False, centerlines=False,
108
                      override_params=None, skip_inversion=False,
109
                      inversion_volume_dataset='iceboost',
110
                      mb_model_class='MonthlyTIModel',
111
                      mb_calibration_strategy='informed_threestep',
112
                      geodetic_mb_file_path=None,
113
                      temp_bias_file_path=None,
114
                      select_source_from_dir=None, keep_dem_folders=False,
115
                      add_consensus_thickness=False, add_itslive_velocity=False,
116
                      add_millan_thickness=False, add_millan_velocity=False,
117
                      add_hugonnet_dhdt=False, add_bedmachine=False,
118
                      add_glathida=False, add_distributed_thickness=False,
119
                      add_export_thickness_geotiff=False, compute_hypsometry=False,
120
                      custom_climate_task=None,
121
                      custom_climate_task_kwargs=None,
122
                      start_level=None, start_base_url=None, max_level=5,
123
                      logging_level='WORKFLOW',
124
                      dynamic_spinup=False, ref_mb_err_scaling_factor=0.2,
125
                      dynamic_spinup_start_year=1979,
126
                      dynamic_spinup_periods_to_try=None,
127
                      continue_on_error=True, store_fl_diagnostics=False,
128
                      store_hydro_output=False, store_monthly_hydro=True,
129
                      ref_area_yr=None):
130
    """Generate the preprocessed OGGM glacier directories for this OGGM version
131

132
    Parameters
133
    ----------
134
    rgi_version : str
135
        the RGI version to use (defaults to cfg.PARAMS)
136
    rgi_reg : str
137
        the RGI region to process
138
    border : int
139
        the number of pixels at the maps border
140
    output_folder : str
141
        path to the output folder (where to put the preprocessed tar files)
142
    dem_source : str
143
        which DEM source to use: default, SOURCE_NAME, STANDARD or ALL
144
        ALL is to generate RGITOPO
145
        "STANDARD" is doina small RGITOPO using COPDEM + NASADEM
146
        default is the current default lookup tables found at
147
        https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.6/rgitopo/2025.4/
148
    working_dir : str
149
        path to the OGGM working directory
150
    params_file : str
151
        path to the OGGM parameter file (to override defaults)
152
    is_test : bool
153
        to test on a couple of glaciers only!
154
    test_ids : list
155
        if is_test: list of ids to process
156
    rgi_file : str or geopandas.GeoDataFrame, optional
157
        path to an RGI shapefile or a GeoDataFrame to use instead of
158
        the default RGI region file. Useful to override the default RGI
159
        files for custom runs as well as for testing.
160
    intersects_file : str or geopandas.GeoDataFrame, optional
161
        path to an intersects shapefile or a GeoDataFrame to use instead of
162
        the default RGI intersects file. Can also be None to skip setting
163
        the intersects database.
164
    test_topofile : str
165
        for testing purposes only
166
    test_crudir : str
167
        for testing purposes only
168
    disable_mp : bool
169
        disable multiprocessing
170
    elev_bands : bool
171
        compute all flowlines based on the Huss & Farinotti 2012 method.
172
    centerlines : bool
173
        compute all flowlines based on the OGGM centerline(s) method.
174
    mb_model_class : str
175
        The mb_model_class to use. Options are 'MonthlyTIModel' (default) and
176
        'SfcTypeTIModel'.
177
    mb_calibration_strategy : str
178
        how to calibrate the massbalance. Currently one of:
179
        - 'informed_threestep' (default)
180
        - 'melt_temp'
181
        - 'temp_melt'
182
        Add the `_regional` suffix to use regional values instead,
183
        for example `informed_threestep_regional`
184
    geodetic_mb_file_path : str
185
        optional path or URL to a custom geodetic MB file, passed to
186
        utils.get_geodetic_mb_dataframe and
187
        tasks.mb_calibration_from_geodetic_mb.
188
    temp_bias_file_path : str
189
        optional path or URL to a custom temperature-bias file, passed to
190
        tasks.mb_calibration_from_geodetic_mb (only used with the
191
        'informed_threestep' calibration strategy). Use this together with a
192
        `custom_climate_task` to calibrate on an arbitrary climate dataset.
193
        The file must follow the same format as the default temp-bias files.
194
    select_source_from_dir : str
195
        if starting from a level 1 "ALL" or "STANDARD" DEM sources directory,
196
        select the chosen DEM source here. If you set it to "BY_RES" here,
197
        COPDEM will be used and its resolution chosen based on the gdir's
198
        map resolution (COPDEM30 for dx < 60 m, COPDEM90 elsewhere).
199
    keep_dem_folders : bool
200
        if `select_source_from_dir` is used, wether to keep the original
201
        DEM folders in or not.
202
    add_consensus_thickness : bool
203
        adds (reprojects) the consensus estimates thickness to the glacier
204
        directories. With elev_bands=True, the data will also be binned.
205
    add_itslive_velocity : bool
206
        adds (reprojects) the ITS_LIVE velocity to the glacier
207
        directories. With elev_bands=True, the data will also be binned.
208
    add_millan_thickness : bool
209
        adds (reprojects) the millan thickness to the glacier
210
        directories. With elev_bands=True, the data will also be binned.
211
    add_millan_velocity : bool
212
        adds (reprojects) the millan velocity to the glacier
213
        directories. With elev_bands=True, the data will also be binned.
214
    add_hugonnet_dhdt : bool
215
        adds (reprojects) the hugonnet dhdt maps to the glacier
216
        directories. With elev_bands=True, the data will also be binned.
217
    add_bedmachine : bool
218
        adds (reprojects) the bedmachine ice thickness maps to the glacier
219
        directories. With elev_bands=True, the data will also be binned.
220
    add_glathida : bool
221
        adds (reprojects) the glathida thickness data to the glacier
222
        directories. Data points are stored as csv files.
223
    add_distributed_thickness : bool
224
        adds a thickness field to gridded_data using
225
        distribute_thickness_per_altitude.
226
    add_export_thickness_geotiff : bool
227
        exports the distributed thickness field to GeoTIFF files in a
228
        subfolder of the L3 summary directory.
229
    compute_hypsometry : bool
230
        Compute the hypsometry tables for all glaciers,
231
        added to the glacier directory and compiled in
232
        the summary folder.
233
    custom_climate_task : str
234
        optional import path to a custom climate task in the form
235
        "module_path:function_name". If provided, it will be called instead of
236
        the default process_climate_data.
237
    custom_climate_task_kwargs : dict
238
        optional kwargs passed to the custom climate task when it is executed.
239
    start_level : int
240
        the pre-processed level to start from (default is to start from
241
        scratch). If set, you'll need to indicate start_base_url as well.
242
    start_base_url : str
243
        the pre-processed base-url to fetch the data from.
244
    max_level : int
245
        the maximum pre-processing level before stopping
246
    skip_inversion : bool
247
         do not run the inversion (level 3 files). This is a temporary
248
         workaround for workflows that wont run that far into level 3.
249
    inversion_volume_dataset : str
250
        which reference volume dataset to calibrate the ice thickness
251
        inversion (Glen A) against. One of:
252
        - 'iceboost' (default): the IceBoost v2 product, auto-selected by RGI
253
          version. Supported for RGI62, RGI70G and RGI70C.
254
        - 'consensus': the Farinotti et al. (2019) consensus (ITMIX) estimate.
255
          Only supported for RGI62.
256
    logging_level : str
257
        the logging level to use (DEBUG, INFO, WARNING, WORKFLOW)
258
    override_params : dict
259
        a dict of parameters to override.
260
    dynamic_spinup : str
261
        include a dynamic spinup matching 'area/dmdtda' OR 'volume/dmdtda' at
262
        the RGI-date
263
    ref_mb_err_scaling_factor : float
264
        scaling factor to reduce individual geodetic mass balance uncertainty
265
    dynamic_spinup_start_year : int
266
        if dynamic_spinup is set, define the starting year for the simulation.
267
        The default is 1979, unless the climate data starts later.
268
    dynamic_spinup_periods_to_try : list or None
269
        If the spinup_period defined by rgi_date - dynamic_spinup_start_yr was
270
        not successful, you can provide here a list of spinup periods which
271
        should be tried in order.
272
        Default is None
273
    continue_on_error : bool
274
        if True the workflow continues if a task raises an error. For operational
275
        runs it should be set to True (the default).
276
    store_fl_diagnostics : bool
277
        if True, also compute and store flowline diagnostics during preprocessing.
278
        This can increase data usage quite a bit.
279
    store_hydro_output : bool
280
        if True, also store the hydrological model output.
281
    store_monthly_hydro : bool
282
        if True and store_hydro_output is True the hydrological mode output will
283
        also be stored in a monthly resolution (see flowline.run_with_hydro)
284
    ref_area_yr : int
285
        the hydrological output is computed over a reference area, which
286
        per default is the largest area covered by the glacier in the simulation
287
        period. Use this kwarg to force a specific area to the state of the
288
        glacier at the provided simulation year.
289
    """
290

291
    # Input check
292
    if max_level not in [1, 2, 3, 4, 5]:
1!
293
        raise InvalidParamsError('max_level should be one of [1, 2, 3, 4, 5]')
×
294

295
    if start_level is not None:
1✔
296
        if start_level not in [0, 1, 2, 3, 4]:
1!
297
            raise InvalidParamsError('start_level should be one of [0, 1, 2, 3, 4]')
×
298
        if start_level > 0 and start_base_url is None:
1✔
299
            raise InvalidParamsError('With start_level, please also indicate '
1✔
300
                                     'start_base_url')
301
    else:
302
        start_level = 0
1✔
303

304
    if dynamic_spinup:
1✔
305
        if dynamic_spinup not in ['area/dmdtda', 'volume/dmdtda']:
1!
306
            raise InvalidParamsError(f"Dynamic spinup option '{dynamic_spinup}' "
×
307
                                     "not supported")
308

309
    # Time
310
    start = time.time()
1✔
311

312
    def _time_log():
1✔
313
        # Log util
314
        m, s = divmod(time.time() - start, 60)
1✔
315
        h, m = divmod(m, 60)
1✔
316
        log.workflow('OGGM prepro_levels is done! Time needed: '
1✔
317
                     '{:02d}:{:02d}:{:02d}'.format(int(h), int(m), int(s)))
318

319
    # Local paths
320
    if override_params is None:
1✔
321
        override_params = {}
1✔
322

323
    # Use multiprocessing?
324
    override_params['use_multiprocessing'] = not disable_mp
1✔
325

326
    # How many grid points around the glacier?
327
    # Make it large if you expect your glaciers to grow large
328
    override_params['border'] = border
1✔
329

330
    # Some arbitrary heuristics on the length of tidewater extension
331
    extension = int(utils.clip_min(border / 2, 30))
1✔
332
    override_params['calving_line_extension'] = extension
1✔
333

334
    # Set to True for operational runs
335
    override_params['continue_on_error'] = continue_on_error
1✔
336

337
    # For centerlines we have to change the default evolution model and bed
338
    if centerlines:
1✔
339
        override_params['downstream_line_shape'] = 'parabola'
1✔
340
        override_params['evolution_model'] = 'FluxBased'
1✔
341

342
    # define the default melt_f depending on the the used mb_model_class
343
    if mb_model_class == 'MonthlyTIModel':
1✔
344
        override_params['melt_f'] = 5.
1✔
345
        mb_model_class = MonthlyTIModel
1✔
346
        store_mb_diagnostics = False
1✔
347
    elif mb_model_class == 'SfcTypeTIModel':
1✔
348
        # TODO: According to Schuster et al. (2023) Figure 1, the default melt_f
349
        # should be larger when including snow tacking (around 6. to 7.). If we
350
        # change this we also need to include this for the preparation of the
351
        # three step calibration. Currently I stick to the same value as the
352
        # MonthlyTIModel.
353
        override_params['melt_f'] = 5.
1✔
354
        mb_model_class = SfcTypeTIModel
1✔
355
        store_mb_diagnostics = True
1✔
356
    else:
357
        raise NotImplementedError(f"Unknown mb_model: {mb_model_class}")
358

359
    # Other things that make sense
360
    override_params['store_model_geometry'] = True
1✔
361
    override_params['store_fl_diagnostics'] = store_fl_diagnostics
1✔
362

363
    utils.mkdir(working_dir)
1✔
364
    override_params['working_dir'] = working_dir
1✔
365

366
    # Initialize OGGM and set up the run parameters
367
    cfg.initialize(file=params_file, params=override_params,
1✔
368
                   logging_level=logging_level)
369

370
    # Prepare the download of climate file to be shared across processes
371
    # TODO
372

373
    # Log the parameters
374
    msg = '# OGGM Run parameters:'
1✔
375
    for k, v in cfg.PARAMS.items():
1✔
376
        if type(v) in [pd.DataFrame, dict]:
1✔
377
            continue
1✔
378
        msg += '\n    {}: {}'.format(k, v)
1✔
379
    log.workflow(msg)
1✔
380

381
    if rgi_version is None:
1!
382
        rgi_version = cfg.PARAMS['rgi_version']
×
383
    output_base_dir = Path(output_folder) / f'RGI{rgi_version}' / f'b_{border:03d}'
1✔
384

385
    # Add a package version file
386
    utils.mkdir(output_base_dir)
1✔
387
    opath = output_base_dir / 'package_versions.txt'
1✔
388
    with open(opath, 'w') as vfile:
1✔
389
        vfile.write(utils.show_versions(logger=log))
1✔
390

391
    if rgi_file is None:
1!
392

393
        # Get the RGI file
394
        rgidf = gpd.read_file(utils.get_rgi_region_file(rgi_reg,
×
395
                                                        version=rgi_version))
396
        # We use intersects
397
        if rgi_version != '70C':
×
398
            if intersects_file is None:
×
399
                rgif = utils.get_rgi_intersects_region_file(rgi_reg,
×
400
                                                            version=rgi_version)
401
            else:
402
                rgif = intersects_file
×
403
            cfg.set_intersects_db(rgif)
×
404

405
        if rgi_version == '62':
×
406
            # Some RGI input quality checks - this is based on visual checks
407
            # of large glaciers in the RGI
408
            ids_to_ice_cap = [
×
409
                'RGI60-05.10315',  # huge Greenland ice cap
410
                'RGI60-03.01466',  # strange thing next to Devon
411
                'RGI60-09.00918',  # Academy of sciences Ice cap
412
                'RGI60-09.00969',
413
                'RGI60-09.00958',
414
                'RGI60-09.00957',
415
            ]
416
            rgidf.loc[rgidf.RGIId.isin(ids_to_ice_cap), 'Form'] = 1
×
417

418
            # In AA almost all large ice bodies are actually ice caps
419
            if rgi_reg == '19':
×
420
                rgidf.loc[rgidf.Area > 100, 'Form'] = 1
×
421

422
            # For greenland we omit connectivity level 2
423
            if rgi_reg == '05':
×
424
                rgidf = rgidf.loc[rgidf['Connect'] != 2]
×
425
    else:
426
        if isinstance(rgi_file, str):
1!
427
            rgidf = gpd.read_file(rgi_file)
×
428
        else:
429
            rgidf = rgi_file
1✔
430
        cfg.set_intersects_db(intersects_file)
1✔
431

432
    if is_test:
1!
433
        if test_ids is not None:
1✔
434
            try:
1✔
435
                rgidf = rgidf.loc[rgidf.RGIId.isin(test_ids)]
1✔
436
            except AttributeError:
×
437
                # RGI7
438
                rgidf = rgidf.loc[rgidf.rgi_id.isin(test_ids)]
×
439
        else:
440
            rgidf = rgidf.sample(4)
1✔
441

442
    if len(rgidf) == 0:
1!
443
        raise InvalidParamsError('Zero glaciers selected!')
×
444

445
    log.workflow('Starting prepro run for RGI reg: {} '
1✔
446
                 'and border: {}'.format(rgi_reg, border))
447
    log.workflow('Number of glaciers: {}'.format(len(rgidf)))
1✔
448

449
    # Try to avoid concurrency
450
    if rgi_version == '70C':
1!
NEW
451
        from oggm.utils._downloads import get_lock
×
NEW
452
        with get_lock():
×
NEW
453
            fp = file_downloader('https://cluster.klima.uni-bremen.de/~oggm/'
×
454
                                'ref_mb_params/oggm_v1.6/inv_rgi7/'
455
                                'rgi7c_rgi_year_2025.1.csv')
NEW
456
            rgi_year_by_id = pd.read_csv(fp, index_col=0)['rgi_year'].astype(int).astype(str)
×
NEW
457
            rgidf['src_date'] = rgidf['rgi_id'].map(rgi_year_by_id) + '-01-01 00:00:00'
×
458

459
    # Add a new default source
460
    if not dem_source:
1✔
461
        fs_url = 'https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.6/rgitopo/2025.4/'
1✔
462
        if rgi_version == '62':
1!
NEW
463
            with get_lock():
×
NEW
464
                fs = utils.file_downloader(fs_url + 'chosen_dem_RGI62_20251029.csv')
×
NEW
465
                dfs = pd.read_csv(fs, index_col=0)
×
NEW
466
                rgidf['dem_source'] = dfs.loc[rgidf['RGIId'], 'dem_source'].values
×
467
        if rgi_version == '70G':
1!
NEW
468
            with get_lock():
×
NEW
469
                fs = utils.file_downloader(fs_url + 'chosen_dem_RGI70G_20251029.csv')
×
NEW
470
                dfs = pd.read_csv(fs, index_col=0)
×
NEW
471
                rgidf['dem_source'] = dfs.loc[rgidf['rgi_id'], 'dem_source'].values
×
472
        if rgi_version == '70C':
1!
NEW
473
            with get_lock():
×
NEW
474
                fs = utils.file_downloader(fs_url + 'chosen_dem_RGI70C_20251029.csv')
×
NEW
475
                dfs = pd.read_csv(fs, index_col=0)
×
NEW
476
                rgidf['dem_sourc`e'] = dfs.loc[rgidf['rgi_id'], 'dem_source'].values
×
477

478
    # L0 - go
479
    if start_level == 0:
1✔
480
        gdirs = workflow.init_glacier_directories(rgidf, reset=True, force=True)
1✔
481

482
        # Glacier stats
483
        sum_dir = Path(output_base_dir) / 'L0' / 'summary'
1✔
484
        utils.mkdir(sum_dir)
1✔
485
        opath = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
486
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
487

488
        # L0 OK - compress all in output directory
489
        log.workflow('L0 done. Writing to tar...')
1✔
490
        level_base_dir = Path(output_base_dir) / 'L0'
1✔
491
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
492
                                     base_dir=level_base_dir)
493
        utils.base_dir_to_tar(level_base_dir)
1✔
494
        if max_level == 0:
1!
495
            _time_log()
×
496
            return
×
497
    else:
498
        gdirs = workflow.init_glacier_directories(rgidf, reset=True, force=True,
1✔
499
                                                  from_prepro_level=start_level,
500
                                                  prepro_border=border,
501
                                                  prepro_rgi_version=rgi_version,
502
                                                  prepro_base_url=start_base_url
503
                                                  )
504

505
    # L1 - Add dem files
506
    if start_level == 0:
1✔
507
        if test_topofile:
1!
508
            cfg.PATHS['dem_file'] = test_topofile
1✔
509

510
        # Which DEM source?
511
        if dem_source.upper() in ['ALL', 'STANDARD']:
1✔
512
            # This is the complex one, just do the job and leave
513

514
            if dem_source.upper() == 'ALL':
1!
515
                sources = utils.DEM_SOURCES
1✔
516
            if dem_source.upper() == 'STANDARD':
1!
517
                sources = ['COPDEM30', 'COPDEM90', 'NASADEM']
×
518

519
            log.workflow('Running prepro on several sources')
1✔
520
            for i, s in enumerate(sources):
1✔
521
                rs = i == 0
1✔
522
                log.workflow('Running prepro on sources: {}'.format(s))
1✔
523
                gdirs = workflow.init_glacier_directories(rgidf, reset=rs,
1✔
524
                                                          force=rs)
525
                workflow.execute_entity_task(tasks.define_glacier_region, gdirs,
1✔
526
                                             source=s)
527
                workflow.execute_entity_task(_rename_dem_folder, gdirs, source=s)
1✔
528

529
            # make a GeoTiff mask of the glacier, choose any source
530
            workflow.execute_entity_task(gis.rasterio_glacier_mask,
1✔
531
                                         gdirs, source='ALL')
532

533
            # Glacier stats
534
            sum_dir = Path(output_base_dir) / 'L1' / 'summary'
1✔
535
            utils.mkdir(sum_dir)
1✔
536
            opath = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
537
            utils.compile_glacier_statistics(gdirs, path=opath)
1✔
538

539
            # Add hypsometry files
540
            if compute_hypsometry:
1!
541
                for dem_source in utils.DEM_SOURCES:
×
542
                    from oggm.shop.rgitopo import select_dem_from_dir
×
543
                    workflow.execute_entity_task(select_dem_from_dir, gdirs,
×
544
                                                 dem_source=dem_source,
545
                                                 keep_dem_folders=True)
546
                    workflow.execute_entity_task(tasks.rasterio_glacier_mask, gdirs,
×
547
                                                 no_nunataks=True,
548
                                                 overwrite=False)
549
                    workflow.execute_entity_task(tasks.rasterio_glacier_exterior_mask,
×
550
                                                 gdirs,
551
                                                 overwrite=False)
552
                    workflow.execute_entity_task(tasks.compute_hypsometry_attributes, gdirs)
×
553
                    opath = sum_dir / f'hypsometry_{rgi_reg}_{dem_source}.csv'
×
554
                    utils.compile_glacier_hypsometry(gdirs, path=opath,
×
555
                                                     add_column=('dem_source', dem_source))
556
                    workflow.execute_entity_task(_move_hypsometry_to_dem_folder,
×
557
                                                 gdirs, source=dem_source)
558

559
            # L1 OK - compress all in output directory
560
            log.workflow('L1 done. Writing to tar...')
1✔
561
            level_base_dir = Path(output_base_dir) / 'L1'
1✔
562
            workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
563
                                         base_dir=level_base_dir)
564
            utils.base_dir_to_tar(level_base_dir)
1✔
565

566
            _time_log()
1✔
567
            return
1✔
568

569
        # Force a given source
570
        source = dem_source.upper() if dem_source else None
1✔
571

572
        # L1 - go
573
        workflow.execute_entity_task(tasks.define_glacier_region, gdirs,
1✔
574
                                     source=source)
575

576
        # Summaries
577
        sum_dir = Path(output_base_dir) / 'L1' / 'summary'
1✔
578
        utils.mkdir(sum_dir)
1✔
579

580
        # Add hypsometry files
581
        if compute_hypsometry:
1!
582
            workflow.execute_entity_task(tasks.rasterio_glacier_mask, gdirs)
×
583
            workflow.execute_entity_task(tasks.rasterio_glacier_mask, gdirs,
×
584
                                         no_nunataks=True)
585
            workflow.execute_entity_task(tasks.rasterio_glacier_exterior_mask, gdirs)
×
586
            workflow.execute_entity_task(tasks.compute_hypsometry_attributes, gdirs)
×
587
            opath = sum_dir / f'hypsometry_{rgi_reg}.csv'
×
588
            utils.compile_glacier_hypsometry(gdirs, path=opath)
×
589

590
        # Glacier stats
591
        opath = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
592
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
593

594
        # L1 OK - compress all in output directory
595
        log.workflow('L1 done. Writing to tar...')
1✔
596
        level_base_dir = Path(output_base_dir) / 'L1'
1✔
597
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
598
                                     base_dir=level_base_dir)
599
        utils.base_dir_to_tar(level_base_dir)
1✔
600
        if max_level == 1:
1!
601
            _time_log()
×
602
            return
×
603

604
    # L2 - Tasks
605
    if start_level <= 1:
1!
606
        # Check which glaciers will be processed as what
607
        if elev_bands:
1✔
608
            gdirs_band = gdirs
1✔
609
            gdirs_cent = []
1✔
610
        elif centerlines:
1!
611
            gdirs_band = []
1✔
612
            gdirs_cent = gdirs
1✔
613
        else:
614
            raise InvalidParamsError('Need to specify if `elev_bands` or '
×
615
                                     '`centerlines` type.')
616

617
        log.workflow('Start flowline processing with: '
1✔
618
                     'N centerline type: {}, '
619
                     'N elev bands type: {}.'
620
                     ''.format(len(gdirs_cent), len(gdirs_band)))
621

622
        # If we are coming from a multi-dem setup, let's select it from there
623
        if select_source_from_dir is not None:
1!
624
            from oggm.shop.rgitopo import select_dem_from_dir
×
625
            workflow.execute_entity_task(select_dem_from_dir, gdirs_band,
×
626
                                         dem_source=select_source_from_dir,
627
                                         keep_dem_folders=keep_dem_folders)
628
            workflow.execute_entity_task(select_dem_from_dir, gdirs_cent,
×
629
                                         dem_source=select_source_from_dir,
630
                                         keep_dem_folders=keep_dem_folders)
631

632
        # HH2015 method
633
        workflow.execute_entity_task(tasks.simple_glacier_masks, gdirs_band)
1✔
634

635
        # Centerlines OGGM
636
        workflow.execute_entity_task(tasks.glacier_masks, gdirs_cent)
1✔
637

638
        bin_variables = []
1✔
639
        if add_consensus_thickness:
1!
640
            from oggm.shop.bedtopo import add_consensus_thickness
×
641
            workflow.execute_entity_task(add_consensus_thickness, gdirs)
×
642
            bin_variables.append('consensus_ice_thickness')
×
643
        if add_itslive_velocity:
1!
644
            from oggm.shop.its_live import itslive_velocity_to_gdir
×
645
            workflow.execute_entity_task(itslive_velocity_to_gdir, gdirs)
×
646
            bin_variables.append('itslive_v')
×
647
        if add_millan_thickness:
1!
648
            from oggm.shop.millan22 import millan_thickness_to_gdir
×
649
            workflow.execute_entity_task(millan_thickness_to_gdir, gdirs)
×
650
            bin_variables.append('millan_ice_thickness')
×
651
        if add_millan_velocity:
1!
652
            from oggm.shop.millan22 import millan_velocity_to_gdir
×
653
            workflow.execute_entity_task(millan_velocity_to_gdir, gdirs)
×
654
            bin_variables.append('millan_v')
×
655
        if add_hugonnet_dhdt:
1!
656
            from oggm.shop.hugonnet_maps import hugonnet_to_gdir
×
657
            workflow.execute_entity_task(hugonnet_to_gdir, gdirs)
×
658
            bin_variables.append('hugonnet_dhdt')
×
659
        if add_bedmachine:
1!
660
            from oggm.shop.bedmachine import bedmachine_to_gdir
×
661
            workflow.execute_entity_task(bedmachine_to_gdir, gdirs)
×
662
            bin_variables.append('bedmachine_ice_thickness')
×
663
        if add_glathida:
1!
664
            from oggm.shop.glathida import glathida_to_gdir
×
665
            workflow.execute_entity_task(glathida_to_gdir, gdirs)
×
666
        if rgi_version == '70C':
1!
667
            # Some additional data for the 70C glaciers
668
            workflow.execute_entity_task(tasks.rgi7g_to_complex, gdirs)
×
669

670
        if bin_variables and gdirs_band:
1!
671
            workflow.execute_entity_task(tasks.elevation_band_flowline,
×
672
                                         gdirs_band,
673
                                         bin_variables=bin_variables)
674
            workflow.execute_entity_task(tasks.fixed_dx_elevation_band_flowline,
×
675
                                         gdirs_band,
676
                                         bin_variables=bin_variables)
677
        else:
678
            # HH2015 method without it
679
            task_list = [
1✔
680
                tasks.elevation_band_flowline,
681
                tasks.fixed_dx_elevation_band_flowline,
682
            ]
683
            for task in task_list:
1✔
684
                workflow.execute_entity_task(task, gdirs_band)
1✔
685

686
        # Centerlines OGGM
687
        task_list = [
1✔
688
            tasks.compute_centerlines,
689
            tasks.initialize_flowlines,
690
            tasks.catchment_area,
691
            tasks.catchment_intersections,
692
            tasks.catchment_width_geom,
693
            tasks.catchment_width_correction,
694
        ]
695
        for task in task_list:
1✔
696
            workflow.execute_entity_task(task, gdirs_cent)
1✔
697

698
        # Same for all glaciers
699
        if border >= 20:
1!
700
            task_list = [
1✔
701
                tasks.compute_downstream_line,
702
                tasks.compute_downstream_bedshape,
703
            ]
704
            for task in task_list:
1✔
705
                workflow.execute_entity_task(task, gdirs)
1✔
706
        else:
707
            log.workflow('L2: for map border values < 20, wont compute '
×
708
                         'downstream lines.')
709

710
        # Glacier stats
711
        sum_dir = Path(output_base_dir) / 'L2' / 'summary'
1✔
712
        utils.mkdir(sum_dir)
1✔
713
        opath = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
714
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
715

716
        if add_itslive_velocity:
1!
717
            from oggm.shop.its_live import compile_itslive_statistics
×
718
            opath = sum_dir / f'itslive_statistics_{rgi_reg}.csv'
×
719
            compile_itslive_statistics(gdirs, path=opath)
×
720
        if add_millan_thickness or add_millan_velocity:
1!
721
            from oggm.shop.millan22 import compile_millan_statistics
×
722
            opath = sum_dir / f'millan_statistics_{rgi_reg}.csv'
×
723
            compile_millan_statistics(gdirs, path=opath)
×
724
        if add_consensus_thickness:
1!
725
            from oggm.shop.bedtopo import compile_consensus_statistics
×
726
            opath = sum_dir / f'consensus_statistics_{rgi_reg}.csv'
×
727
            compile_consensus_statistics(gdirs, path=opath)
×
728
        if add_hugonnet_dhdt:
1!
729
            from oggm.shop.hugonnet_maps import compile_hugonnet_statistics
×
730
            opath = sum_dir / f'hugonnet_statistics_{rgi_reg}.csv'
×
731
            compile_hugonnet_statistics(gdirs, path=opath)
×
732
        if add_bedmachine:
1!
733
            from oggm.shop.bedmachine import compile_bedmachine_statistics
×
734
            opath = sum_dir / f'bedmachine_statistics_{rgi_reg}.csv'
×
735
            compile_bedmachine_statistics(gdirs, path=opath)
×
736
        if add_glathida:
1!
737
            from oggm.shop.glathida import compile_glathida_statistics
×
738
            opath = sum_dir / f'glathida_statistics_{rgi_reg}.csv'
×
739
            compile_glathida_statistics(gdirs, path=opath)
×
740

741
        # And for level 2: shapes
742
        if len(gdirs_cent) > 0:
1✔
743
            opath = sum_dir / f'centerlines_{rgi_reg}.shp'
1✔
744
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
745
                                             path=opath)
746
            opath = sum_dir / f'centerlines_smoothed_{rgi_reg}.shp'
1✔
747
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
748
                                             ensure_exterior_match=True,
749
                                             simplify_line_before=0.75,
750
                                             corner_cutting=3,
751
                                             path=opath)
752
            opath = sum_dir / f'flowlines_{rgi_reg}.shp'
1✔
753
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
754
                                             flowlines_output=True,
755
                                             path=opath)
756
            opath = sum_dir / f'geom_widths_{rgi_reg}.shp'
1✔
757
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
758
                                             geometrical_widths_output=True,
759
                                             path=opath)
760
            opath = sum_dir / f'widths_{rgi_reg}.shp'
1✔
761
            utils.write_centerlines_to_shape(gdirs_cent, to_tar=True,
1✔
762
                                             corrected_widths_output=True,
763
                                             path=opath)
764

765
        # L2 OK - compress all in output directory
766
        log.workflow('L2 done. Writing to tar...')
1✔
767
        level_base_dir = Path(output_base_dir) / 'L2'
1✔
768
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
769
                                     base_dir=level_base_dir)
770
        utils.base_dir_to_tar(level_base_dir)
1✔
771
        if max_level == 2:
1!
772
            _time_log()
×
773
            return
×
774

775
    # L3 - Tasks
776
    if start_level <= 2:
1!
777
        sum_dir = Path(output_base_dir) / 'L3' / 'summary'
1✔
778
        utils.mkdir(sum_dir)
1✔
779

780
        # Climate
781
        climate_kwargs = custom_climate_task_kwargs or {}
1✔
782
        if custom_climate_task:
1!
783
            try:
×
784
                mod_path, func_name = custom_climate_task.rsplit(':', 1)
×
785
            except ValueError:
×
786
                raise InvalidParamsError('custom_climate_task must be of the form "module:function"')
×
787
            try:
×
788
                mod = importlib.import_module(mod_path)
×
789
            except ModuleNotFoundError as err:
×
790
                raise InvalidParamsError(f'Cannot import module {mod_path}') from err
×
791
            try:
×
792
                custom_task_func = getattr(mod, func_name)
×
793
            except AttributeError as err:
×
794
                raise InvalidParamsError(f'Module {mod_path} has no attribute {func_name}') from err
×
795
            workflow.execute_entity_task(custom_task_func, gdirs, **climate_kwargs)
×
796
        else:
797
            workflow.execute_entity_task(tasks.process_climate_data, gdirs)
1✔
798

799
        use_regional_avg = False
1✔
800
        if '_regional' in mb_calibration_strategy:
1!
801
            use_regional_avg = True
×
802
            mb_calibration_strategy = mb_calibration_strategy.replace('_regional', '')
×
803

804
        if mb_calibration_strategy == 'informed_threestep':
1✔
805
            workflow.execute_entity_task(tasks.mb_calibration_from_geodetic_mb,
1✔
806
                                         gdirs,
807
                                         informed_threestep=True,
808
                                         mb_model_class=mb_model_class,
809
                                         use_regional_avg=use_regional_avg,
810
                                         file_path=geodetic_mb_file_path,
811
                                         temp_bias_file_path=temp_bias_file_path)
812
        elif mb_calibration_strategy == 'melt_temp':
1!
813
            workflow.execute_entity_task(tasks.mb_calibration_from_geodetic_mb,
1✔
814
                                         gdirs,
815
                                         calibrate_param1='melt_f',
816
                                         calibrate_param2='temp_bias',
817
                                         mb_model_class=mb_model_class,
818
                                         use_regional_avg=use_regional_avg,
819
                                         file_path=geodetic_mb_file_path)
820
        elif mb_calibration_strategy == 'temp_melt':
×
821
            workflow.execute_entity_task(tasks.mb_calibration_from_geodetic_mb,
×
822
                                         gdirs,
823
                                         calibrate_param1='temp_bias',
824
                                         calibrate_param2='melt_f',
825
                                         mb_model_class=mb_model_class,
826
                                         use_regional_avg=use_regional_avg,
827
                                         file_path=geodetic_mb_file_path)
828
        else:
829
            raise InvalidParamsError('mb_calibration_strategy not understood: '
×
830
                                     f'{mb_calibration_strategy}')
831

832
        if not skip_inversion:
1!
833
            workflow.execute_entity_task(tasks.apparent_mb_from_any_mb, gdirs,
1✔
834
                                         mb_model_class=mb_model_class,)
835

836
            filter = border >= 20
1✔
837

838
            # Inversion: calibrate Glen A to a reference volume dataset.
839
            # Be explicit about which dataset is used for which RGI version.
840
            if inversion_volume_dataset not in ('iceboost', 'consensus'):
1!
841
                raise InvalidParamsError(
×
842
                    "inversion_volume_dataset must be 'iceboost' or "
843
                    f"'consensus', not '{inversion_volume_dataset}'.")
844

845
            if rgi_version in ('70G', '70C') and \
1!
846
                    inversion_volume_dataset != 'iceboost':
847
                raise InvalidParamsError(
×
848
                    f"For {rgi_version} only inversion_volume_dataset='iceboost' "
849
                    f"is supported, not '{inversion_volume_dataset}' (the "
850
                    "consensus estimate is only available for RGI62).")
851

852
            # 'iceboost'/'consensus' map directly to ref_table presets
853
            workflow.calibrate_inversion_from_ref_table(
1✔
854
                gdirs,
855
                ref_table=inversion_volume_dataset,
856
                apply_fs_on_mismatch=True,
857
                error_on_mismatch=False,
858
                filter_inversion_output=filter)
859

860
            # Distribute thickness per altitude for gridded data
861
            if add_distributed_thickness:
1✔
862
                workflow.execute_entity_task(tasks.distribute_thickness_per_altitude, gdirs)
1✔
863

864
            # We get ready for modelling
865
            if border >= 20:
1!
866
                workflow.execute_entity_task(tasks.init_present_time_glacier, gdirs)
1✔
867
            else:
868
                log.workflow('L3: for map border values < 20, wont initialize glaciers '
×
869
                             'for the run.')
870
        # Glacier stats
871
        opath = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
872
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
873

874
        # Export thickness to GeoTIFF if requested
875
        if add_export_thickness_geotiff and add_distributed_thickness:
1✔
876
            thickness_dir = sum_dir / 'distributed_thickness'
1✔
877
            utils.mkdir(thickness_dir)
1✔
878
            workflow.execute_entity_task(tasks.gridded_data_var_to_geotiff, gdirs,
1✔
879
                                         varname='distributed_thickness',
880
                                         output_folder=thickness_dir)
881
        opath = sum_dir / f'climate_statistics_{rgi_reg}.csv'
1✔
882
        utils.compile_climate_statistics(gdirs, path=opath)
1✔
883
        opath = sum_dir / f'fixed_geometry_mass_balance_{rgi_reg}.csv'
1✔
884
        utils.compile_fixed_geometry_mass_balance(gdirs, path=opath,
1✔
885
                                                  mb_model_class=mb_model_class)
886

887
        # L3 OK - compress all in output directory
888
        log.workflow('L3 done. Writing to tar...')
1✔
889
        level_base_dir = Path(output_base_dir) / 'L3'
1✔
890
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
891
                                     base_dir=level_base_dir)
892
        utils.base_dir_to_tar(level_base_dir)
1✔
893
        if max_level == 3:
1✔
894
            _time_log()
1✔
895
            return
1✔
896
        if border < 20:
1!
897
            log.workflow('L3: for map border values < 20, wont compute L4 and L5.')
×
898
            _time_log()
×
899
            return
×
900

901
        # is needed to copy some files for L4 and L5
902
        sum_dir_L3 = sum_dir
1✔
903

904
    # L4 - Tasks (add historical runs (old default) and dynamic spinup runs)
905
    if start_level <= 3:
1!
906
        sum_dir = Path(output_base_dir) / 'L4' / 'summary'
1✔
907
        utils.mkdir(sum_dir)
1✔
908

909
        # Copy L3 files for consistency
910
        for bn in ['glacier_statistics', 'climate_statistics',
1✔
911
                   'fixed_geometry_mass_balance']:
912
            if start_level <= 2:
1!
913
                ipath = sum_dir_L3 / f'{bn}_{rgi_reg}.csv'
1✔
914
            else:
915
                ipath = file_downloader(os.path.join(
×
916
                    get_prepro_base_url(base_url=start_base_url,
917
                                        rgi_version=rgi_version, border=border,
918
                                        prepro_level=start_level), 'summary',
919
                    bn + '_{}.csv'.format(rgi_reg)))
920

921
            opath = sum_dir / f'{bn}_{rgi_reg}.csv'
1✔
922
            shutil.copyfile(ipath, opath)
1✔
923

924
        # Get end date. The first gdir might have blown up, try some others
925
        i = 0
1✔
926
        while True:
1✔
927
            if i >= len(gdirs):
1!
928
                raise RuntimeError('Found no valid glaciers!')
×
929
            try:
1✔
930
                y0 = gdirs[i].get_climate_info()['baseline_yr_0']
1✔
931
                # One adds 1 because the run ends at the end of the year
932
                ye = gdirs[i].get_climate_info()['baseline_yr_1'] + 1
1✔
933
                break
1✔
934
            except BaseException:
×
935
                i += 1
×
936

937
        # here we define the actual start date of the model outputs
938
        if y0 > dynamic_spinup_start_year:
1!
939
            dynamic_spinup_start_year = y0
×
940

941
        # conduct historical run before dynamic melt_f calibration
942
        # (for comparison to old default behavior)
943
        kwargs_run_from_climate_data = {
1✔
944
            'min_ys': y0, 'ye': ye, 'mb_model_class': mb_model_class,
945
            'save_mb_diagnostics_filesuffix': '_historical' if store_mb_diagnostics else None,
946
            'output_filesuffix': '_historical',
947
            'fixed_geometry_spinup_yr': dynamic_spinup_start_year,
948
        }
949
        if not store_hydro_output:
1✔
950
            workflow.execute_entity_task(
1✔
951
                tasks.run_from_climate_data, gdirs,
952
                **kwargs_run_from_climate_data
953
            )
954
        else:
955
            workflow.execute_entity_task(
1✔
956
                tasks.run_with_hydro, gdirs,
957
                run_task=tasks.run_from_climate_data,
958
                store_monthly_hydro=store_monthly_hydro,
959
                ref_area_yr=ref_area_yr,
960
                **kwargs_run_from_climate_data
961
            )
962
        # Now compile the output
963
        opath = Path(sum_dir) / f'historical_run_output_{rgi_reg}.nc'
1✔
964
        utils.compile_run_output(gdirs, path=opath, input_filesuffix='_historical')
1✔
965

966
        # conduct dynamic spinup if wanted
967
        if dynamic_spinup:
1✔
968

969
            minimise_for = dynamic_spinup.split('/')[0]
1✔
970

971
            melt_f_max = cfg.PARAMS['melt_f_max']
1✔
972
            kwargs_run_dynamic_melt_f_calibration = {
1✔
973
                'ref_mb_err_scaling_factor': ref_mb_err_scaling_factor,
974
                'ys': dynamic_spinup_start_year, 'ye': ye,
975
                'melt_f_max': melt_f_max,
976
                'mb_model_class': mb_model_class,
977
                'kwargs_run_function': {'minimise_for': minimise_for,
978
                                        'spinup_periods_to_try':
979
                                            dynamic_spinup_periods_to_try
980
                                        },
981
                'ignore_errors': True,
982
                'kwargs_fallback_function': {'minimise_for': minimise_for,
983
                                             'spinup_periods_to_try':
984
                                                 dynamic_spinup_periods_to_try
985
                                             },
986
                'save_mb_diagnostics_filesuffix': ('_spinup_historical'
987
                                                   if store_mb_diagnostics else None),
988
                'output_filesuffix': '_spinup_historical',
989
            }
990

991
            if not store_hydro_output:
1!
992
                workflow.execute_entity_task(
1✔
993
                    tasks.run_dynamic_melt_f_calibration, gdirs,
994
                    **kwargs_run_dynamic_melt_f_calibration
995
                    )
996
            else:
997
                workflow.execute_entity_task(
×
998
                    tasks.run_with_hydro, gdirs,
999
                    run_task=tasks.run_dynamic_melt_f_calibration,
1000
                    store_monthly_hydro=store_monthly_hydro,
1001
                    ref_area_yr=ref_area_yr,
1002
                    **kwargs_run_dynamic_melt_f_calibration
1003
                )
1004

1005
            # Now compile the output
1006
            opath = sum_dir / f'spinup_historical_run_output_{rgi_reg}.nc'
1✔
1007
            utils.compile_run_output(gdirs, path=opath,
1✔
1008
                                     input_filesuffix='_spinup_historical')
1009

1010
        # Glacier statistics we recompute here for error analysis
1011
        opath = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
1012
        utils.compile_glacier_statistics(gdirs, path=opath)
1✔
1013

1014
        # Add the extended files
1015
        pf = sum_dir / f'historical_run_output_{rgi_reg}.nc'
1✔
1016
        # We have copied the files above
1017
        mf = sum_dir / f'fixed_geometry_mass_balance_{rgi_reg}.csv'
1✔
1018
        sf = sum_dir / f'glacier_statistics_{rgi_reg}.csv'
1✔
1019
        opath = sum_dir / f'historical_run_output_extended_{rgi_reg}.nc'
1✔
1020
        utils.extend_past_climate_run(past_run_file=pf,
1✔
1021
                                      fixed_geometry_mb_file=mf,
1022
                                      glacier_statistics_file=sf,
1023
                                      path=opath)
1024

1025
        # L4 OK - compress all in output directory
1026
        log.workflow('L4 done. Writing to tar...')
1✔
1027
        level_base_dir = Path(output_base_dir) / 'L4'
1✔
1028
        workflow.execute_entity_task(utils.gdir_to_tar, gdirs, delete=False,
1✔
1029
                                     base_dir=level_base_dir)
1030
        utils.base_dir_to_tar(level_base_dir)
1✔
1031

1032
        sum_dir_L4 = sum_dir
1✔
1033

1034
        if max_level == 4:
1✔
1035
            _time_log()
1✔
1036
            return
1✔
1037

1038
    # L5 - No tasks: make the dirs small
1039
    sum_dir = Path(output_base_dir) / 'L5' / 'summary'
1✔
1040
    utils.mkdir(sum_dir)
1✔
1041

1042
    # Copy L4 files for consistency
1043
    files_to_copy = ['glacier_statistics', 'climate_statistics',
1✔
1044
                     'fixed_geometry_mass_balance', 'historical_run_output',
1045
                     'historical_run_output_extended']
1046
    files_suffixes = ['csv', 'csv', 'csv', 'nc', 'nc']
1✔
1047
    if dynamic_spinup:
1✔
1048
        files_to_copy.append('spinup_historical_run_output')
1✔
1049
        files_suffixes.append('nc')
1✔
1050
    for bn, suffix in zip(files_to_copy, files_suffixes):
1✔
1051
        if start_level <= 3:
1!
1052
            ipath = sum_dir_L4 / f'{bn}_{rgi_reg}.{suffix}'
1✔
1053
        else:
1054
            ipath = file_downloader(os.path.join(
×
1055
                get_prepro_base_url(base_url=start_base_url,
1056
                                    rgi_version=rgi_version, border=border,
1057
                                    prepro_level=start_level), 'summary',
1058
                f'{bn}_{rgi_reg}.{suffix}'))
1059
        opath = sum_dir / f'{bn}_{rgi_reg}.{suffix}'
1✔
1060
        shutil.copyfile(ipath, opath)
1✔
1061

1062
    # Copy mini data to new dir
1063
    mini_base_dir = (
1✔
1064
        Path(working_dir)
1065
        / 'mini_perglacier'
1066
        / f'RGI{rgi_version}'
1067
        / f'b_{border:03d}'
1068
    )
1069
    mini_gdirs = workflow.execute_entity_task(tasks.copy_to_basedir, gdirs,
1✔
1070
                                              base_dir=mini_base_dir,
1071
                                              setup='run/spinup')
1072

1073
    # L5 OK - compress all in output directory
1074
    log.workflow('L5 done. Writing to tar...')
1✔
1075
    level_base_dir = Path(output_base_dir) / 'L5'
1✔
1076
    workflow.execute_entity_task(utils.gdir_to_tar, mini_gdirs, delete=False,
1✔
1077
                                 base_dir=level_base_dir)
1078
    utils.base_dir_to_tar(level_base_dir)
1✔
1079

1080
    _time_log()
1✔
1081

1082

1083
def parse_args(args):
1✔
1084
    """Check input arguments and env variables"""
1085

1086
    # CLI args
1087
    description = ('Generate the preprocessed OGGM glacier directories for '
1✔
1088
                   'this OGGM version.')
1089
    parser = argparse.ArgumentParser(description=description)
1✔
1090
    parser.add_argument('--map-border', type=int,
1✔
1091
                        help='the size of the map border. Is required if '
1092
                             '$OGGM_MAP_BORDER is not set.')
1093
    parser.add_argument('--rgi-reg', type=str,
1✔
1094
                        help='the rgi region to process. Is required if '
1095
                             '$OGGM_RGI_REG is not set.')
1096
    parser.add_argument('--rgi-version', type=str,
1✔
1097
                        help='the RGI version to use. Defaults to the OGGM '
1098
                             'default.')
1099
    parser.add_argument('--start-level', type=int, default=0,
1✔
1100
                        help='the pre-processed level to start from (default '
1101
                             'is to start from 0). If set, you will need to '
1102
                             'indicate --start-base-url as well.')
1103
    parser.add_argument('--start-base-url', type=str,
1✔
1104
                        help='the pre-processed base-url to fetch the data '
1105
                             'from when starting from level > 0.')
1106
    parser.add_argument('--max-level', type=int, default=5,
1✔
1107
                        help='the maximum level you want to run the '
1108
                             'pre-processing for (1, 2, 3, 4 or 5).')
1109
    parser.add_argument('--working-dir', type=str,
1✔
1110
                        help='path to the directory where to write the '
1111
                             'output. Defaults to current directory or '
1112
                             '$OGGM_WORKDIR.')
1113
    parser.add_argument('--params-file', type=str,
1✔
1114
                        help='path to the OGGM parameter file to use in place '
1115
                             'of the default one.')
1116
    parser.add_argument('--output', type=str,
1✔
1117
                        help='path to the directory where to write the '
1118
                             'output. Defaults to current directory or '
1119
                             '$OGGM_OUTDIR.')
1120
    parser.add_argument('--logging-level', type=str, default='WORKFLOW',
1✔
1121
                        help='the logging level to use (DEBUG, INFO, WARNING, '
1122
                             'WORKFLOW).')
1123
    parser.add_argument('--elev-bands', nargs='?', const=True, default=False,
1✔
1124
                        help='compute the flowlines based on the Huss & Farinotti '
1125
                             '2012 method.')
1126
    parser.add_argument('--centerlines', nargs='?', const=True, default=False,
1✔
1127
                        help='compute the flowlines based on the OGGM '
1128
                             'centerline(s) method.')
1129
    parser.add_argument('--skip-inversion', nargs='?', const=True, default=False,
1✔
1130
                        help='do not run the inversion (level 3 files). '
1131
                             'this is a temporary workaround for workflows '
1132
                             'that wont run that far into level 3.')
1133
    parser.add_argument('--mb-model-class', type=str, default='MonthlyTIModel',
1✔
1134
                        help='the mass balance model class to use. Options are '
1135
                             'MonthlyTIModel (default) or SfcTypeTIModel.')
1136
    parser.add_argument('--inversion-volume-dataset', type=str,
1✔
1137
                        default='iceboost',
1138
                        choices=['iceboost', 'consensus'],
1139
                        help="reference volume dataset to calibrate the ice "
1140
                             "thickness inversion against. 'iceboost' (default, "
1141
                             "IceBoost v2, RGI62/RGI70G/RGI70C) or 'consensus' "
1142
                             "(Farinotti et al. 2019, RGI62 only).")
1143
    parser.add_argument('--mb-calibration-strategy', type=str,
1✔
1144
                        default='informed_threestep',
1145
                        help='how to calibrate the massbalance. Currently one of '
1146
                             'informed_threestep (default) , melt_temp '
1147
                             'or temp_melt. Add the _regional suffix to '
1148
                             'use regional values instead, for example '
1149
                             'informed_threestep_regional')
1150
    parser.add_argument('--dem-source', type=str, default='',
1✔
1151
                        help='which DEM source to use. Possible options are '
1152
                             'the name of a specific DEM (e.g. RAMP, SRTM...) '
1153
                             'or ALL, in which case all available DEMs will '
1154
                             'be processed and adjoined with a suffix at the '
1155
                             'end of the file name. The ALL option is only '
1156
                             'compatible with level 1 folders, after which '
1157
                             'the processing will stop. The default is to use '
1158
                             'the default OGGM DEM.')
1159
    parser.add_argument('--select-source-from-dir', type=str,
1✔
1160
                        default=None,
1161
                        help='if starting from a level 1 "ALL" or "STANDARD" DEM '
1162
                        'sources directory, select the chosen DEM source here. '
1163
                        'If you set it to "BY_RES" here, COPDEM will be used and '
1164
                        'its resolution chosen based on the gdirs map resolution '
1165
                        '(COPDEM30 for dx < 60 m, COPDEM90 elsewhere).')
1166
    parser.add_argument('--keep-dem-folders', nargs='?', const=True, default=False,
1✔
1167
                        help='if `select_source_from_dir` is used, wether to keep '
1168
                        'the original DEM folders in or not.')
1169
    parser.add_argument('--add-consensus-thickness', nargs='?', const=True, default=False,
1✔
1170
                        help='adds (reprojects) the consensus thickness '
1171
                             'estimates to the glacier directories. '
1172
                             'With --elev-bands, the data will also be '
1173
                             'binned.')
1174
    parser.add_argument('--add-itslive-velocity', nargs='?', const=True, default=False,
1✔
1175
                        help='adds (reprojects) the ITS_LIVE velocity '
1176
                             'estimates to the glacier directories. '
1177
                             'With --elev-bands, the data will also be '
1178
                             'binned.')
1179
    parser.add_argument('--add-millan-thickness', nargs='?', const=True, default=False,
1✔
1180
                        help='adds (reprojects) the millan thickness '
1181
                             'estimates to the glacier directories. '
1182
                             'With --elev-bands, the data will also be '
1183
                             'binned.')
1184
    parser.add_argument('--add-millan-velocity', nargs='?', const=True, default=False,
1✔
1185
                        help='adds (reprojects) the millan velocity '
1186
                             'estimates to the glacier directories. '
1187
                             'With --elev-bands, the data will also be '
1188
                             'binned.')
1189
    parser.add_argument('--add-hugonnet-dhdt', nargs='?', const=True, default=False,
1✔
1190
                        help='adds (reprojects) the hugonnet dhdt '
1191
                             'maps to the glacier directories. '
1192
                             'With --elev-bands, the data will also be '
1193
                             'binned.')
1194
    parser.add_argument('--add-bedmachine', nargs='?', const=True, default=False,
1✔
1195
                        help='adds (reprojects) the Bedmachine ice thickness '
1196
                             'maps to the glacier directories. '
1197
                             'With --elev-bands, the data will also be '
1198
                             'binned.')
1199
    parser.add_argument('--add-glathida', nargs='?', const=True, default=False,
1✔
1200
                        help='adds (reprojects) the glathida point thickness '
1201
                             'observations to the glacier directories. '
1202
                             'The data points are stored as csv.')
1203
    parser.add_argument('--custom-climate-task', type=str, default=None,
1✔
1204
                        help='Custom climate task import path in the form module:function. '
1205
                            'If provided, it replaces the default process_climate_data.')
1206
    parser.add_argument('--custom-climate-task-kwargs', type=json.loads, default=None,
1✔
1207
                        help='JSON dict of kwargs passed to the custom climate task.')
1208
    parser.add_argument('--add-distributed-thickness', nargs='?', const=True, default=False,
1✔
1209
                        help='adds a thickness field to gridded_data using '
1210
                             'distribute_thickness_per_altitude.')
1211
    parser.add_argument('--add-export-thickness-geotiff', nargs='?', const=True, default=False,
1✔
1212
                        help='exports the distributed thickness field to '
1213
                             'GeoTIFF files in a subfolder of the L3 summary '
1214
                             'directory. Requires --add-distributed-thickness.')
1215
    parser.add_argument('--compute-hypsometry', nargs='?', const=True, default=False,
1✔
1216
                        help='Compute the hypsometry tables for all glaciers, '
1217
                             'added to the glacier directory and compiled in '
1218
                             'the summary folder')
1219
    parser.add_argument('--test', nargs='?', const=True, default=False,
1✔
1220
                        help='if you want to do a test on a couple of '
1221
                             'glaciers first.')
1222
    parser.add_argument('--test-ids', nargs='+',
1✔
1223
                        help='if --test, specify the RGI ids to run separated '
1224
                             'by a space (default: 4 randomly selected).')
1225
    parser.add_argument('--rgi-file', type=str, default=None,
1✔
1226
                        help='path to an RGI shapefile to use instead of '
1227
                             'the default RGI region file.')
1228
    parser.add_argument('--intersects-file', type=str, default=None,
1✔
1229
                        help='path to an intersects shapefile to use instead '
1230
                             'of the default RGI intersects file.')
1231
    parser.add_argument('--disable-mp', nargs='?', const=True, default=False,
1✔
1232
                        help='if you want to disable multiprocessing.')
1233
    parser.add_argument('--dynamic-spinup', type=str, default='',
1✔
1234
                        help="include a dynamic spinup for matching glacier area "
1235
                             "('area/dmdtda') OR volume ('volume/dmdtda') at "
1236
                             "the RGI-date, AND mass-change from Hugonnet "
1237
                             "in the period 2000-2020 (dynamic melt_f "
1238
                             "calibration).")
1239
    parser.add_argument('--ref-mb-err-scaling-factor', type=float, default=0.2,
1✔
1240
                        help="scaling factor to account for correlated "
1241
                             "uncertainties of geodetic mass balance "
1242
                             "observations when looking at regional scale. "
1243
                             "Should be smaller or equal to 1.")
1244
    parser.add_argument('--dynamic-spinup-start-year', type=int, default=1979,
1✔
1245
                        help="if --dynamic-spinup is set, define the starting"
1246
                             "year for the simulation. The default is 1979, "
1247
                             "unless the climate data starts later.")
1248
    parser.add_argument('--dynamic-spinup-periods-to-try', nargs='*',
1✔
1249
                        default=[30, 40, 50, 60, 70, 80, 90, 100],
1250
                        help="if --dynamic-spinup is set, define additional "
1251
                             "spinup periods to try, if the spinup starting "
1252
                             "from --dynamic-spinup-year is not successful. If"
1253
                             "you do not want to use set"
1254
                             "'--dynamic-spinup-periods-to-try none' in the"
1255
                             "terminal.")
1256
    parser.add_argument('--geodetic-mb-file-path', type=str, default=None,
1✔
1257
                        help='optional path or URL to a custom geodetic MB '
1258
                             'file passed to MB calibration.')
1259
    parser.add_argument('--temp-bias-file-path', type=str, default=None,
1✔
1260
                        help='optional path or URL to a custom temperature-bias '
1261
                             'file passed to MB calibration (informed_threestep '
1262
                             'only). Use together with --custom-climate-task.')
1263
    parser.add_argument('--store-fl-diagnostics', nargs='?', const=True, default=False,
1✔
1264
                        help="Also compute and store flowline diagnostics during "
1265
                             "preprocessing. This can increase data usage quite "
1266
                             "a bit.")
1267
    parser.add_argument('--store-hydro-output', nargs='?', const=True, default=False,
1✔
1268
                        help='Add optional hydrological model output')
1269
    parser.add_argument('--store-monthly-hydro', nargs='?', const=True, default=True,
1✔
1270
                        help='If store-hydro-output is True, also store the '
1271
                             'hydrological model output in monthly resolution.')
1272
    parser.add_argument('--ref-area-yr', type=int, default=None,
1✔
1273
                        help='Force the reference area used for the hydrological '
1274
                             'output to the glacier state of the given simulation '
1275
                             'year, instead of the largest area during the '
1276
                             'simulation period.')
1277
    parser.add_argument('--override-params', type=json.loads, default=None)
1✔
1278

1279
    args = parser.parse_args(args)
1✔
1280

1281
    # Check input
1282
    rgi_reg = args.rgi_reg
1✔
1283
    if not rgi_reg:
1✔
1284
        rgi_reg = os.environ.get('OGGM_RGI_REG', None)
1✔
1285
        if rgi_reg is None:
1✔
1286
            raise InvalidParamsError('--rgi-reg is required!')
1✔
1287
    rgi_reg = '{:02}'.format(int(rgi_reg))
1✔
1288
    ok_regs = ['{:02}'.format(int(r)) for r in range(1, 20)]
1✔
1289
    if rgi_reg not in ok_regs:
1!
1290
        raise InvalidParamsError('--rgi-reg should range from 01 to 19!')
×
1291

1292
    rgi_version = args.rgi_version
1✔
1293

1294
    border = args.map_border
1✔
1295
    if not border:
1✔
1296
        border = os.environ.get('OGGM_MAP_BORDER', None)
1✔
1297
        if border is None:
1✔
1298
            raise InvalidParamsError('--map-border is required!')
1✔
1299

1300
    working_dir = args.working_dir
1✔
1301
    if not working_dir:
1✔
1302
        working_dir = os.environ.get('OGGM_WORKDIR', '')
1✔
1303

1304
    output_folder = args.output
1✔
1305
    if not output_folder:
1✔
1306
        output_folder = os.environ.get('OGGM_OUTDIR', '')
1✔
1307

1308
    border = int(border)
1✔
1309
    output_folder = os.path.abspath(output_folder)
1✔
1310
    working_dir = os.path.abspath(working_dir)
1✔
1311

1312
    dynamic_spinup = False if args.dynamic_spinup == '' else args.dynamic_spinup
1✔
1313

1314
    if args.dynamic_spinup_periods_to_try == ['none']:
1!
1315
        args.dynamic_spinup_periods_to_try = None
×
1316

1317
    # All good
1318
    return dict(rgi_version=rgi_version, rgi_reg=rgi_reg,
1✔
1319
                border=border, output_folder=output_folder,
1320
                working_dir=working_dir, params_file=args.params_file,
1321
                is_test=args.test, test_ids=args.test_ids,
1322
                rgi_file=args.rgi_file,
1323
                intersects_file=args.intersects_file,
1324
                dem_source=args.dem_source,
1325
                start_level=args.start_level, start_base_url=args.start_base_url,
1326
                max_level=args.max_level, disable_mp=args.disable_mp,
1327
                logging_level=args.logging_level,
1328
                elev_bands=args.elev_bands,
1329
                skip_inversion=args.skip_inversion,
1330
                inversion_volume_dataset=args.inversion_volume_dataset,
1331
                centerlines=args.centerlines,
1332
                select_source_from_dir=args.select_source_from_dir,
1333
                keep_dem_folders=args.keep_dem_folders,
1334
                add_consensus_thickness=args.add_consensus_thickness,
1335
                add_millan_thickness=args.add_millan_thickness,
1336
                add_itslive_velocity=args.add_itslive_velocity,
1337
                add_millan_velocity=args.add_millan_velocity,
1338
                add_hugonnet_dhdt=args.add_hugonnet_dhdt,
1339
                add_bedmachine=args.add_bedmachine,
1340
                add_glathida=args.add_glathida,
1341
                add_distributed_thickness=args.add_distributed_thickness,
1342
                add_export_thickness_geotiff=args.add_export_thickness_geotiff,
1343
                compute_hypsometry=args.compute_hypsometry,
1344
                custom_climate_task=args.custom_climate_task,
1345
                custom_climate_task_kwargs=args.custom_climate_task_kwargs,
1346
                dynamic_spinup=dynamic_spinup,
1347
                ref_mb_err_scaling_factor=args.ref_mb_err_scaling_factor,
1348
                dynamic_spinup_start_year=args.dynamic_spinup_start_year,
1349
                dynamic_spinup_periods_to_try=args.dynamic_spinup_periods_to_try,
1350
                mb_model_class=args.mb_model_class,
1351
                mb_calibration_strategy=args.mb_calibration_strategy,
1352
                geodetic_mb_file_path=args.geodetic_mb_file_path,
1353
                temp_bias_file_path=args.temp_bias_file_path,
1354
                store_fl_diagnostics=args.store_fl_diagnostics,
1355
                store_hydro_output=args.store_hydro_output,
1356
                store_monthly_hydro=args.store_monthly_hydro,
1357
                ref_area_yr=args.ref_area_yr,
1358
                override_params=args.override_params,
1359
                )
1360

1361

1362
def main():
1✔
1363
    """Script entry point"""
1364

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

© 2026 Coveralls, Inc