The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the DeepPVMapper listing page.
Large-scale rooftop PV detection pipeline for France. Classifies IGN aerial tiles with InceptionV3, segments positive patches with FCN/DeepLab, and extracts panel characteristics (surface, tilt, azimuth, installed capacity) via pypvroof.
🗺️ Explore the map and results →
🎮 Try the interactive demo →
Work carried out by Gabriel Kasmi as part of his PhD at Mines Paris-PSL (2020–2024).

DeepPVMapper's detection registry is also exposed as a public MCP (Model Context Protocol) server over Streamable HTTP, so any MCP-compatible client (Claude, etc.) can query the 1.14M+ rooftop-solar detections directly via natural language: search detections, aggregate installed capacity by department, explore an area, track yearly deployment, and check data-quality signals.
https://zelhliylrlktnasircwp.supabase.co/functions/v1/mcp/mcpsupabase/functions/mcp (gh-pages branch)io.github.gabrielkasmi/deeppvmapperNo install required — add the endpoint as a custom connector in any MCP client.
Pre-computed detection results for French departments are available on Zenodo:
GDAL must be installed system-wide first, then via pip to match the running Python version:
GPU required. CUDA 11.8+, 8 GB VRAM minimum.
Download model weights and runtime data from Zenodo:
Model weights are also available on Hugging Face:
The training dataset (BDAPPV):
Fill in the source paths in config.yml before running:
| config key | what goes there |
|---|---|
source_images_dir | IGN JP2 tiles + dalles.shp index shapefile |
source_topo_dir | BDTOPO folder (BATIMENT.shp, ZONE_D_ACTIVITE_OU_D_INTERET.shp) |
source_commune_dir | folder containing communes-20210101.shp |
model_dir | folder containing model_bdappv_cls.pth and model_bdappv_seg.pth |
--count sets tiles per classification batch (default 16 — reduce if OOM):
--config points to an alternative config file (useful for RunPod deployments):
To process a subset of tiles (local testing), set tiles_list in config.yml:
Force a full rerun (wipe prior progress):
Four steps run sequentially inside main.py:
| step | what happens |
|---|---|
| Init | Builds per-department auxiliary files (buildings, plants, communes) into temp/. Skipped if already present — safe to rerun after a crash. |
| Classification | Tiles loaded fully in memory. InceptionV3 classifies 299×299 patches; positives saved as GeoTIFFs to temp/segmentation/. |
| Segmentation | FCN/DeepLab segments each positive patch. LAMB93 polygons extracted, sorted by tile, merged into pseudo-arrays. |
| Aggregation | pypvroof extracts tilt/azimuth/kWp per polygon. Building filter applied. Results written to outputs_dir. |
On success: temp/ is deleted automatically.
On crash: temp/ is kept. Rerun the same command to resume from where it stopped.
Written to outputs_dir (default: data/):
| file | description |
|---|---|
arrays_{dpt}.geojson | Detected PV polygons in WGS84 |
characteristics_{dpt}.csv | Per-installation registry: surface (m²), tilt (°), azimuth (°), kWp, city code, lat, lon |
aggregated_characteristics_{dpt}.csv | City-level aggregation: count, total kWp, avg surface, avg kWp |
arrays_characteristics_{dpt}.geojson | Polygons enriched with all characteristics |
Only residential-scale installations (1.7–36.1 kWp) located on buildings are retained.
| parameter | default | description |
|---|---|---|
temp_dir | temp | Working directory. Deleted on success, kept on crash. |
outputs_dir | data | Final outputs directory. |
cls_threshold | 0.4 | Classification confidence threshold |
cls_batch_size | 512 | Patches per GPU batch (classification) |
decode_workers | 3 | Concurrent JP2 decode processes feeding the GPU — tune to your real CPU quota, not host core count |
decode_stagger_s | 35 | Gap between initial decode submissions, to avoid lockstep bursty waits — rule of thumb: decode_time / decode_workers |
seg_threshold | 0.46 | Segmentation binarization threshold |
seg_batch_size | 64 | Images per GPU batch (segmentation) |
filter_building | True | Discard detections not on a building |
tilt_method | lut | pypvroof tilt method (lut or constant) |
azimuth_method | bounding-box | pypvroof azimuth method |
ic_method | clustered | pypvroof installed-capacity regression type |
tiles_list | (empty) | Optional tile subset for partial runs |
Contributions are welcome — both code (performance, new imagery sources, models, building filters) and registry corrections via the interactive map, no coding required.
See CONTRIBUTING.md for the contribution areas, setup instructions and workflow. Issues labelled good first issue are the best entry points.
GDAL is fragile to install, for two distinct reasons — and the fix below handles both.
GDAL binding must match the system libgdal version exactly. pip install GDAL fails to build, or segfaults at import, if its version differs from the system library.python3-gdal apt package (common on RunPod/cloud GPU images), that package bundles its own osgeo/, which takes priority over the pip-installed one in sys.path — and its .so is often broken, regardless of what pip installs.Run this in place of a plain pip install, e.g. right when deploying the pipeline, around the pip install -r requirements.txt step: