U-Net land-cover classifier for landscape-change-detection-pipeline
Pixel-wise land-cover / land-surface segmentation model (U-Net with FiLM conditioning) trained for the landscape-change-detection-pipeline. It classifies satellite scenes (Landsat 5/7/8/9, Sentinel-2) on a common 10 m grid into 16 classes. The resulting class maps feed the pipeline's monthly composites, break detection and change-event analysis.
This checkpoint is not a general-purpose land-cover model. It is meant to be run through the pipeline above, which builds the exact 21-channel input it expects. See Limitations.
Quick start
git clone https://github.com/Research-Tarka/landscape-change-detection-pipeline
cd landscape-change-detection-pipeline
conda env create -f environment.yml && conda activate landscape-change-detection-pipeline
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu130
pip install -r requirements-torch.txt && pip install -e . huggingface_hub
hf download <USER>/<REPO> unet_best.pt --local-dir models/checkpoints
Then run python scripts/06_run_inference.py (with inference.checkpoint_path: null it picks up
models/checkpoints/unet_best.pt by default) (steps 01-03 of the README produce the scenes it needs).
The checkpoint is self-describing (weights, class names, feature names, normalisation, architecture), so nothing else is needed to load it:
from landscape_change_detection_pipeline.inference.model_loader import load_model_for_inference
loaded = load_model_for_inference("models/checkpoints/unet_best.pt", device="cuda")
print(loaded.class_names, loaded.feature_names)
# loaded.model, loaded.mean, loaded.std are what inference.engine.predict_scene() takes.
Model
| Architecture | U-Net, depth 4, 48 base channels, batch norm, dropout 0.006, multi-head self-attention in the bottleneck (8 heads) |
| Conditioning | FiLM on sensor and day-of-year (film_sensor, film_doy); no lat/lon FiLM |
Checkpoint num_sensors |
1 |
| Input | 21 channels, 256 x 256 patches (sliding window + Hann weighting at inference), normalised with the train-only mean/std stored in the checkpoint |
| Output | 16 classes (dense ids = position in configs/classes.yaml) |
| Size | ~20 MB |
Input channels (in this order): NDVI, NDSI, NDWI_GAO, NBR, ND_SWIR1_SWIR2, ND_BLUE_RED, ND_BLUE_NIR, ND_GREEN_RED, NDWI_MCFEETERS, r, g, b, BSI, EVI, TC_BRIGHTNESS, TC_GREENNESS, TC_WETNESS, elevation, slope, aspect_sin, aspect_cos. Spectral inputs are top-of-atmosphere (TOA) reflectance; terrain layers come from the
tile DEM. Changing this list changes the input size and requires retraining.
Classes (dense id: name): 0 forest, 1 bare_ground, 2 grassland_herbaceous, 3 cultivated_agriculture, 4 wetland_marsh, 5 open_water, 6 cutblock_harvest, 7 burned_disturbed, 8 snow_cover, 9 ice_cover, 10 rock_alpine_bare, 11 built_up_infrastructure, 12 cloud, 13 shadow, 14 firn, 15 sand_gravel.
Not every class is predicted: cultivated_agriculture, wetland_marsh, cutblock_harvest and sand_gravel had
no training pixels in this run, and three classes were merged into others (training.class_merge):
built_up_infrastructure and rock_alpine_bare into bare_ground, and firn into ice_cover. The model
effectively predicts 9 classes: forest, bare ground, grassland/herbaceous, open water, burned/disturbed,
snow, ice, cloud and shadow (the other outputs are unused). Because firn is folded into ice, this checkpoint
never outputs a separate firn class.
Raw ids (used in masks, class_map.npz, composites) differ from dense ids; the pipeline converts between them.
See configs/classes.yaml and section 5 of the pipeline README.
Training
- Labels: 705 hand-annotated scenes painted with MaskForge (420 train / 142 val / 143 test), split by tile (60 / 20 / 20, seed 0) so no tile appears in more than one partition. No pseudo-labels were used in this run.
- Loss: focal (gamma 1.33) + Dice (weight 0.588), class weighting (power 0.447, max 10), extra penalties on six confusable (true, predicted) pairs (cloud->burned, bare->burned, forest->water, forest->ice, bare->snow, snow->ice).
- Optimisation: AdamW, lr 4.5e-4, weight decay 2e-3, batch 32 (micro-batch 8), AMP, grad clip 1.0, EMA of the weights (decay 0.995), flips + 90-degree rotations and radiometric augmentation (brightness / contrast / noise), early stopping (patience 15). Best epoch 52 (validation mIoU 0.632 during training); training stopped at epoch 67 and the released weights are the best-epoch ones.
- Full configuration, per-epoch history, per-class IoU and confusion matrices are in
unet_run_report.json.
Evaluation
Macro mIoU over the classes present in each split:
| Split | mIoU | Kappa |
|---|---|---|
| Train | 0.665 | 0.822 |
| Validation | 0.643 | 0.803 |
| Test | 0.604 | 0.749 |
Per-class IoU on the test split:
| Class | IoU | Precision | Recall |
|---|---|---|---|
| forest | 0.761 | 0.873 | 0.856 |
| bare_ground | 0.600 | 0.728 | 0.774 |
| grassland_herbaceous | 0.609 | 0.762 | 0.752 |
| open_water | 0.790 | 0.854 | 0.913 |
| burned_disturbed | 0.532 | 0.578 | 0.869 |
| snow_cover | 0.592 | 0.795 | 0.698 |
| ice_cover | 0.383 | 0.685 | 0.465 |
| cloud | 0.715 | 0.830 | 0.837 |
| shadow | 0.457 | 0.629 | 0.626 |
The split is by tile, so the test score measures generalisation to unseen tiles of the same study area and is the realistic figure to use.
Limitations and intended use
- Weak classes:
ice_cover(test IoU 0.38, recall 0.47) andshadow(0.46) are the least reliable.burned_disturbedis over-predicted (precision 0.58 for recall 0.87), so burned areas can include bare ground or dark surfaces. Firn, rock and built-up are merged into other classes and cannot be told apart. - Domain: trained on one study area and its annotation protocol; the checkpoint was not validated elsewhere. Expect degraded results in other regions, climates or seasons, and re-annotate / fine-tune for a new study.
- Resolution: all sensors are resampled to a 10 m grid; Landsat pixels remain ~30 m information. A 1-2 pixel misregistration with reference data is the same size as the boundary errors being measured.
- Inputs: TOA reflectance (not surface reflectance); Landsat 7 after 2002 (SLC-off) is not used.
- Not a certified product: not suitable on its own for safety-critical, legal or regulatory decisions.
Data sources and licences
Imagery is accessed through Google Earth Engine: Landsat Collection 2 TOA (USGS, public domain), Sentinel-2 harmonized (Copernicus, free and open), DEM from MRDEM-30 / Copernicus GLO-30. Please check the terms of each provider if you redistribute derived products. Model weights and code: MIT licence.
Citation
@software{tarka_landscape_change_detection_pipeline,
author = {Tarka, Maxime},
title = {landscape-change-detection-pipeline},
url = {https://github.com/Research-Tarka/landscape-change-detection-pipeline},
license = {MIT}
}