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)
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 = datasetImpact:
- 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).
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# 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
)# 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']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# 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']# Semantic segmentation
python train.py fit --config config_segmentation.yaml
# Change detection
python train.py fit --config config_change_detection.yamlThe 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
The HabitAlp2 dataset consists of single large GeoTIFFs covering the entire study area. The current implementation cannot spatially partition the data because:
- It uses a single GeoDataset instance for all splits
- Random sampling draws patches from the entire area for all splits
- There's no built-in mechanism to create geographic train/val/test regions
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 samplerFor 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
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- Image:
(batch, channels, height, width) - Mask:
(batch, height, width)with values 0-23
- Image:
(batch, 2, channels, height, width)- bi-temporal stack - Mask:
(batch, 1, height, width)with values 0-8
| Band Type | 2003 | 2013 | 2020 |
|---|---|---|---|
| RGB | ✓ | ✓ | ✓ |
| NIR | ✗ | ✓ | ✓ |
| Terrain | ✗ | ✓ | ✓ |
- Start with RGB only for initial experiments
- Use patch_size=256 for most modern GPUs
- Adjust length based on epoch duration needs
- For change detection, RGB bands are available for all pairs
- Terrain layers can significantly improve segmentation but increase model complexity
- Consider the split limitation when interpreting validation metrics
- For production, implement proper spatial cross-validation
@article{habitalp2,
title={HabitAlp2: A large-scale dataset for habitat mapping in the Alps},
year={2024},
doi={10.48550/arXiv.2511.00073}
}For dataset-specific issues, refer to the TorchGeo documentation or open an issue on the repository.