Skip to content

Contribute a calibration area

roofclf, the per-building classifier that supplies most of the evidence atlas's Best estimate (83% of it, currently), is only as good as the ground truth it is fit on: a handful of small, hand-picked, exhaustively mapped neighbourhoods ("calibration quadrats") scattered across Pakistan. More quadrats, in landscapes the current set does not yet cover, is the single most direct way to make this project's numbers more defensible. No model change, no retraining infrastructure, no GPU -- just OpenStreetMap mapping, done to one specific, strict rule.

This page is the fast path in. For the exhaustive rules (tagging conventions, imagery dating, QA, all six landscape strata), see the full mapping protocol; this page is the on-ramp to it.

Why this matters, concretely

A quadrat's own building density decides which national cells it can calibrate. As of 2026-08-20, the sparsest quadrat actually feeding roofclf's coverage-ratio/area-recall correction sits at 124 buildings/km2 -- but 13.5% of the buildings that correction is applied to nationally sit sparser than that (median around 89 buildings/km2). That gap used to be far worse: as measured 2026-08-16, the sparsest supporting quadrat was 872 buildings/km2 and 84% of buildings sat below it, pricing 54% of the whole published Best estimate with a correction fit on much denser ground than it was applied to. See Calibration density mismatch for the full before/after.

One new, well-mapped quadrat in the right density range moves this measurably. That is what the candidates below are for.

Start here: eight ready-made candidates

Rather than pick a location from scratch, eight candidate squares are already screened by building density, spread across Punjab, Sindh, Khyber Pakhtunkhwa and Balochistan, and confirmed clear of every existing quadrat:

candidate province district source-grid density (bldg/km2)
rajanpur_gap_calib_2km Punjab Rajanpur 54.4
kasur_gap_calib_2km Punjab Kasur 124.4
khairpur_gap_calib_2km Sindh Khairpur 55.3
jacobabad_gap_calib_2km Sindh Jacobabad 107.9
deraismailkhan_gap_calib_2km Khyber Pakhtunkhwa Dera Ismail Khan 63.2
haripur_gap_calib_2km Khyber Pakhtunkhwa Haripur 102.1
pishin_gap_calib_2km Balochistan Pishin 53.8
mastung_gap_calib_2km Balochistan Mastung 118.8

Files: data/labels/candidate_quadrats/<name>_candidate.geojson (one each) and density_gap_candidates_combined.geojson (all eight in one layer). Full rationale for why these eight, this density band, and these locations specifically: Calibration boxes log, Box 19.

Read each candidate's own note property before doing anything else with it. Every one says the same thing: this is a coarse regional screen, not a verified settlement. The point (lat, lon) is the centroid of a ~100 km2 national grid cell whose average density falls in the target gap -- it is not a promise that panels, or even a proper settlement, sit at that exact point. Box 18's own three published quadrats moved from source-grid densities of 549/250/150 buildings/km2 to actual, own-measured densities of 895/237/146 once mapped -- expect something similar here. Picking your own location instead of one of these eight is equally welcome, as long as it targets a building density under about 125 buildings/km2 (the current gap) and is not already inside an existing quadrat -- scripts/new_calibration_quadrat.py (below) checks the second condition for you.

The five steps

1. Check the imagery, before anything else

There is no automated check for this anywhere in the pipeline -- it is a manual, visual step, and it is the single most common reason a candidate gets dropped. Three of Box 18's six original candidates (Mardan, Shikarpur, Sialkot District) never got mapped for exactly this reason.

