A research pipeline for large-scale urban tree crown detection and tree genus mapping using very-high-resolution multispectral aerial imagery and LiDAR data.
The multispectral aerial imagery and LiDAR-products provided by the LGL Open GeoData-Portal https://www.lgl-bw.de/Produkte/Open-Data/
Urban Tree Genera Mapping provides an end-to-end, research-oriented workflow to:
- Download and preprocess LGL Open GeoData (multi-spectral orthophotos & nDSM).
- Build 5-channel raster tiles (RGB + NIR + normalized height).
- Perform tree crown delineation and detection
- Predict tree genera using deep learning
- Apply a teacher–student learning strategy with human-in-the-loop curation
- Scale inference to statewide coverage
- Export results as GeoPackage for GIS analysis
The code accompanies an upcoming open dataset and scientific publication on regional-scale tree genera mapping in Baden-Württemberg, Germany.
Clone the repository:
git clone https://github.com/GIScience/tree-genera-mapping
cd tree-genera-mappingCreate and activate a Conda environment:
conda env create -f environment.yaml
conda activate map-tree-generaCreate a kernel to run notebooks scripts
python -m ipykernel install --user --name map-tree-genera --display-name "Python (tree-genera)"Download the pretrained model (5ch) with weights
mkdir -p cache/weights
cd cache/weights
wget https://huggingface.co/solo2307/urban-tree-genera/blob/main/yolo11l_tree_genus.pt
wget https://huggingface.co/solo2307/urban-tree-genera/resolve/main/yolo11l_tree.pt
cd ../..The same checkpoints are archived with the dataset at https://doi.org/10.11588/DATA/MKZPUY under weights/.
notebooks/01_demo_inference.ipynb provides a step-by-step demonstration of the genera predictions over data/samples 5-stack images.
- Download LGL products to Generate TileDataset for selected tile ids:
python tree_genera_mapping/scripts/fetch_tiles.py \
--tiles-gpkg data/tiles.gpkg \
--tile-ids data/tiles_split.txt \
--tmp-root cache/tmp_dir \
--output-dir cache/img_dir \
--mode RGBIH \
--norm-height global- Run pre-trained YOLOv11l model to detect and classify tree genus:
python tree_genera_mapping/scripts/predict_yolo.py \
--tiles-gpkg data/tiles.gpkg \
--images-dir cache/dataset_dir \
--model-path cache/yolov11l_tree_genus.pth \
--output-dir cache/predictions-
Data Preparation i. Detection dataset:
python -m tree_genera_mapping.scripts.build_dataset det \ --tiles-gpkg data/tiles.gpkg \ --bboxes-gpkg cache/curated_annotations.gpkg \ --images-dir cache/img_dir \ --output-dir cache/data \ --mode rgbih \ --tile-id-col tile_id \ --label-col top1_class \ --classes-csv data/genera_labels.csv \ --unknown-class skip \ --size 640 \ --overlap 0.2 \ --tile-split-table data/tiles_split_city.txt \ --subtile-split-table data/subtiles_ids.txt \ --include-empty-tiles \ --plain-tiffNote:
yolo_train.pyexpects plain TIFF images (Non-GeoTIFF). If your source imagery is stored as GeoTIFF, run the dataset builder with the --plain-tiff flag so that geospatial metadata is removed during chip generation.ii. Classification dataset (crown-centred genus patches):
python -m tree_genera_mapping.scripts.build_dataset cls \ --tiles-gpkg data/tiles.gpkg \ --genus-labels-csv /greehill_genera.csv \ --split-csv data/greehill_genera_split.csv \ --images-dir cache/img_dir \ --output-dir cache/patches_dir \ --mode rgbih \ --class-col genus \ --tile-id-col tile_id \ --labels-tile-col tile_id \ --id-col tree_id \ --crop-mode bbox \ --bbox-col bbox \ --patch-size 128
-
Teacher-Ensemble i. Train Faster R-CNN model for Tree Detection
python -m tree_genera_mapping.dl.detection.tree_train \
--dataset-root cache/data \
--save-dir cache/models/frcnn_tree \
--backbone resnet101 --in-channels 5 --num-classes 2 \
--epochs 100 --batch-size 8 --lr 5e-4 --weight-decay 0.01 \
--pretrained-backbone --seed 42ii. Train Genus Classifier(ResNet)
python -m tree_genera_mapping.dl.classification.genus_train \
--images-dir cache/patches_dir \
--labels-csv data/genera_labels.csv \
--out-dir cache/models/resnet101_5ch \
--experiment image_only \
--backbone resnet101 --in-channels 5 --img-size 128 \
--epochs 50 --batch-size 32 --lr 5e-4 --weight-decay 0.01 \
--loss ce --early-stop-monitor val_loss --early-stop-patience 10 --seed 42iii. Teacher-ensemble inference to generate pseudo-labels:
python -m tree_genera_mapping.scripts.predict_teacher \
--tile-dir cache/img_dir \
--ckpt-paths cache/models/frcnn_tree/best.pt \
cache/models/resnet101_5ch/image_only_best.pt \
--output-dir cache/pseudo_labels \
--patch-size 640 --image-patch-size 128 --stride 512 --conf 0.3 --iou 0.5Pseudo-labels are then reviewed and corrected in QGIS before use — see docs/README.md.
- Train Ultralytics YOLO model for Genera Detection
python -m tree_genera_mapping.dl.detection.yolo_train \
--run-dir cache/models \
--run-name y11l_rgbih_genus_1024 \
--data conf/data_genera.yaml \
--model-size l --num-bands 5 \
--img-size 1024 --batch 16 --epochs 160 \
--optimizer AdamW --lr0 0.0005 --lrf 0.01 --cos-lr \
--weight-decay 0.01 --warmup-epochs 8 --patience 30- Evaluation
python -m tree_genera_mapping.dl.detection.yolo_eval \
--weights cache/weights/yolo11l_tree_genus.pt \
--data conf/data_genera.yaml \
--imgsz 1024 --conf 0.30 --iou 0.5 --save-cmFull pipeline documentation, data conventions and reproducibility notes are in docs/README.md.
| Task | Model Name | Modification | URL Link |
|---|---|---|---|
| Object Detection (tree + genus) | YOLO11l | 5-Channel Input | yolo11l_tree_genus.pt |
| Object Detection (tree) | YOLO11l | 5-Channel Input | yolo11l_tree.pt |
Both were trained at 1024 × 1024 on five-channel stacks. The genus model covers the ten classes listed in conf/data_genera.yaml and conf/data_tree.yaml; the one-class model is an auxiliary benchmark and was not used to assign genus labels in the released inventory.
This repository accompanies:
- Dataset:
https://doi.org/10.11588/DATA/MKZPUY
If you use this code or workflow, please cite the accompanying paper.
See CITATION.cff for details.

