Skip to content

Use your own GeoTIFF

Drone and aerial orthophotos, commercial imagery and national mapping rasters usually come as GeoTIFF files. mapcv reads them directly, local or remote: patches are cut from the file’s own pixels, in the file’s CRS, at the file’s resolution. Nothing is resampled. This page walks through a local file and a public Cloud Optimized GeoTIFF (COG) URL.

No extra install is needed: pip install mapcv is enough.

  • Grid. Your WGS-84 region is reprojected into the file’s CRS and expanded outward to whole pixels of the file, then clipped to the raster. Every patch is a block of the file’s pixels, so it equals what rasterio reads from the same window.
  • Labels. Keep your labels in WGS-84 longitude/latitude, as for any mapcv dataset. mapcv reprojects them into the file’s CRS and burns them onto the same pixels. Never reproject labels yourself.
  • Bands and data type. The file’s bands and data type are kept. 8-bit RGB (or one gray band) can be written as PNG or JPG; everything else (16-bit, float, 2 or 4 and more bands) needs npy (bands-first) or tif (a georeferenced GeoTIFF), both in the file’s own data type.
  • NoData. Pixels equal to the file’s NoData value in every selected band, and the area outside the file, count as empty and become labels.ignore_index (255) in masks.
  1. Look at the file. The guided mapcv init asks for the path, opens it and prints what it found; answer geotiff to the first question:

    Terminal window
    $ mapcv init
    …
    GeoTIFF / COG path or URL: ortho/paris-2024.tif
    CRS EPSG:32631
    Size 12,000 × 9,000 px · 3 bands · uint8
    Pixel 0.1 × 0.1 CRS units
    NoData 0
    Overviews 4

    It then offers the whole file as the area (press Enter), asks for labels and writes a working config. To write a commented starting file instead, run mapcv init --template geotiff.

  2. Write the config. Relative paths are resolved against the config file’s folder:

    mapcv.yaml
    region: { west: 2.3330, south: 48.8560, east: 2.3400, north: 48.8600 }
    imagery:
    type: geotiff
    path: ortho/paris-2024.tif
    labels:
    path: buildings.geojson # lon/lat
    label_field: class
    sampler:
    patch_size: 256
    edge_strategy: drop
    max_empty_ratio: 0.2 # skip patches that are mostly NoData
    writer:
    staging_dir: ./dataset
    image_format: png # 8-bit RGB
    split:
    strategy: spatial
  3. Plan, then generate. mapcv plan opens the file’s header (no pixels), reports the CRS, the window on the file’s grid, the pixel size in metres and the expected patches, and warns if the region leaves the file:

    Terminal window
    $ mapcv plan mapcv.yaml
    ╭─ Plan for mapcv.yaml ────────────────────────────────────────────────────────────────────────────╮
    │ Region 2.333, 48.856 → 2.34, 48.86 (≈ 0.5 × 0.4 km) │
    │ Imagery GeoTIFF paris-2024.tif · EPSG:32631 · 3 bands uint8 (≈ 0.10 m/px) │
    │ Raster 5,174 × 4,492 px │
    │ Labels buildings.geojson · … polygons · classes: building → 1 │
    │ Patches ≈ 340 × 256 px (grid, stride 256) │
    │ Output dataset · png (≈ 36.8 MB) │
    │ Split spatial · test 0.2 · val 0.1 │
    │ Memory ≈ 53.0 MB per chunk │
    ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
    $ mapcv generate mapcv.yaml
  4. Inspect it. mapcv info dataset/ shows geotiff · paris-2024.tif, the band names (b1, b2, b3) and the CRS. The manifest’s sources[0] records the file, its grid and a fingerprint, so resuming into the same folder with a different file is refused.

A 4-band uint16 file (blue, green, red, near-infrared, for example) cannot be a PNG. Ask for npy, which keeps the band count and data type, and optionally pick or reorder bands with 1-based numbers:

imagery:
type: geotiff
path: ortho/multispectral.tif
bands: [3, 2, 1, 4] # red, green, blue, NIR, in this order
writer:
image_format: npy # patches are (4, H, W) uint16 arrays; tif writes them as GeoTIFFs

With the default png, a file that does not fit stops mapcv plan and mapcv generate with a message that says so. To get an 8-bit RGB PNG from a 4-band 8-bit file, select three bands (bands: [1, 2, 3]); 16-bit data has to stay npy (rescale it in your training code).

mapcv uses the NoData value stored in the file. If the file does not declare one, or declares the wrong one, set it in the config:

imagery:
type: geotiff
path: ortho/scene.tif
nodata: 0

Floating-point files also treat NaN and infinite values as empty.

If the file has overviews (check the Overviews line above), overview: 1 reads the first one, usually half the resolution, overview: 2 the next, and so on. The patches then use that coarser grid. mapcv does not build overviews or resample otherwise; to change the resolution by another factor, resample the file first (for example with gdalwarp).

A Cloud Optimized GeoTIFF stores its pixels in tiles with an index at the start, so mapcv can read only the tiles under your region with HTTP range requests; the rest of the file is never downloaded. Use the URL as the path:

region: { west: 2.3330, south: 48.8560, east: 2.3400, north: 48.8600 }
imagery:
type: geotiff
path: https://example.com/cogs/paris-2024.tif

A public object in S3 works as s3://bucket/key.tif (anonymous access, for buckets that allow it). One example is the Sentinel-2 L2A COGs of the Earth Search sentinel-cogs bucket; their 8-bit true-colour file is TCI.tif inside each scene folder, and the Earth Search catalogue lists the scenes. To read their spectral bands, let type: stac_cog find the scene and stack the bands for you.

Rules for remote files:

  • The server must support HTTP range requests; otherwise the error says so.
  • Only https:// and anonymous s3:// URLs are accepted. A URL with a username, password, query string or fragment is rejected, so signed (pre-signed, SAS) URLs and private buckets are not supported. http:// is accepted only for a local test server on localhost or 127.0.0.1.
  • Reads are cached in memory and retried on transient errors. A plain, non-tiled GeoTIFF over HTTP works too, but mapcv then has to fetch whole strips, so a COG is much cheaper.
  • Resuming checks the URL, the server’s ETag and the file size, so a file replaced on the server is noticed.
  • A remote read is bound by latency, not bandwidth. Every tile of the file that the region touches is one range request, so even a small region can take dozens of seconds, depending on how far away the server is and how quickly it answers. plan is quick because it reads only the header; generate is where the time goes. For a file you will read more than once, download it and point path at the local copy.

Files that cover the same place on the same pixel grid (two dates, optical and a DEM, a stack of indices) can be sampled together: list them under imagery, each with a name. Every patch then has one file per source, Images/<name>/patch_*.tif, all at the same position and with one shared mask.

imagery:
- {type: geotiff, name: before, path: city_2023.tif}
- {type: geotiff, name: after, path: city_2025.tif}
- {type: geotiff, name: dem, path: dem_2m.tif, bands: [1]}
writer: {staging_dir: ./dataset, image_format: tif}

The first file’s grid is the dataset’s grid. The others must be in the same CRS with pixels that are the same size or a whole number of them across (the 2 m DEM above, under 1 m imagery), sharing pixel corners with the first; coarser pixels are repeated, never interpolated. A pixel has imagery only where every file has it. See Several imagery sources for the rules.

Orthophotos and national mapping rasters are often delivered as many files, each covering part of the area. Give a glob pattern as the path and mapcv reads the matching files as one raster, without merging them first:

imagery:
type: geotiff
path: ortho/tiles/*.tif # or ortho/**/*.tif to include subfolders

mapcv plan shows the pattern and how many files it matched (*.tif (24 files)). The files have to be pieces of one grid: the same CRS, data type, band count, NoData value, pixel size, and pixel corners that line up. Otherwise the error names the file and what differs. Where tiles overlap, the first file in sorted path order that has data at a pixel wins, and a pixel that is NoData in one file is taken from the next. Gaps between tiles are empty, like the area outside a single file. Patches cut from a mosaic are byte-identical to patches cut from the same raster as one file.

Patterns work for local files only; a remote COG is always one URL. Files on different grids (another CRS or resolution) have to be warped onto one grid first, for example with gdalwarp.

  • Tiles of one grid can be read as a mosaic (above); files on different grids are not resampled to fit together.
  • The file’s CRS must be an EPSG code (UTM, Web Mercator, WGS-84 and national grids all are); files with a custom CRS or no CRS are reported with the reason, and need re-tagging first.
  • Supported compression: none, Deflate, LZW, PackBits, ZSTD, JPEG and WebP. A file that uses another one (LERC, JPEG 2000, …) fails when opened, naming it; convert it with gdal_translate -co COMPRESS=DEFLATE.
  • External .ovr overviews, internal mask bands and GCP-only georeferencing are not read.

See the configuration reference for every field and rule, and Coordinates and grids for how the window is chosen.