Skip to content

Instantly share code, notes, and snippets.

@hkristen
Created January 16, 2026 12:00
Show Gist options
  • Select an option

  • Save hkristen/d64fda3e40903b459a820ee8d688368c to your computer and use it in GitHub Desktop.

Select an option

Save hkristen/d64fda3e40903b459a820ee8d688368c to your computer and use it in GitHub Desktop.
HabitAlp2 DataModule Usage Guide

HabitAlp2 DataModule - Quick Reference

What's New

Built on your original semantic segmentation work with these additions:

  • Change Detection task - multiclass CD (9 classes) with bi-temporal pairs
  • 12 terrain layers - LiDAR-derived features (2013/2020 only)
  • Multi-year support - 2003 (RGB only), 2013, 2020 (RGB+NIR+terrain)

⚠️ Critical Limitation: No Proper Splits

The datamodule uses the same GeoDataset instance for train/val/test.

# Current implementation in HabitAlp2DataModule.setup()
dataset = HabitAlp2(...) or HabitAlp2CD(...)
self.train_dataset = dataset  # Same instance!
self.val_dataset = dataset
self.test_dataset = dataset

Impact:

  • Random sampling from entire region for all splits
  • No spatial separation → validation metrics are optimistic
  • Need to implement proper spatial CV for real evaluation

Why: Single large GeoTIFF, can't use random_bbox_assignment without manual region splitting.

TODO: Needs a proper split strategy (manual bbox splits, temporal splits, or k-fold spatial CV).


Usage

Semantic Segmentation (same as your original work)

from torchgeo.datamodules import HabitAlp2DataModule

# Create datamodule for semantic segmentation
dm = HabitAlp2DataModule(
    root='/path/to/data',
    batch_size=16,
    patch_size=256,
    length=1000,  # samples per epoch
    task='segmentation',  # or 'classification'
    year='2013',  # one of '2003', '2013', '2020'
    bands=['R', 'G', 'B', 'NIR'],  # customize bands
    download=True
)

# Setup and use
dm.setup('fit')
train_loader = dm.train_dataloader()
val_loader = dm.val_dataloader()

# Iterate
for batch in train_loader:
    image = batch['image']  # (B, C, H, W)
    mask = batch['mask']    # (B, H, W) with values 0-23
    # Training code here

Using Terrain Layers

# Include terrain data (only available for 2013, 2020)
dm = HabitAlp2DataModule(
    root='/path/to/data',
    batch_size=16,
    patch_size=256,
    length=1000,
    task='segmentation',
    year='2020',
    bands=['R', 'G', 'B', 'NIR', 'ndsm', 'slope', 'dtm'],
    download=True
)

YAML Configuration Example

# config_segmentation.yaml
model:
  class_path: torchgeo.trainers.SemanticSegmentationTask
  init_args:
    model: 'unet'
    backbone: 'resnet50'
    in_channels: 4  # RGB + NIR
    num_classes: 24  # 23 classes + background
    loss: 'ce'
    ignore_index: 0

data:
  class_path: torchgeo.datamodules.HabitAlp2DataModule
  init_args:
    batch_size: 16
    patch_size: 256
    length: 2000
    task: 'segmentation'
  dict_kwargs:
    root: '/path/to/data'
    year: '2013'
    bands: ['R', 'G', 'B', 'NIR']

2. Change Detection

Basic Usage

from torchgeo.datamodules import HabitAlp2DataModule

# Create datamodule for change detection
dm = HabitAlp2DataModule(
    root='/path/to/data',
    batch_size=8,
    patch_size=256,
    length=500,
    task='change_detection',
    pair='2013_2020',  # or '2003_2013'
    bands=['R', 'G', 'B'],
    download=True
)

# Setup and use
dm.setup('fit')
train_loader = dm.train_dataloader()

