ZAsolar is a research codebase for detecting residential rooftop photovoltaic installations from high-resolution aerial imagery, with the goal of building a grid-aggregate panel data record of solar adoption across South Africa. Cape Town and Johannesburg are currently covered.
The detection stack is Mask R-CNN (ResNet-50 + FPN) + SAM 2.1 mask-prompt
refinement. The V1.4 validation framework treats grid-aggregate installation
inventory as the primary success metric, with per-polygon F1 retained as a
diagnostic. Install-date back-dating is handled by a sibling repo,
solar_backdating.
Primary benchmark: Johannesburg CBD, 25 grids, Vexcel 2024 aerial (~6.7 cm GSD), detector = V3-C-HN, post-proc = SAM 2.1 mask+box refinement.
| Channel | Metric | Result | Sample |
|---|---|---|---|
| Ch1 — stratified precision | P (V3-C, hit-table) | 0.749 [0.71, 0.78] | 25 grids × stratified roof samples |
| Ch3 — inventory accuracy | area F1 | 0.821 | JHB CBD 25-grid Vexcel |
| Ch3 — inventory accuracy | aggregate |A|/|B| | 0.992 | JHB CBD 25-grid Vexcel |
See docs/validation_strategy.md for the full
four-channel framework, what each channel does and does not certify, and the
known confounders (e.g. SSEG building geocoding mismatch, vintage gaps).
core/ shared modules (region_registry, postproc, models)
pipeline/ declarative dataset builder (V1.2 spec)
configs/ region / imagery / training / model registries
data/annotations/ Cape Town + Johannesburg ground truth (gitignored)
docs/ architecture.md, validation_strategy.md, workflows.md
scripts/
analysis/ benchmarks, audits, calibration sweeps
imagery/ tile download / preview / VRT
training/ COCO export, hard-negative export
annotations/ review GUI, SAM FN GUI, batch finalize
detect_and_evaluate.py geoai eval/dev entry (benchmarks + diagnostics)
detect_direct.py production census engine, stage 1 (raw detections)
finalize.py production census engine, stage 3: raw_detections -> predictions_metric.gpkg
train.py Mask R-CNN fine-tune
export_coco_dataset.py annotations -> COCO instance-segmentation dataset
Full structure: docs/architecture.md.
Workflow command sequences: docs/workflows.md.
# Environment (creates ./.venv from requirements.lock.txt)
./scripts/bootstrap_env.sh && source scripts/activate_env.sh
# Verify CUDA GPU + GIS deps
./scripts/check_env.sh
# Inference + eval on one grid (CUDA required; geoai eval/dev chain;
# current-best checkpoint: see configs/model_registry.yaml)
python detect_and_evaluate.py \
--grid-id G1688 \
--model-path <ckpt> \
--postproc-config configs/postproc/v4_canonical.json \
--force
# Production census engine (per-detection merge; see docs/workflows.md)
python detect_direct.py --grid-id <GRID> --region <region> --model-path <ckpt>
python finalize.py --input <out>/raw_detections.pkl --output-dir <out> \
--postproc-config configs/postproc/v4_canonical.json --merge-mode per-detection
# Benchmark (suite definitions: configs/benchmarks/post_train.yaml)
python scripts/analysis/run_benchmark.py --suite <suite_id>Large data (tiles, COCO datasets, model weights) lives outside the repo under
~/zasolar_data/. configs/datasets/regions.yaml is the authoritative
imagery-layer and model-run registry. Annotations sync via Dropbox; checkpoints
sync via RunPod S3.
Four orthogonal channels:
- Stratified precision — random stratified roof samples on the benchmark grids; certifies detection precision conditioned on roof type and target size.
- Exhaustive recall — clean GT (full panel inventory) on a small grid set; measures recall against an installation-merged reference.
- Plausibility — hex-aggregated detections vs admin-level installation counts (SSEG, kW calibration); used as a sanity check, not a benchmark.
- Opportunistic external — comparison with third-party datasets (e.g. Li GT for Cape Town) where vintage and coverage permit.
Task grid is the primary aggregation unit. Per-polygon F1 is diagnostic only.
The Tier-1 metric system uses area_aggregate_eval.py
(agg_F1 + pgF1 + bulk + sigma_Bw + log-sigma + RMSE + thru0_beta
- R²), with
sigma_Bwand RMSE as primary arbiters andbulk in [0.5, 2.0]as a sanity gate.
The pipeline is split into three deployable units. Two sibling repositories run
as plugins of this repo: they share this repo's .venv and import
core.region_registry / core.grid_utils (and, for back-dating,
core.annotation_loader) through a ZASOLAR_ROOT anchor. Each concern stays in
its own repo so it can be versioned and run independently.
solar_backdating(public) — install-date back-dating. For each detected footprint it walks historical Google Earth imagery (GEHistoricalImagery) to infer when the installation appeared. All temporal / install-date code lives here, not in this repo.solar_cls(private) — a post-hoc PV-vs-(solar-thermal / look-alike) chip classifier that suppresses detector false positives. The detector consumes its filtered output as a file path (--classifier-filtered-gpkg), never as a Python import.
Code: MIT.
Annotations and reviewed predictions: see
data/annotations/ANNOTATION_SPEC.md.