Skip to content

Quickstart

In five minutes you will build a small, labeled dataset of building footprints over Amsterdam’s Eastern Docklands: Esri aerial imagery at about 0.36 m per pixel, OpenStreetMap building polygons as labels, cut into 256 × 256 patches with a leakage-safe train/val/test split.

You need Python 3.10+ and an internet connection. Everything below is real output from mapcv.

  1. Terminal window
    pip install mapcv

    Check it worked with mapcv --version. More options: Installation.

  2. Make a working folder and download the example building outlines (967 OpenStreetMap polygons, © OpenStreetMap contributors, ODbL):

    Terminal window
    mkdir buildings && cd buildings
    curl -LO https://raw.githubusercontent.com/tahamukhtar20/mapcv/main/examples/quickstart/buildings.geojson

    Any polygon GeoJSON or KML in longitude/latitude, or a GeoPackage, Shapefile or GeoParquet file in any CRS, works the same way; see Prepare labels.

  3. mapcv init asks a few questions in four steps and writes mapcv.yaml. Type the answers shown after the prompts below; press Enter for the others to accept the default:

    Terminal window
    $ mapcv init
    ╭─ mapcv init ─────────────────────────────────────────────────────────────────────────╮
    │ Answer a few questions to get a working config. Press Enter to accept a default. │
    │ Change detection and regression need before/after pairs or a raster of values: start │
    │ from mapcv init --template change or --template regression. │
    ╰──────────────────────────────────────────────────────────────────────────────────────╯
    1/4 Imagery
    esri Esri World Imagery — sub-metre RGB
    sentinel2 Sentinel-2 L2A — open 10 m multispectral (EOPF Zarr)
    custom your own XYZ tile URL
    geotiff your own GeoTIFF / COG file or URL
    gee Google Earth Engine — Sentinel-2, Landsat, NAIP or any asset
    Imagery [esri/sentinel2/custom/geotiff/gee] (esri):
    2/4 Area
    Area: a bounding box west,south,east,north or a vector file (.geojson, .kml, .gpkg, .shp,
    .parquet): 4.9375,52.3725,4.9515,52.3780
    zoom pixel size
    15 2.92 m
    16 1.46 m
    17 0.73 m
    18 0.36 m
    19 0.18 m
    Zoom (17): 18
    3/4 Labels
    Label file (.geojson, .kml, .gpkg, .shp, .parquet, or a .tif label raster; blank for an
    image-only dataset): buildings.geojson
    field example values
    class building
    building house, apartments, houseboat, yes, retail
    Which field holds the class? (blank = every polygon is class 1) (): class
    segmentation a class mask per patch
    detection a box per object (COCO and YOLO)
    instance a mask per object (COCO RLE, optional instance-ID PNG)
    classification a label (or set of labels) per patch, as a CSV
    Task [segmentation/detection/instance/classification] (segmentation):
    4/4 Patches and output
    Patch size in pixels (256):
    Output folder (./dataset):
    Split into train/val/test (spatial, no leakage)? [y/n] (y):
    ✓ Wrote mapcv.yaml
    Next
    1. See what it will cost: mapcv plan mapcv.yaml
    2. Build the dataset: mapcv generate mapcv.yaml
    Imagery terms: https://github.com/tahamukhtar20/mapcv/blob/main/PROVIDERS.md

    The area can also be a vector file (.geojson, .kml, .gpkg, .shp or .parquet): mapcv then uses the extent of its polygons and offers the same file as labels. The result is a short, commented YAML file you can edit and commit:

    mapcv.yaml
    # mapcv config - docs: https://tahamukhtar20.github.io/mapcv/reference/configuration/
    # Check the cost first with `mapcv plan <this file>`, then run `mapcv generate <this file>`.
    # Imagery sources, their licenses and credit lines: https://github.com/tahamukhtar20/mapcv/blob/main/PROVIDERS.md
    region:
    west: 4.937500
    south: 52.372500
    east: 4.951500
    north: 52.378000
    imagery:
    type: xyz
    zoom: 18
    source: esri_satellite
    max_connections: 4 # keep requests modest
    labels:
    path: 'buildings.geojson'
    label_field: class
    sampler:
    patch_size: 256
    stride: 0 # 0 = no overlap
    mode: grid
    edge_strategy: pad
    writer:
    staging_dir: './dataset'
    image_format: png
    split:
    strategy: spatial
    test_ratio: 0.20
    val_ratio: 0.10

    Relative paths are resolved against the folder that contains mapcv.yaml, so the config works from any directory.

  4. plan reads the labels and does the tile math, but downloads nothing:

    Terminal window
    $ mapcv plan mapcv.yaml
    ╭─ Plan for mapcv.yaml ────────────────────────────────────────────────────────────────╮
    │ Region 4.9375, 52.3725 → 4.9515, 52.378 (≈ 1.0 × 0.6 km) │
    │ Imagery esri_satellite · zoom 18 (≈ 0.36 m/px) │
    │ Raster 2,816 × 1,792 px · 77 tiles (≈ 1.9 MB to download) │
    │ Labels buildings.geojson · 967 polygons · classes: building → 1 │
    │ Patches ≈ 77 × 256 px (grid, stride 256) │
    │ Output dataset · png (≈ 8.6 MB) │
    │ Split spatial · test 0.2 · val 0.1 │
    │ Memory ≈ 28.8 MB per chunk │
    ╰──────────────────────────────────────────────────────────────────────────────────────╯
    Looks right? Build it with mapcv generate mapcv.yaml

    Warnings appear under the panel, for example when label features are skipped or no polygon falls inside the region. Sizes are estimates.

  5. Terminal window
    $ mapcv generate mapcv.yaml
    ╭─ Plan for mapcv.yaml ─────────────────── (same panel as above) ──────────────────────╮
    ╰──────────────────────────────────────────────────────────────────────────────────────╯
    Reading imagery and writing patches ━━━━━━━━━━━━━━━ 2/2 chunks • 0:00:21 • eta 0:00:00
    ╭─ Dataset ready ──────────────────────────────────────────────────────────────────────╮
    │ Patches 77 │
    │ Source xyz · esri_satellite │
    │ Shape 256×256×3 uint8 │
    │ Tiles 77 fetched · 0 failed │
    │ Splits train 52 (68%) · val 8 (10%) · test 17 (22%) │
    │ Time 21s │
    │ Files dataset/ (Images/, Masks/, manifest.json, splits/, patches.geojson) │
    ╰──────────────────────────────────────────────────────────────────────────────────────╯
    class id pixels
    background 0 81.0%
    building 1 19.0%
    Next
    • Inspect it: mapcv info dataset
    • Re-split it: mapcv split dataset --strategy spatial
    • Band stats: mapcv stats dataset
    • Train on it: https://tahamukhtar20.github.io/mapcv/guides/use-your-dataset/

    If the run is interrupted, run the same command again: finished chunks are kept and it resumes. Jobs over 20,000 tiles, 5 GB or 1,000,000 patches ask for confirmation first; pass --yes to skip the prompt.

  6. Terminal window
    $ mapcv info dataset
    ╭─ dataset ────────────────────────────────────────────────────────────────────────────╮
    │ Task segmentation │
    │ Source xyz · esri_satellite │
    │ Bands red, green, blue │
    │ Patches 77 · 256×256×3 uint8 │
    │ CRS EPSG:3857 │
    │ Ignore mask value 255 marks pixels without imagery │
    │ Splits train 52 (68%) · val 8 (10%) · test 17 (22%) │
    │ Manifest version 3 │
    ╰──────────────────────────────────────────────────────────────────────────────────────╯
    class id pixels
    background 0 81.0%
    building 1 19.0%