# Iterate
for batch in train_loader:
    image = batch['image']  # (B, T, C, H, W) where T=2 (bi-temporal)
    mask = batch['mask']    # (B, 1, H, W) with values 0-8 (multiclass CD)
    # Training code here

YAML Configuration Example

# config_change_detection.yaml
model:
  class_path: torchgeo.trainers.ChangeDetectionTask
  init_args:
    model: 'unet'
    backbone: 'resnet18'
    in_channels: 3
    task: 'multiclass'
    num_classes: 9  # 9 change classes
    loss: 'ce'
    ignore_index: 255

data:
  class_path: torchgeo.datamodules.HabitAlp2DataModule
  init_args:
    batch_size: 8
    patch_size: 256
    length: 1000
    task: 'change_detection'
  dict_kwargs:
    root: '/path/to/data'
    pair: '2013_2020'
    bands: ['R', 'G', 'B']

Training with Lightning CLI

# Semantic segmentation
python train.py fit --config config_segmentation.yaml

# Change detection
python train.py fit --config config_change_detection.yaml

⚠️ Important Limitations: Train/Val/Test Splits

Current Behavior

The datamodule currently uses the SAME underlying GeoDataset for train/val/test splits.

This means:

  • No spatial separation between splits
  • Train and validation samples are drawn from the same geographic area
  • Validation metrics may be overly optimistic
  • Test performance may not reflect real-world generalization

Why This Happens

The HabitAlp2 dataset consists of single large GeoTIFFs covering the entire study area. The current implementation cannot spatially partition the data because:

  1. It uses a single GeoDataset instance for all splits
  2. Random sampling draws patches from the entire area for all splits
  3. There's no built-in mechanism to create geographic train/val/test regions

Workaround Options

Option 1: Manual Spatial Split (Recommended)

Create separate dataset instances with different bounding boxes:

from torchgeo.datasets import HabitAlp2
from torchgeo.datamodules import HabitAlp2DataModule

# Define geographic splits manually
train_bounds = BoundingBox(minx, maxx_train, miny, maxy, minz, maxz)
val_bounds = BoundingBox(minx_val, maxx, miny, maxy, minz, maxz)

# Create separate datasets
train_ds = HabitAlp2(root, year='2013', bounds=train_bounds)
val_ds = HabitAlp2(root, year='2013', bounds=val_bounds)

# Note: You'll need to create a custom datamodule or sampler

Option 2: Accept the Limitation

For exploratory work or when you only care about per-pixel accuracy:

  • Use the datamodule as-is
  • Be aware validation metrics are optimistic
  • Test on a separate geographic region or time period if available

Option 3: Use Temporal Splits

Since the dataset has multiple years (2003, 2013, 2020):

# Train on 2013, validate on 2020
train_dm = HabitAlp2DataModule(root, year='2013', task='segmentation')
val_dm = HabitAlp2DataModule(root, year='2020', task='segmentation')

# Note: Class distributions may differ between years

Data Shape Reference

Segmentation Task

  • Image: (batch, channels, height, width)
  • Mask: (batch, height, width) with values 0-23

Change Detection Task

  • Image: (batch, 2, channels, height, width) - bi-temporal stack
  • Mask: (batch, 1, height, width) with values 0-8

Available Bands by Year

Band Type 2003 2013 2020
RGB
NIR
Terrain

Tips & Best Practices

  1. Start with RGB only for initial experiments
  2. Use patch_size=256 for most modern GPUs
  3. Adjust length based on epoch duration needs
  4. For change detection, RGB bands are available for all pairs
  5. Terrain layers can significantly improve segmentation but increase model complexity
  6. Consider the split limitation when interpreting validation metrics
  7. For production, implement proper spatial cross-validation

Citation

@article{habitalp2,
  title={HabitAlp2: A large-scale dataset for habitat mapping in the Alps},
  year={2024},
  doi={10.48550/arXiv.2511.00073}
}

Questions or Issues?

For dataset-specific issues, refer to the TorchGeo documentation or open an issue on the repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment