A scientific Python toolkit for downloading and analysing global mountain-glacier area change from satellite imagery. It pulls glacier outlines, multi-decadal Landsat surface-reflectance imagery, and reanalysis climate fields from open archives; classifies glacier ice with the Normalised-Difference Snow Index (NDSI); builds per-glacier area time series; couples those trends to local temperature and precipitation; validates them against published reference areas and an independent DEM-differencing mass-balance dataset; and exposes a suite of statistical methods (trend fitting, bootstrap confidence intervals, robustness sweeps, and critical-slowing-down / autocorrelation diagnostics) together with vector figure builders. This repository is the code-only distribution: it ships the methods and pipelines, not the (large, separately-licensed) raw datasets.
- Data acquisition — server- and client-side Google Earth Engine workflows for Landsat 5/7/8/9 Collection-2 Level-2 imagery, GLIMS glacier outlines (single glacier, whole RGI region, or full database), ERA5-Land monthly climate, plus loaders for the Hugonnet (2021) mass-balance archive and the RGI v6.2 metadata table.
- NDSI ice classification — sensor harmonisation, cloud masking, seasonal median compositing, NDSI/NDWI computation, slope masking, and pixel-count area estimation with boundary-pixel uncertainty.
- Area-change analysis — multi-temporal area time series, OLS trends with bootstrap CIs and Mann–Kendall tests, and acceleration tests.
- Climate coupling — local CRU TS temperature and precipitation extraction, warming/wetting trends, cross-glacier warming-vs-retreat regression (OLS and Theil–Sen), and confounder control via partial correlation, multivariate regression, mediation decomposition, and interval / fixed-effects panel estimators.
- Validation — comparison of NDSI-derived areas against published reference areas, and cross-validation of area trends against the independent Hugonnet (2021) mass-balance dataset.
- Resilience diagnostics — a critical-slowing-down (CSD) toolkit implementing AR(1), variance and skewness estimators (point and rolling), Pettitt change-point detection, the Kendall-tau-on-rolling-AR(1) fingerprint, and surrogate/null machinery, applied to both glacier area and mass-balance series.
- Reproducible pipelines & figures — console entry points that chain the above into end-to-end runs, a library of vector (PDF) figure builders, and numbered analysis work-packages.
Note. A manuscript that uses this toolkit is in preparation. This README documents the code and methods only; no scientific results are reported here.
| Path | Role |
|---|---|
glacier_toolkit/ |
Installable Python package (glacier-toolkit). |
acquire/ |
Data-acquisition layer (Landsat, GLIMS, ERA5-Land via Earth Engine; loaders for Hugonnet 2021 mass balance and RGI v6.2). |
analyze/ |
Statistical core: NDSI classification, area-change pipeline, climate/precipitation coupling, confounder control, interval analysis, and the CSD/resilience toolkit. |
validate/ |
Validation against published reference areas and the Hugonnet (2021) mass-balance dataset. |
visualize/ |
Vector figure builders (white-background, 300 DPI, PDF) for the analysis outputs. |
pipelines/ |
End-to-end CLI pipelines exposed as glacier-* console scripts. |
config.py |
Central paths, stats defaults (bootstrap N / seed / CI), and a registry of notable glaciers. |
scripts/ |
Download/extraction helpers, numbered analysis work-packages (wp*.py), figure builders (make_*.py), and shell orchestration for the long-running Earth Engine extraction chain. |
tests/ |
pytest suite covering config, NDSI, area, statistics, correlation, multivariate, interval, resilience, validation, and more. |
paper/figures/ |
Gitignored output directory for generated figure PDFs (only .gitkeep is tracked). Figure scripts write here. |
Computed products (
glacier_data/,glacier_outputs/) and generated figures (paper/figures/) are gitignored and not part of the repository.
Requires Python 3.10–3.12.
git clone https://github.com/bijanf/climate-shift.git
cd climate-shift
python -m pip install -e . # core install
python -m pip install -e ".[all]" # everything (recommended for full pipelines)The core install pulls numpy, pandas, matplotlib, scipy, xarray, scikit-image, Pillow, tqdm and requests. Heavier capabilities are split into optional extras:
| Extra | pip install -e ".[extra]" adds |
Needed for |
|---|---|---|
geo |
rasterio, rioxarray, geopandas, shapely, fiona, pyproj, cartopy, netCDF4 | Reading GeoTIFFs/shapefiles, equal-area computation, CRU/ERA5 netCDF, and cartopy maps. |
gee |
earthengine-api | Google Earth Engine acquisition (Landsat, GLIMS, ERA5-Land). |
paper |
statsmodels | LOESS detrending in analyze/resilience_mass.py and the statistical work-packages (scripts/wp*.py). |
dev |
ruff, mypy, pre-commit, pytest, pytest-cov, pytest-xdist | Linting, typing, hooks, and the test suite. |
all |
glacier-toolkit[geo,gee,paper,dev] |
Combined extras. |
- GIS system libraries for the
geoextra: GDAL, GEOS and PROJ must be present. On Debian/Ubuntu:(CI installs exactly these beforesudo apt-get install -y libgdal-dev gdal-bin libgeos-dev libproj-dev proj-bin proj-data
pip install -e ".[geo,dev]".) - Google Earth Engine account + authenticated
earthengine-apifor any acquisition that hits GEE (Landsat, GLIMS, ERA5-Land). The GEE-backed modules runee.Authenticate()lazily on first use. - Copernicus CDS account if you obtain ERA5-Land outside of the GEE path.
- Local disk for the gitignored cache: full-region Landsat/GLIMS extractions and the Hugonnet/RGI archives are large (the GLIMS bulk shapefile alone is ~500 MB).
Raw datasets are not redistributed in this repository — only the code that
fetches them. Acquisition writes into a local, gitignored cache (default
glacier_data/) so that downloads are reused across runs.
The glacier_toolkit/acquire/ modules and the scripts/ download helpers obtain:
- Landsat NDSI / imagery —
acquire/landsat.pyexports per-glacier NDSI/RGB GeoTIFFs from Landsat C2 L2 via Earth Engine;acquire/landsat_batch.pyis the server-side batch variant that returns per-glacier-per-year ice area (km²) without downloading rasters, with payload/feature-limit handling and CSV resume caching.scripts/download_all_glaciers.pydrives a resumable per-glacier run. - Glacier outlines (GLIMS / RGI) —
acquire/glims_gee.py(single glaciers) andacquire/glims_regions.py(whole RGI O1 regions) fetch from the GEEGLIMS/currentcollection and cache GeoJSON;acquire/glims.pyis a heavier fallback that downloads the full GLIMS shapefile directly.acquire/rgi_stats.pyloads the RGI v6.2 metadata table and provides the RGIId↔GLIMSId join. - Climate —
acquire/era5.pyextracts ERA5-Land monthly fields per glacier from GEE;scripts/extract_annual_cru_per_glacier.pyextracts annual CRU TS temperature and precipitation at glacier locations. - Independent mass balance —
acquire/hugonnet.pyis a loader for the Hugonnet et al. (2021) per-glacier mass-balance archive: download the per-RGI TAR files manually from the data DOI (SEDOO/Theia) intoglacier_data/hugonnet/and the module auto-extracts and joins them.scripts/fetch_dussaillant2025.pyprepares the Dussaillant (2025) surface-mass-balance series.
Manual prerequisites (cannot be fetched programmatically):
- Hugonnet (2021) per-region TAR files in
glacier_data/hugonnet/. rgi62_stats.csv(RGI v6.2 metadata) placed alongside the Hugonnet download.- CRU TS netCDF grids (temperature
tmx, precipitationpre) underdata/.
The pipelines communicate through CSV/parquet caches written under
glacier_outputs/ (gitignored), not through direct function calls, so they are
run in sequence. Each is exposed as a console script (installed by pip install -e .):
| Command | Module | Purpose |
|---|---|---|
glacier-paper |
pipelines/run_paper.py |
Per-registry-glacier run: NDSI area series + trends, local warming, climate sensitivity, reference-area validation, terminus-stratified cross-glacier regression, tables and figures. |
glacier-global |
pipelines/run_global.py |
Scale-out run over whole RGI regions: GLIMS fetch/dedup, batched GEE ice-area computation, coverage/length filtering, cross-glacier regression, RGI enrichment, region + combined result CSVs. |
glacier-refit-with-coverage-filter |
pipelines/refit_with_coverage_filter.py |
Maintenance refit: drop low-coverage glacier-years, re-fit per-glacier slopes, back up raw CSVs, overwrite region/combined results. |
glacier-nc-paper |
pipelines/run_nc_paper.py |
Cross-region regressions, Hugonnet cross-validation, multivariate regression + partial correlation + per-stratum coupling, scale-aware figures. |
glacier-interval-analysis |
pipelines/run_interval_analysis.py |
Fixed-interval panel decomposition with within-glacier fixed-effects and first-difference estimators. |
glacier-multipathway-analysis |
pipelines/run_multipathway_analysis.py |
Direct vs precipitation-mediated decomposition (bivariate T+P, partial Spearman, mediation). |
glacier-sign-flip-validation |
pipelines/run_sign_flip_validation.py |
Per-region comparison of the area-trend coupling sign against the Hugonnet mass-balance coupling sign. |
glacier-era5-seb-analysis |
pipelines/run_era5_seb_analysis.py |
Extends the multipathway controls with ERA5-Land surface-energy-balance proxies (temperature, humidity, shortwave). |
glacier-sensitivity |
pipelines/run_sensitivity.py |
Robustness sweep over NDSI threshold, smoothing window, time range and estimator; reports stability and renders a forest plot. |
A typical scale-out order is:
glacier-global → glacier-refit-with-coverage-filter →
{ glacier-nc-paper, glacier-interval-analysis, glacier-multipathway-analysis, glacier-sign-flip-validation, glacier-era5-seb-analysis }.
The numbered work-packages in scripts/wp*.py (wp4–wp26) implement
additional CSD/resilience, null/surrogate, FDR, power and robustness analyses
(for example wp4_pettitt_l5sensitivity.py, wp5_spatial_coherence.py,
wp8_dual_metric_csd.py, wp11_mass_csd_robustness.py). Shell scripts such as
scripts/launch_global_extraction_chain.sh, scripts/watchdog_extraction_chain.sh
and scripts/run_paper_full.sh orchestrate the long-running, interruptible Earth
Engine extraction chain. All outputs land under glacier_outputs/ (gitignored).
Many pipelines and scripts require the upstream cache to exist first; on a clean checkout they will fetch/recompute their inputs (the global extraction chain is long-running and Earth-Engine-bound) rather than read pre-shipped products.
Figure builders live in scripts/ (make_fig*.py, make_supp*.py,
make_figS*.py) and in glacier_toolkit/visualize/. They render vector PDFs
(sans-serif, RGB, editable text) from the analysis outputs and write them into
paper/figures/, which is gitignored except for .gitkeep. After a run,
scripts/freeze_paper_figures.sh collects the curated figure PDFs from that
directory. The visualize/ modules use an explicit white-background, 300-DPI
publication style; scripts/verify_greyscale_colorblind.py provides figure QC.
The repository is set up for standard Python tooling.
python -m pip install -e ".[dev]"
python -m pytest # run the test suite (175 tests collected)
ruff check . # lint
ruff format --check . # formatting check
mypy glacier_toolkit # static typing (informational)
pre-commit install # enable git hooks
pre-commit run --all-files # run all hooks- pytest — 15
test_*.pyfiles (175 tests collected) covering config, correlation, area, NDSI, statistics, multivariate, interval analysis, resilience (area and mass), coverage refit, RGI stats, and validation. Markersslow,integrationandgeeare configured. - ruff — line length 100, target Python 3.10, rule sets E/W/F/I/B/C4/UP/SIM/RUF; used for both linting and formatting.
- mypy — configured in
pyproject.toml(informational in CI). - pre-commit — whitespace/EOF/merge/large-file hooks, ruff (
--fix) + ruff-format, and mypy onglacier_toolkit/. - Continuous integration —
.github/workflows/ci.ymlruns lint, typecheck (non-blocking), a test matrix across Python 3.10 / 3.11 / 3.12 (with GDAL/ GEOS/PROJ apt deps and coverage), and a build/twine-check job..github/workflows/codeql.ymlruns CodeQL security scanning (public repos).
This toolkit consumes the following open datasets. Each is obtained under its own terms; please cite the original providers when you use them.
| Dataset | Module(s) | License / terms | Citation |
|---|---|---|---|
| Landsat 5/7/8/9 Collection-2 Level-2 | acquire/landsat.py, acquire/landsat_batch.py |
USGS — public domain | USGS / NASA Landsat; Roy et al. (2016) sensor harmonisation. |
| GLIMS glacier outlines | acquire/glims*.py |
NSIDC / GLIMS data-use policy | GLIMS & NSIDC (glims.org). |
| Randolph Glacier Inventory (RGI v6.2) | acquire/rgi_stats.py |
NSIDC / RGI use policy | RGI Consortium (2017). |
| Hugonnet et al. (2021) mass balance | acquire/hugonnet.py, validate/hugonnet_validation.py |
SEDOO/Theia data-use terms | Hugonnet et al. (2021), Nature; data DOI 10.6096/13. |
| Dussaillant (2025) surface mass balance | analyze/resilience_mass.py, scripts/fetch_dussaillant2025.py |
Provider terms | Dussaillant et al. (2025). |
| ERA5-Land monthly reanalysis | acquire/era5.py |
Copernicus / ECMWF licence | Muñoz-Sabater et al. (2021), ERA5-Land. |
| CRU TS v4.x (tmx, pre) | analyze/climate_link.py, analyze/precipitation_link.py |
CEDA / CRU open licence | Harris et al. (2020), CRU TS. |
If you use this toolkit, please cite the software:
@software{fallah_climate_shift_2026,
author = {Fallah, Bijan},
title = {{climate-shift}: a toolkit for downloading and analysing
global mountain-glacier area change from satellite imagery},
year = {2026},
version = {0.1.0},
url = {https://github.com/bijanf/climate-shift}
}A manuscript using this toolkit is in preparation; a reference will be added here on publication.
Released under the MIT License. Copyright © 2026 Bijan Fallah.
Bijan Fallah — bijan.fallah01@gmail.com GitHub: github.com/bijanf/climate-shift