Four aerial patches over Amsterdam and the same patches with building masks

Patches and masks from this dataset’s region. Shown with Dutch open aerial imagery (Beeldmateriaal Nederland, CC BY 4.0) over the same area; with the tutorial’s Esri imagery your patches look alike.

  • Directorybuildings/
    • mapcv.yaml
    • buildings.geojson
    • Directorydataset/
      • DirectoryImages/ 77 RGB PNG patches, 256 × 256
        • patch_0000000.png
        • …
      • DirectoryMasks/ 77 single-channel PNGs: 0 = background, 1 = building
        • patch_0000000.png
        • …
      • manifest.json
      • Directorysplits/
        • train.txt
        • val.txt
        • test.txt
        • split.json settings and counts of this split
        • Directory10/
          • …
        • Directory20/
          • …
        • Directory30/ labeled/unlabeled subsets of train
          • …
      • patches.geojson footprints of the patches, to open in QGIS

Load a pair to check that the mask lines up:

import numpy as np
from PIL import Image
name = open("dataset/splits/train.txt").read().split()[0]
image = np.asarray(Image.open(f"dataset/Images/{name}")) # (256, 256, 3) uint8
mask = np.asarray(Image.open(f"dataset/Masks/{name}")) # (256, 256) uint8, values 0/1
print(name, image.shape, mask.shape, np.unique(mask))