A command-line tool for detecting elevation changes between Digital Terrain Models (DTMs). Built for geotechnical monitoring workflows—subsidence tracking, erosion detection, cut/fill analysis.
Requires Python 3.13+. Install with uv (recommended) or pip:
git clone <repository-url>
cd dtm-differ
uv pip install -e .# Basic usage
dtm-differ run --a before.tif --b after.tif --out output/
# Custom movement thresholds (meters)
dtm-differ run --a before.tif --b after.tif --out output/ --thresholds "0.5,2.0,5.0"
# Coastal monitoring: mask out sea/water areas
dtm-differ run --a before.tif --b after.tif --out output/ --min-elevation 2.0
# Focus on specific elevation range (e.g., cliff zone 2-80m)
dtm-differ run --a before.tif --b after.tif --out output/ --min-elevation 2.0 --max-elevation 80.0| Option | Default | Description |
|---|---|---|
--a, --b |
required | Input DTM paths (GeoTIFF) |
--out |
required | Output directory |
--thresholds |
1.0,3.0,6.0 |
Green/amber/red thresholds in meters |
--resample |
bilinear |
Resampling method (nearest, bilinear) |
--align |
to-a |
Reference grid (to-a, to-b) |
--min-elevation |
None |
Exclude areas below this elevation (meters) |
--max-elevation |
None |
Exclude areas above this elevation (meters) |
--uncertainty |
constant |
Uncertainty mode (constant, none) |
--sigma-a, --sigma-b |
0.5 |
DEM vertical uncertainty (1σ, meters) |
--sigma-coreg |
0.3 |
Co-registration uncertainty (1σ, meters) |
--k-sigma |
1.96 |
Significance threshold (~95% confidence) |
output/
├── map_layers/
│ ├── diff.tif # Raw difference (A - B)
│ ├── elevation_change.tif # Difference with nodata handling
│ ├── change_magnitude.tif # Absolute change
│ ├── change_direction.tif # -1 (subsidence), 0, +1 (uplift)
│ ├── slope_degrees.tif # Slope from DEM A
│ ├── movement_rank.tif # 0=below threshold, 1=green, 2=amber, 3=red
│ ├── sigma_dh.tif # Combined uncertainty (if enabled)
│ ├── z_score.tif # Significance score (if enabled)
│ └── within_noise_mask.tif # Areas below detection threshold
└── reports/
├── report.html # Visual summary
└── metrics.json # Machine-readable QA metrics
from pathlib import Path
from uuid import uuid4
from dtm_differ.db import Database
from dtm_differ.pipeline import run_pipeline
from dtm_differ.pipeline.types import ProcessingConfig
db = Database("jobs.sqlite")
db.initialise()
job_id = str(uuid4())
db.create_job(job_id)
result = run_pipeline(
db=db,
job_id=job_id,
a_path=Path("before.tif"),
b_path=Path("after.tif"),
out_dir=Path("output/"),
config=ProcessingConfig(t_green=1.0, t_amber=3.0, t_red=6.0),
)Changes are classified by absolute magnitude:
| Rank | Label | Default Range |
|---|---|---|
| 0 | Below threshold | < 1.0 m |
| 1 | Green | 1.0 – 3.0 m |
| 2 | Amber | 3.0 – 6.0 m |
| 3 | Red | ≥ 6.0 m |
Thresholds are inclusive on the lower bound: a 3.0m change is amber, not green.
- Format: GeoTIFF with valid CRS
- Units: Thresholds assume meters—verify your vertical units match
- Memory: ~8× input file size at peak (loaded into memory)
The tool auto-reprojects mismatched CRS and resamples different grid alignments.
uv pip install -e ".[test]"
pytest src/tests/I needed a repeatable way to compare survey DEMs for a mining monitoring project. Existing tools either required a full GIS setup or didn't handle uncertainty propagation. This fills the gap: a focused CLI that produces defensible outputs with confidence intervals.
See docs/methodology.md for technical details on the difference calculation, slope algorithm, and uncertainty propagation.