Open the candidate box in JOSM (or Esri World Imagery Wayback, or Google Earth Pro's historical slider) and check:

  • Is there high-resolution imagery at all over this box?
  • How recent is it? PV in Pakistan is being installed fast; imagery more than about a year old will under-represent what is actually there today, and a quadrat mapped against it reads as "the model overcounts" when it is really just seeing newer panels. See Calibration imagery dating for why this specifically, not just general staleness, matters.
  • Does the box actually sit on a real settlement, or does it need nudging? The candidate point is a coarse grid-cell centroid, not a surveyed location (see above).

If the imagery is too old or the box lands somewhere with nothing to map (open desert, empty of any built-up area), drop it and try another candidate, or pick your own location in the same density band. This is a legitimate, expected outcome, not a failure -- it is exactly what happened to half of Box 18's screen.

If the imagery looks good but the box needs to move onto the actual settlement, draw a better boundary in JOSM around it (File -> Save As -> GeoJSON) instead of using the candidate square as-is. Any closed shape works; it does not have to stay a square.

2. Preview it

python scripts/new_calibration_quadrat.py --dry-run \
    --name rajanpur_gap --lat 28.558966 --lon 69.790516 --side-m 2000

or, if you drew your own boundary in JOSM:

python scripts/new_calibration_quadrat.py --dry-run \
    --name rajanpur_gap --geojson ~/drawn/rajanpur_gap.geojson

--dry-run prints the geometry (area, bounding box, whether it fits inside one 2.24 km training-chip window) and the overlap check against every existing quadrat, without writing or fetching anything. Fix anything it flags before continuing -- most commonly an unexpected overlap, which the next step refuses to proceed past without --allow-overlap.

3. Register it

Drop --dry-run to actually write the boundary and pull the current OpenStreetMap solar features inside it:

python scripts/new_calibration_quadrat.py \
    --name rajanpur_gap --lat 28.558966 --lon 69.790516 --side-m 2000

This writes data/labels/rajanpur_gap_calib_2km_boundary.geojson (+ .parquet) and rajanpur_gap_calib_2km_overpass_solar.parquet, and prints a ground-truth profile: size distribution against the 400 m2 detection floor, rooftop/ground-mount split, and roofclf.packing_density. A fresh box is never Rule-1 complete just because this step ran. Registering the boundary and pulling whatever OSM already has is step zero, not completeness -- see step 4.

4. Map every visible installation

This is the actual mapping work, and the one rule that overrides everything else:

Rule 1: completeness beats coverage. A quadrat is only usable once every visible panel inside it, as of the imagery you mapped against is in OpenStreetMap, down to the smallest rooftop unit. A half-mapped quadrat is worse than an unmapped one -- it silently teaches the calibration that the model overcounts. If you cannot finish it, say so; it gets excluded, no harm done.

Trace panel area only (never the whole roof), one polygon per contiguous panel group, tag with power=generator / generator:source=solar / generator:method=photovoltaic / location=roof (omit for ground-mounted). The full protocol has the complete tagging reference, what counts as PV (solar pumps, yes; water heaters, no), and the common failure modes worth reading before you start (mapping only the obvious installations, choosing a quadrat because it already looks solar-heavy, tracing roofs instead of panels).

A second mapper independently sweeping the same quadrat is part of the deliverable, not an optional extra -- it is what turns "I mapped everything" into a checked completeness declaration rather than one person's confidence.

5. Declare it and hand it off

Record, next to the boundary: mapper name(s), completion date, the imagery layer and its best-known capture date, installation count, and how many the second pass added. This is what turns a registered box into a Rule-1 quadrat ready to enter the next roofclf refit -- roofclf.discover_quadrats picks up any boundary + mapped-solar pair automatically, but nothing folds a quadrat into a fit without a human completeness declaration first (see the national workflow's random-cell validation note for why this matters as much on the deployment side as on the mapping side).

Open a pull request, or hand the boundary + register row to whoever maintains this project's data/labels/ -- the same place every quadrat in the current set came from.

What happens next

Once a quadrat is Rule-1 and its density sits inside the target gap (or genuinely below the current calibrated domain floor, currently 48.5 buildings/km2 -- a different, harder thing to move safely: a quadrat only lowers that floor if its own measured density is below it, not its surrounding region's average, and several attempts at this have gone wrong in non-obvious ways -- see Calibration quadrats before attempting it), it is included in the next earthpv roof-classifier refit, rescored nationally, and folded into the next evidence-atlas rebuild. The most recent example of exactly this cycle -- three quadrats declared Rule-1, folded into a refit, and moving the published headline number the same day -- is Box 18.