A robust Python toolkit for detecting and quantifying terrain changes using multi-temporal LiDAR point cloud data. Developed for the Norwegian Water Resources and Energy Directorate (NVE) as a scalable alternative to raster-based DTM differencing.
- Automated Discovery: Tools to scan and index complex directory structures from national data providers (hoydedata.no).
- Targeted Analysis: Support for polygon-based clipping to focus processing on specific regions of interest (e.g., river corridors).
- ICP Registration: Precise spatial alignment of multi-temporal epochs.
- Robust Algorithms:
- M3C2: Accurate, normal-oriented 3D change detection (wraps the py4dgeo library).
- DoD & C2C: Complementary methods for analysis and validation.
- Scalable Architecture: Support for out-of-core streaming and spatial tiling to process massive national-scale datasets.
- Flexible Configuration: One canonical
config/default.yaml, repeatable--setoverrides, and optional small override presets for common dataset types. - Point Cloud Native: Computes changes directly on 3D point clouds (LAS/LAZ) to preserve fine terrain details.
terrain-change-detection-pc/
├── config/ # Canonical default config + small override presets
├── data/ # Data directory (raw inputs & outputs)
├── docs/ # Documentation and guides
├── scripts/ # Entry point scripts (workflow, generators)
├── src/ # Source code
│ └── terrain_change_detection/
│ ├── preprocessing/ # Discovery, loading, clipping
│ ├── alignment/ # ICP and coarse registration
│ ├── detection/ # M3C2 (py4dgeo wrapper), DoD, C2C
│ ├── acceleration/ # GPU and parallel processing
│ ├── visualization/ # Plotting and 3D rendering
│ └── utils/ # Configuration and helpers
└── tests/ # Pytest suite
Requires Python 3.13+. We recommend using uv for fast dependency management.
# Clone the repository
git clone https://github.com/NVE/terrain-change-detection-pc.git
cd terrain-change-detection-pc
# Install dependencies
uv syncFor managed devices (without admin rights) using conda/Anaconda, do the following:
# Clone the repository
git clone https://github.com/NVE/terrain-change-detection-pc.git
cd terrain-change-detection-pc
conda create --name myenv python=3.13
conda activate myenv
pip install uv
uv pip install -r pyproject.toml
# Further installs with uv pip install <package_name> as needed-
Generate Test Data (Optional) To verify installation, generate a synthetic dataset with known changes:
uv run scripts/generate_synthetic_laz.py
or
# conda activate myenv python scripts/generate_synthetic_laz.py -
Run Processing Execute the full pipeline (Discovery → Alignment → Detection → Visualization):
uv run scripts/run_workflow.py --config config/profiles/synthetic.yaml
or
# conda activate myenv python scripts/run_workflow.py --config config/profiles/synthetic.yaml
The toolkit always starts from config/default.yaml. You can then:
- add one or more small override YAMLs with
--config - override individual values with
--set section.key=value
Common override presets live in config/profiles/.
| Config | Description |
|---|---|
default.yaml |
Full canonical config with all runtime defaults. |
profiles/drone.yaml |
Minimal override preset for high-density drone LiDAR data. |
profiles/large_scale.yaml |
Minimal override preset for large out-of-core runs. |
default_clipped.yaml |
Minimal override that enables polygon clipping. |
Run with CLI overrides only:
uv run scripts/run_workflow.py --set paths.base_dir=data --set discovery.source_type=drone --area-name Jeksla --years 2024 2025Run with an override preset:
uv run scripts/run_workflow.py --config config/profiles/drone.yaml --set paths.base_dir=data --area-name Jeksla --years 2024 2025You can combine both styles:
uv run scripts/run_workflow.py --config config/profiles/drone.yaml --set alignment.enabled=falseRun a specific area and time period:
uv run scripts/run_workflow.py --config config/profiles/drone.yaml --area-name Ristvassdrag --years 2017 2025The toolkit expects data organized by "Area" and "Time Period".
Standard Structure (hoydedata.no):
data/raw/
└── my_area_name/
├── 2015/
│ └── data/
│ └── file1.laz
└── 2020/
└── data/
└── file2.laz
Drone Structure:
Set discovery.source_type: drone in config.
data/raw/
└── my_drone_site/
├── 2023-05-01/
│ └── flight_line.laz
└── 2023-09-15/
└── flight_line.laz
- Best Practice Guide: Guidance on what settings to start with, when to override them, and how to validate results.
- Best-Practice Evidence: Short summary of the repo runs and datasets used to support the guide.
- Configuration Guide: Detailed reference for all YAML parameters.
- Known Issues: Current limitations and workarounds.
- Changelog: History of changes and updates.
This project is licensed under the MIT License.
If you use this software in your research, please cite it using the metadata in CITATION.cff, or click the "Cite this repository" button on GitHub.