Skip to content

Configuration

A mapcv config is one YAML file with six top-level blocks, an optional task and, for task: detection, instance, classification or change, an optional block of the same name with that task’s settings. region, imagery, sampler and writer are required; task, labels, detection, instance, classification, change and split are optional. Unknown keys are errors, reported with their path (for example sampler.stide: Extra inputs are not permitted), so typos never pass silently. A key written twice in the same mapping is an error too (the message names the key and both lines), because YAML would otherwise let the last one win. A number field refuses a YAML boolean (patch_size: true, zoom: yes, test_ratio: on), which YAML 1.1 would otherwise turn into 1: write the number. Whole numbers are fine for decimal fields (max_empty_ratio: 1). Relative paths (region.path; path, files[].path and annotated_area of labels and of change.before/change.after; writer.staging_dir and a local imagery.path) are resolved against the folder that contains the config file, so a config works from any working directory. mapcv validate <file> checks a config; mapcv plan <file> also reads the labels and estimates the run.

task: segmentation # what the dataset is for: segmentation (default), detection, instance, classification, change or regression
region: { … } # where (required)
imagery: { … } # which pixels (required)
labels: { … } # which polygons or label raster (optional: omit for an image-only dataset)
detection: { … } # box settings (optional, task: detection only)
instance: { … } # mask settings (optional, task: instance only)
classification: { … } # label rule (optional, task: classification only)
change: { … } # change mask settings (optional, task: change only)
sampler: { … } # how to cut patches (required)
writer: { … } # where and how to write (required)
split: { … } # train/val/test lists (optional: omit to skip splitting)
region: { west: 4.9375, south: 52.3725, east: 4.9515, north: 52.3780 }
imagery:
type: xyz
zoom: 18
source: esri_satellite
max_connections: 4
labels:
path: buildings.geojson
label_field: class
sampler:
patch_size: 256
writer:
staging_dir: ./dataset
split:
strategy: spatial

What the dataset is for. It decides what each patch is annotated with and is recorded in the manifest, so a resumed run must use the same task.

Value Status Annotation
segmentation (default) supported A class mask per patch, from labels (none without labels: an image-only dataset)
detection supported A box per object, written as COCO JSON and YOLO labels; needs labels. Settings in the detection block
instance supported A binary mask per object (instance segmentation), written as COCO JSON with RLE masks and optionally an instance-ID PNG per patch; needs vector labels. Settings in the instance block
classification supported One label, or a set of labels, per patch, written as labels.csv and labels.json; needs labels (vector or raster). Settings in the classification block
change supported A before/after image pair and a binary change mask per patch, in LEVIR-CD’s A/, B/, label/ layout; needs two imagery sources and labels that mark the change, or two label sets to compare. Settings in the change block
regression supported A float32 target per pixel (canopy height, biomass, elevation …) read from a raster of values; needs labels.type: continuous. Targets are written as tif (default) or npy masks, NaN where there is no value
task: segmentation

Optional, and only allowed with task: detection (another task with a detection block is an error). Omit it for the defaults. One object is one label feature: a MultiPolygon is one object, and so is a feature that a patch edge cuts into pieces. Its box is the axis-aligned box of the part that is visible in the patch: inside the patch, inside the raster (not in padding) and over pixels with imagery. Holes do not change a box. Walkthrough: Object detection datasets; files: Dataset format.

Field Type Default Description
min_visible float, 0–1 0.3 Keep an object in a patch only if at least this fraction of its area is visible there. 0 keeps every visible part; 1 keeps only whole objects. An object too large for any patch to show this fraction is never kept, and mapcv warns about it
min_box_pixels float ≥ 0 2 Drop boxes narrower or shorter than this many pixels (slivers along patch edges)
formats list of coco, yolo [coco, yolo] Which annotation files to write. At least one; no duplicates
point_box_size float > 0 | null null Turn Point/MultiPoint features (not in KML) into square boxes of this many pixels, centred on the point. null: points are skipped with a warning, as for segmentation
task: detection
labels:
path: buildings.geojson
label_field: class
detection:
min_visible: 0.3
min_box_pixels: 2
formats: [coco, yolo]

Validation (with task: detection): labels is required; labels.ignore_index, labels.all_touched and writer.mask_format are mask settings and are rejected when set, as is writer.world_files with images that are not PNG or JPG; point_box_size needs point labels from GeoJSON, GeoPackage, Shapefile or GeoParquet (KML points are not read); sampler.pad_mode: reflect is rejected with edge_strategy: pad, because mirrored padding would show objects without boxes. sampler.min_label_ratio keeps a patch only when its boxes’ visible areas (overlaps counted once) cover at least that fraction of it. Detection writes no masks. A resumed run must use the same detection settings.

Optional, and only allowed with task: instance (another task with an instance block is an error). Omit it for the defaults. One instance is one label feature: a MultiPolygon is one instance, and so is a feature that a patch edge cuts into pieces. Its mask is the part of the feature that is visible in the patch: inside the patch, inside the raster (not in padding) and over pixels with imagery. Every instance is burned on its own with the same rasterizer and pixel rule as segmentation masks (labels.all_touched applies), so overlapping instances each keep their exact mask. Walkthrough: Instance segmentation datasets; files: Dataset format.

Field Type Default Description
min_visible float, 0–1 0.3 Keep an instance in a patch only if at least this fraction of its area is visible there. 0 keeps every visible part; 1 keeps only whole instances. A feature too large for any patch to show this fraction is never kept, and mapcv warns about it
min_area int ≥ 1 4 Drop instances whose mask has fewer pixels than this (slivers along patch edges, features smaller than a pixel)
id_mask bool false Also write one 16-bit instance-ID PNG per patch to masks/ (writer.mask_format then sets its format). A patch with more than 65,535 instances is an error
task: instance
labels:
path: buildings.geojson
label_field: class
instance:
min_visible: 0.3
min_area: 4
id_mask: false

Validation (with task: instance): labels is required and must be vector labels (a label raster has no objects; use task: segmentation); labels.ignore_index is rejected when set (instance masks only hold pixels that have imagery); writer.mask_format is rejected unless instance.id_mask is on; writer.world_files is rejected with images that are not PNG or JPG unless id_mask is on; sampler.pad_mode: reflect is rejected with edge_strategy: pad, because mirrored padding would show objects without masks. sampler.min_label_ratio keeps a patch only when its instances (overlaps counted once) cover at least that fraction of it. A resumed run must use the same instance settings and labels.all_touched.

Optional, and only allowed with task: classification (another task with a classification block is an error). Omit it for the defaults. A patch’s label comes from how much of it each class covers. Coverage is a class’s pixels divided by the patch’s valid pixels, counted on the very mask that segmentation would write for the same labels (the same rasterizer and pixel rule for polygons, labels.all_touched included; the same nearest-neighbour sampling for a label raster). A pixel is valid when it has imagery (inside the raster, not NoData or a failed tile) and is not labels.ignore_index; for a label raster that includes pixels outside the label raster, on its NoData and on labels.ignore_values. Background (mask value 0) is not a class and has no coverage. Where polygons overlap, the mask’s later-wins rule applies: the later feature’s class takes the shared pixels. Coverage is computed as pixels / valid pixels in 64-bit floating point. (With a label raster and labels.ignore_index: null, pixels without a label become background in the mask and so stay in the denominator; only pixels without imagery are left out.) Walkthrough: Patch classification datasets; files: Dataset format.

Field Type Default Description
mode single | multi single single: the patch gets the class with the largest coverage (a tie goes to the lowest class ID), if that class qualifies. multi: the patch gets every class that qualifies, in ascending class ID order (a multi-hot label)
min_fraction float, 0–1 0.0 A class qualifies when its coverage is at least this and above zero, so 0 means “any labeled pixel”. 0.5 with single is a strict majority rule
empty skip | background skip What happens to a patch no class qualifies for. skip drops it (it is not written); background keeps it with the label background (class ID 0). A patch without a single valid pixel is always dropped, because there is nothing to judge; sampler.max_empty_ratio drops patches that are mostly without imagery
task: classification
labels:
path: landuse.geojson
label_field: class
classification:
mode: multi
min_fraction: 0.1
empty: background

Validation (with task: classification): labels is required, vector or type: raster; writer.mask_format is rejected (no masks are written), as is writer.world_files with images that are not PNG or JPG; sampler.pad_mode: reflect is rejected with edge_strategy: pad, because the mirrored part of an edge patch would show imagery that no label counts; sampler.min_label_ratio greater than 0 is rejected with empty: background, because it would drop exactly the background patches (without background, it keeps its segmentation meaning: the labeled share of the whole patch must reach it). Class names must not hold control characters, and with mode: multi no whitespace, because a patch’s labels are joined with a space in labels.csv; with empty: background no class may be named background. A resumed run must use the same classification settings and labels.all_touched.

Optional, and only allowed with task: change (another task with a change block is an error). A change dataset needs imagery as a list of exactly two sources on one grid: the image before (first) and after (second). Each patch gets both images and a change mask, all on the before image’s grid. Files: Dataset format.

The mask comes from one of two places:

  • labels that mark what changed, vector or a label raster (type: raster): every labeled pixel is change. For a raster, pixels it does not label (outside it, its NoData, ignore_values) keep the ignore value.
  • change.before and change.after, two vector label sets (same fields as labels): a pixel changed where their masks differ, for example a building that appeared or was demolished. A set given as one path without label_field marks objects of one kind, and only their presence is compared. Sets that name classes (label_field, files with a class or label_field each, or osm) are compared class by class, so a change of class counts too: classes match by name. Without classes the names of both sets get one class map (IDs 1, 2, … in sorted order, or integer names as themselves; recorded as target.labels.classes in the manifest); with classes, give the same mapping in both. Both sets must name classes, or neither: to compare a plain file with a named set, write it as files: [{path: …, class: <name>}].
Field Type Default Description
change_value int, 1–255 1 Mask value of changed pixels; unchanged pixels are 0. Use 255 (with labels.ignore_index: null) for loaders that expect LEVIR-CD’s 0/255 masks
before, after vector labels — The two label sets to compare, instead of labels: a file (path), several (files) or OpenStreetMap (osm, queried over the region unless it sets bbox). Relative paths, annotated_area included, resolve against the config’s folder
task: change
imagery:
- {type: geotiff, name: before, path: city_2023.tif}
- {type: geotiff, name: after, path: city_2025.tif}
change:
before: {path: buildings_2023.geojson}
after: {path: buildings_2025.geojson}

Pixels without imagery in either image get the ignore value (labels.ignore_index, or the label sets’ shared ignore_index; default 255), as in segmentation masks. So do pixels outside either label set’s annotated_area: nothing was labeled there in that set, so whether the pixel changed is unknown, and it is neither change nor no change. For example, with the same buildings in both sets but before.annotated_area covering the west half, the east half is ignored, not change.

Validation (with task: change): exactly two imagery sources; either labels or both change.before and change.after, not both; the two label sets need the same ignore_index (not null when either has an annotated_area), both sets name classes or neither does, and sets that name classes have the same classes mapping or none; change_value must differ from the ignore value. A resumed run must use the same labels (their files’ hashes are recorded) and change_value.

The area of interest: a WGS-84 longitude/latitude bounding box, or polygons in a file.

Field Type Default Description
west float required Western longitude in degrees
south float required Southern latitude in degrees
east float required Eastern longitude in degrees
north float required Northern latitude in degrees
path path — Instead of the four bounds: a polygon file (any label format, in any CRS it states). The box becomes the polygons’ bounds, and only patches that overlap a polygon (with a positive area; touching an edge is not enough) are made, so an irregular area or several separate areas waste no patches. A relative path is resolved against the config file’s folder
name_field string — With path: the attribute naming each polygon’s region (polygons may share a name). Without it, polygons are regions 1, 2, … in file order. At most 255 distinct names
layer string — With path: the GeoPackage layer to read
region:
path: study_sites.geojson # several separate areas
name_field: site # each patch records its site: summary.region in the manifest
split:
strategy: region # whole sites to one split each: no leakage at all

With path, each patch records the region that covers most of it (summary.region in the manifest; a tie goes to the region listed first), windows are read around the polygons rather than across the gaps between them, and the manifest records the file’s hash (region.aoi_sha256), so a resumed run refuses edited polygons. mapcv plan estimates the patch count from the share of the box the polygons cover.

Validation: longitudes in −180…180, latitudes in −90…90, west < east and south < north; path replaces the four bounds (giving both is an error), must exist and hold at least one polygon when the config is loaded; name_field and layer need path. With XYZ imagery the box must also stay within ±85.0511° latitude, the limit of Web Mercator tiles. A box that crosses the antimeridian is not supported. region.zoom from the 0.1 layout was removed in 0.3.0; set imagery.zoom instead (see Removed 0.1 keys).

For XYZ imagery the box is expanded outward to whole tiles at the chosen zoom. For Sentinel-2 it is reprojected into the product’s CRS and expanded outward to the product’s pixel edges. For a GeoTIFF it is reprojected into the file’s CRS, expanded outward to the file’s pixels and clipped to the raster. See Coordinates and grids.

Selects an imagery source with the required type key: xyz, eopf_zarr, stac_cog or geotiff. For segmentation, regression and change datasets it can also be a list of named sources (several sources).

Write imagery as a list to sample several sources at the same patches: a before/after pair, optical plus a DEM, or two dates of the same product. Every source in the list needs a name. Each source keeps its own settings.

imagery:
- {type: geotiff, name: before, path: 2023.tif}
- {type: geotiff, name: after, path: 2025.tif}
- {type: geotiff, name: dem, path: dem_2m.tif} # pixels twice as large: repeated
writer: {staging_dir: ./dataset, image_format: tif}
Rule Detail
Names name on each source: 1–32 lowercase letters, digits, _ or -, starting with a letter or digit, unique. mask and names ending in _world are reserved. A single (not listed) source takes no name
Grid The first source’s pixel grid is the dataset’s grid; region, patches, masks and georeferencing all follow it
Other sources Same CRS as the first, and pixels the same size or a whole number of them across (2×, 3×…) on a grid that shares pixel corners with the first. Coarser pixels are repeated onto the grid (nearest neighbour, exact); nothing is interpolated. A source with finer pixels must come first
Coverage A pixel has imagery only where every source has it. Pixels outside a source count as empty and are ignore_index in masks
Output Images/<name>/patch_*.<ext> per source, one shared Masks/ folder, and one files key per source name in the manifest. With writer.stack_sources, one file per patch holds every source instead
Tasks segmentation (with or without labels), regression and change (exactly two). Tasks that write one image per patch (detection, instance, classification) take a single source

A source on a grid that does not line up (another CRS, a half-pixel shift, a non-integer pixel ratio) fails before anything is written, and the error says how it differs. Resample it onto the first source’s grid first (for example with gdalwarp -tap -tr).

Field Type Default Description
type "xyz" required Selects XYZ tiles
zoom int, 1–22 required Tile zoom level. Pixel size ≈ 156 543 × cos(latitude) / 2zoom m
source string — A built-in source: esri_satellite, esri_topo, esri_street, cartodb_positron, cartodb_dark_matter
url_template string — Your own http:// or https:// URL with {z}, {x} and {y}
earth_engine block — Tiles rendered by Google Earth Engine (see below)
max_connections int, 1–64 16 Concurrent tile requests. Keep this low for shared servers; mapcv init writes 4
policy strict | lenient | ignore lenient What happens to a tile that still fails after retries (see below)
max_failed_ratio float, 0–1 0.05 Under lenient, the run stops if more than this fraction of a chunk’s tiles fail
strip_rows int ≥ 1 4 Tile rows per processing chunk (4 rows = 1,024 px). Lower it to reduce memory on very wide regions
cache bool true Keep downloaded tiles on disk for as long as the server allows, so re-runs don’t download them again (see below)

Validation:

  • Exactly one of source, url_template or earth_engine is required; setting two is an error.
  • source must be a built-in name. The 0.1 presets google_satellite and osm were removed and fail with an explanation (why).
  • url_template must be http(s) with a host (and a valid port), must contain {x}, {y} and {z}, and may contain no other placeholder or brace (write {z}, not {{z}}). The host and port may not hold a placeholder. A {s} subdomain placeholder is rejected: replace it with one subdomain, for example a.
  • Tiles must decode to 256 × 256 images (PNG, JPEG, WebP or GIF). A 16-bit grayscale PNG is scaled to 8 bits by its high byte, as 16-bit RGB is. Fully transparent pixels (alpha 0) are black and count as empty, so sampler.max_empty_ratio drops patches over areas a service leaves transparent.
  • A tile body larger than 4 MiB is not read: that tile fails (response too large) and policy applies.
  • writer.image_format must be png, jpg or tif.

Failure policy. Each tile is tried up to four times (three retries with backoff that honours Retry-After up to 30 s); a request that times out is tried twice. A connection must open within 10 s, a response (and each piece of its body) must arrive within 10 s, and a whole tile within 30 s. Connection errors, timeouts, truncated bodies, HTTP 429 and 5xx are retried; other 4xx responses, non-image bodies (for example an HTML error page) and bodies over 4 MiB fail immediately.

A fetch stops as soon as it cannot succeed: under lenient, when more than max_failed_ratio of a chunk’s tiles have failed (the rest are not requested), and under every policy when the first max(max_connections, 8) requests all went unanswered (timeouts, refused connections, HTTP 429 or 5xx), which means the server is down or the URL is wrong. A tile that still fails is then handled by policy:

policy Failed tile max_failed_ratio
strict Stops the run with an error not used
lenient Left out; its pixels are black and count as empty enforced per chunk
ignore Filled with a black tile never enforced

Requests identify themselves with the user agent mapcv/<version> (+https://github.com/tahamukhtar20/mapcv) and send no Referer header. A redirect is followed only when it stays on http(s), does not go from https to plain http and does not go from a public server to this machine or a private network; for a url_template with a query string (where providers put keys), only redirects within the same host are followed. The host the URL names is connected wherever it resolves, so a docker-compose service or a server on your own network works by name. Any other host a redirect leads to (or another port of the same host) is judged by the addresses its name resolves to, when connecting: from a URL that names a public host, mapcv does not connect there to a loopback, private (10/8, 172.16/12, 192.168/16, fc00::/7), link-local (169.254/16, fe80::/10), carrier-grade NAT (100.64/10), unspecified or multicast address, IPv4-mapped IPv6 included. A refused redirect or address fails the tile at once, without retries.

Tile cache. Downloaded tiles are kept on disk, so a second run over the same area (another patch size, a new staging_dir, a sweep of settings) reads them from the cache instead of the server. A tile is reused for as long as the server’s caching headers allow: Cache-Control: max-age (less Age), else Expires. A response marked no-store or no-cache is not kept, and one without caching headers is kept for 7 days (the minimum the OpenStreetMap tile usage policy asks clients to cache for). A header asking for longer than a year is capped at a year. Expired tiles are downloaded again. Failed tiles are never cached, and neither is a tile that does not decode (a wrong size, an image that is cut off): it is cached only after it has been decoded, and a copy an earlier run kept is removed when it fails to decode.

  • The cache lives in ~/.cache/mapcv/tiles on Linux ($XDG_CACHE_HOME/mapcv/tiles when that is set), ~/Library/Caches/mapcv/tiles on macOS and %LOCALAPPDATA%\mapcv\Cache\tiles on Windows. Set the environment variable MAPCV_CACHE_DIR to put the cache folder elsewhere; tiles then go to its tiles/ subfolder.
  • Tiles are filed under a hash of url_template: the template, and any key in it, is never written to disk.
  • cache: false downloads every tile and keeps none. mapcv cache shows the cache’s size and clears it.
  • The MCP server never uses the cache, because it writes only inside its --root.

Google Earth Engine (imagery.earth_engine)

Section titled “Google Earth Engine (imagery.earth_engine)”

Any Earth Engine image, or a collection reduced to one image, rendered as RGB tiles by Earth Engine and read like other XYZ tiles. Needs pip install "mapcv[gee]" and a one-time earthengine authenticate with an account registered for Earth Engine.

imagery:
type: xyz
zoom: 15 # ≈ 4.8 m/px; Earth Engine renders any zoom
earth_engine:
collection: COPERNICUS/S2_SR_HARMONIZED
start: "2024-06-01"
end: "2024-09-01"
max_cloud: 40 # skip scenes with more than 40 % cloud
cloud_score_plus: 0.6 # mask the cloudy pixels left (Sentinel-2 only)
reducer: median # median | mean | mosaic | min | max
vis: {bands: [B4, B3, B2], min: 0, max: 3000}
project: my-cloud-project # the Cloud project requests are made (and counted) for
Field Type Default Description
image string — An ee.Image asset ID, for example USGS/SRTMGL1_003
collection string — An ee.ImageCollection asset ID, reduced to one image with reducer
start, end date — Filter the collection to [start, end): a date (YYYY-MM-DD) or an RFC 3339 time; collection only
reducer median | mean | mosaic | min | max median How the collection becomes one image
max_cloud number, 0–100 — Keep only scenes whose cloud percentage is at most this; collection only
cloud_property string detected The scene property max_cloud filters on: CLOUDY_PIXEL_PERCENTAGE for Sentinel-2 and CLOUD_COVER for Landsat are detected; for other collections max_cloud needs it
cloud_score_plus number, above 0 and below 1 — Sentinel-2 collections (COPERNICUS/S2…) only: mask each pixel whose Cloud Score+ clear score (cs_cdf) is below this; 0.6 is a good start
vis.bands 1 or 3 band names required Rendered as grey or a palette (one band) or RGB (three)
vis.min, vis.max number or one per band 0, 1 Values mapped to 0 and 255
vis.gamma number or one per band — Gamma correction
vis.palette list of colours — Colours from min to max, for one band only
project string — Your Cloud project registered for Earth Engine

Set exactly one of image or collection. A collection is filtered to the scenes over the tiles the dataset reads (the region snapped outward to whole tiles, so pixels at the edge get all their scenes too), the dates and max_cloud, masked with cloud_score_plus, then reduced. Without cloud settings a summer median in a cloudy climate is mostly cloud; max_cloud alone is enough where clear scenes are common, and cloud_score_plus also keeps the clear parts of partly cloudy scenes. mapcv init --template earth-engine writes this recipe, and the guided mapcv init offers Sentinel-2, Landsat 8 and NAIP with colours that suit them, or any asset. Each time it opens the imagery, mapcv asks Earth Engine for a tile URL (getMapId) with your credentials. That URL holds a short-lived map ID, so it is never written to the config, the manifest, the tile cache or messages. Cached tiles of a collection are kept per tile area, since another area filters other scenes into the composite: a run over another region never reuses them, so the output does not depend on what ran before. The manifest records earth-engine:<asset> and the rendering settings (not project), so a resumed run must ask for the same image and rendering. Earth Engine’s terms, quotas and any billing are yours; see Providers & licensing.

Sentinel-2 L2A EOPF Zarr (type: eopf_zarr)

Section titled “Sentinel-2 L2A EOPF Zarr (type: eopf_zarr)”

Requires pip install "mapcv[zarr]" (Python 3.10–3.13).

Field Type Default Description
type "eopf_zarr" required Selects one EOPF Sentinel-2 L2A product
path string — Local path, file:// URL, or anonymous https:// or s3:// URL of the .zarr product. A relative local path is resolved against the config file’s folder
search mapping — Find the product in a STAC catalog instead of naming it (below); set path or search, not both
resolution 10 | 20 | 60 10 Output grid in metres; every band is harmonised onto it
bands list of strings all 12 Variables to read, in output order. Default: b01 b02 b03 b04 b05 b06 b07 b08 b8a b09 b11 b12
chunk_rows int ≥ 1 1024 Raster rows per processing chunk
scl_mask non-empty list of ints 0–11 none Scene classification (SCL) classes whose pixels count as having no imagery, such as [3, 8, 9, 10] (cloud shadows, medium and high probability clouds, cirrus)

Validation:

  • Exactly one of path and search.
  • path may not contain a username, password, query string or fragment; private-store authentication is not supported. Windows paths (C:\data\S2.zarr, file:///C:/data/S2.zarr) work.
  • bands must be non-empty and without duplicates; names are lower-cased (B04 → b04). Unknown band names fail when the product is opened, with a list of what is available.
  • writer.image_format must be npy or tif.
  • The region must intersect the product.
imagery:
type: eopf_zarr
search:
datetime: 2025-05-01/2025-05-31
max_cloud: 10
bands: [b02, b03, b04, b08]
scl_mask: [3, 8, 9, 10]
Field Type Default Description
datetime string required A date (2025-05-13), an RFC 3339 time, or an interval start/end (.. for an open end); dates cover whole days
max_cloud number 0–100 20 Highest eo:cloud_cover (percent) of an acceptable product
catalog URL https://stac.core.eopf.eodc.eu A STAC API (https://; http:// only for localhost)
collection string sentinel-2-l2a The collection to search
asset string product The item asset holding the whole EOPF Zarr product

When the run starts, mapcv searches the catalog for the region’s bounding box and picks, among the items whose footprint covers the whole region with at most max_cloud percent cloud, the least cloudy one; ties go to the earlier acquisition, then to the item ID, so the same catalog gives the same product every time. The manifest’s sources[0].fingerprint records the catalog, collection and item, and product_id the product’s name: resuming refuses to continue with another product if the search later finds a different one. mapcv plan estimates without contacting the catalog. A page of results may be at most 64 MiB and must arrive within 120 s, and next links are followed only on the catalog’s own scheme and host. When no item qualifies, the run stops and says how many items were found and how many covered the region.

scl_mask reads the product’s scene classification layer (20 m, nearest-neighbour onto the output grid) and treats pixels of the listed classes as pixels without imagery: their band values become NaN, segmentation masks mark them with labels.ignore_index, and they count towards sampler.max_empty_ratio, so max_empty_ratio: 0.2 drops patches that are more than a fifth cloud. Values outside 0–11 (the fill at product edges) also count as no imagery.

Sentinel-2 as Cloud Optimized GeoTIFFs (type: stac_cog)

Section titled “Sentinel-2 as Cloud Optimized GeoTIFFs (type: stac_cog)”

Each band of a Sentinel-2 L2A scene as its own COG, found in a STAC catalog: by default Earth Search (Element 84), whose COGs sit in a public AWS bucket. No extra install: mapcv reads the COGs with its own reader, over HTTPS range requests.

imagery:
type: stac_cog
search:
datetime: 2025-04-01/2025-06-30
max_cloud: 20
bands: [red, green, blue, nir, swir16] # the item's asset keys, in output order
scl_mask: [3, 8, 9, 10] # cloud shadows, clouds, cirrus: no imagery
writer:
image_format: npy # or tif
Field Type Default Description
search mapping required As for eopf_zarr: datetime (required), max_cloud (20), collection (sentinel-2-l2a), and catalog, here https://earth-search.aws.element84.com/v1 by default. No asset: the bands are the assets
bands list of strings [red, green, blue, nir] Asset keys of the item, in output order. Earth Search names them coastal, blue, green, red, rededge1–rededge3, nir, nir08, nir09, swir16, swir22
scl_mask non-empty list of ints 0–11 none Scene classes whose pixels count as having no imagery, read from scl_asset
scl_asset string scl The item’s scene classification asset
chunk_rows int ≥ 1 1024 Raster rows per processing chunk

The item is chosen as for eopf_zarr search: the least cloudy one covering the whole region, ties to the earlier acquisition, then the ID. Bands come at 10, 20 or 60 m; every band is placed on the grid of the finest band among bands, a coarser band’s pixels repeated (nearest neighbour, exact: Sentinel-2’s grids share their origin), so 20 m bands are not interpolated. Values are the COGs’ as stored (uint16 digital numbers for Earth Search; reflectance = value / 10000, plus the offset its metadata states for processing baseline 04.00 and later). A pixel has imagery when every band has data (not the COGs’ NoData, 0) and, with scl_mask, its scene class is not masked; SCL’s class 0 is “no data” as well. Patches are writer.image_format: npy or tif. The manifest’s sources[0] records source_type: stac_cog, the item ID as product_id, and the catalog, collection and item in its fingerprint, so a resumed run refuses another item.

A catalog may point only to https:// or s3:// COGs (local files only from a localhost catalog, for testing).

GeoTIFF and Cloud Optimized GeoTIFF (type: geotiff)

Section titled “GeoTIFF and Cloud Optimized GeoTIFF (type: geotiff)”

Needs no extra install. Reads one file, or a mosaic of local files, as it is: patches keep the file’s CRS, pixel grid, band count and data type, and nothing is resampled. A guide with examples: Use your own GeoTIFF.

Field Type Default Description
type "geotiff" required Selects one GeoTIFF or COG, or a mosaic of local GeoTIFFs
path string required Local path, file:// URL, or https:// or anonymous s3:// URL (read with HTTP range requests). A relative local path is resolved against the config file’s folder. A local glob pattern (tiles/*.tif, survey/**/*.tif) reads every matching file as one mosaic. A path that exists is always that file, even with [, * or ? in its name (img[12].tif, survey [2024]/ortho.tif); only a path that does not exist is read as a pattern
bands list of ints ≥ 1 all bands 1-based band numbers to read, in output order: [3, 2, 1] reverses the bands of a BGR file. Band names in the manifest keep the file’s numbering (b3, b2, b1)
overview int ≥ 0 0 Overview level: 0 is the full resolution, 1 the first (largest) overview, and so on. Fails when the file has fewer levels
nodata number | .nan file’s value NoData value that overrides (or supplies) the file’s. Pixels where every selected band equals it have no imagery
chunk_rows int ≥ 1 1024 Raster rows per processing chunk

Validation:

  • path may not contain a username, password, query string or fragment; private-store authentication is not supported. Plain http:// is accepted only for a loopback host (localhost, 127.0.0.1), for a local test server. A redirect from the server is checked by the same rules: it may not lead to plain http:// elsewhere, from https:// to http://, to a URL with credentials, or from a public server to this machine or a private network, also by a host name that resolves there (as for tiles, above). Windows paths work.
  • bands must be non-empty and without duplicates.
  • A glob pattern (*, ?, [...]; ** also matches folders) is for local files only and must match at least one file. A part of the path that exists as written is taken literally, so a folder called survey [2024] works in survey [2024]/*.tif. ** does not follow links to folders, and a file reached through several links is used once. Dot files and folders are not matched. The files form one mosaic: they must share the CRS, data type, band count, NoData value (or set nodata for all of them), pixel size and pixel grid (pixel corners line up), and have the overview level asked for. Where files overlap, the first file in sorted path order that has data at a pixel wins; gaps between files are no imagery. bands, overview and nodata apply to every file. The manifest records each file’s name and fingerprint, so resuming after adding, removing or changing a file is refused.
  • nodata must be a value the file’s data type can hold; otherwise no pixel could equal it, and mapcv warns that it has no effect.
  • With overview above 0, the files of a mosaic must also have overviews of one pixel size. Files whose size is not a multiple of the overview factor round it differently; use overview: 0 for those.
  • The file must have a CRS that is an EPSG code of a map CRS (projected or geographic; a geocentric or vertical code is refused) and a pixel transform, and its compression must be one the reader supports (none, Deflate, LZW, PackBits, ZSTD, JPEG, WebP); the error names the problem. The region must intersect the file.
  • writer.image_format: png or jpg need uint8 data in 1 or 3 selected bands (a single band is written as gray, repeated into three channels); any other data type or band count needs npy or tif (both keep the file’s data type and band count; tif also writes georeferenced patches). A selection that cannot fit PNG/JPG is rejected when the config is loaded; the data type is checked when the file is opened (mapcv plan does that, without reading pixels).

Empty pixels. A pixel has no imagery when every selected band equals the NoData value, or, for floating-point files, is NaN or infinite. They count as empty for sampler.max_empty_ratio and are labels.ignore_index in masks, like the area outside the raster. A region that extends beyond the file is clipped to it with a warning; one that misses it is an error.

Resuming. The manifest records a fingerprint of the file in sources[0].fingerprint and a resumed run refuses a different file (see Dataset format).

Optional. Without it you get an image-only dataset (no Masks/). labels.type picks where the masks come from: vector (the default, so configs without type keep working) burns polygons from a GeoJSON, KML, GeoPackage, Shapefile or GeoParquet file (or several, or OpenStreetMap); raster reads a classified GeoTIFF, see Raster labels; continuous reads a raster of values for task: regression, see Continuous values.

Field Type Default Description
type vector vector Polygon labels
path path required A .geojson, .json, .kml, .gpkg, .shp, .parquet or .geoparquet file. GeoJSON and KML are WGS-84 longitude/latitude; the others may use any CRS they state and are reprojected. A relative path is resolved against the config file’s folder. A local file only: file:// and https:// URLs are for imagery.path and are refused here
layer string | null null GeoPackage only: the layer (table) to read. Required when the file has several; leave unset with one. An error for other formats
label_field string | null null Feature property (GeoJSON, KML) or column (the other formats) holding the class. null: every polygon is class 1
classes map label → int null Fixed class IDs, each 1–255, for example {building: 1, road: 2}. Requires label_field. Labels not in the map are skipped
ignore_index int 1–255 or null 255 Mask value for pixels with no imagery under them: padding, failed tiles and NoData. Pass it to your loss (CrossEntropyLoss(ignore_index=255)). No class may use it; null writes background (0) there instead. With task: classification no mask is written and imagery-less pixels never count either way
all_touched bool false Burn every pixel a polygon touches, not only pixels whose centre is inside
buffer {line, point} in metres null Turn line and point features into polygons: a line becomes a band line metres wide (a road, a river), a point a disc point metres across (a tree, a well). Give one or both; features of the other kind are skipped as before. The buffer is drawn in metres on the ground, in the UTM zone of each feature, then the polygon is labeled like any other. Not for KML (mapcv reads only its polygons)
files list null Several label files instead of path (see below)
osm mapping null Labels straight from OpenStreetMap instead of a file (below)
annotated_area path null A polygon file (any of the formats above) outlining where the scene was labeled. Mask pixels outside it get ignore_index instead of background, so a partly labeled scene does not teach that unlabeled ground is background. For task: segmentation and classification (where the outside pixels do not count towards coverage); needs an ignore_index. Feature attributes are ignored. A relative path is resolved against the config file’s folder
labels:
path: osm_roads_and_trees.geojson
label_field: kind
classes: {road: 1, tree: 2}
buffer: {line: 6, point: 8} # roads 6 m wide, tree crowns 8 m across
annotated_area: surveyed.geojson # outside it: ignore_index, not background

Several label files. List them under files instead of giving path: buildings from one source, roads from another, land use from a third. Each entry has a path (any of the formats above), its own layer and buffer, and either a label_field (each feature’s class is that attribute’s value) or a class (one class for every feature of the file). classes, ignore_index, all_touched and annotated_area stay under labels and apply to all files; label_field, layer and buffer move into the entries. Class IDs are given once over the names of all files (with classes when set, so a name has the same ID whichever file it comes from). Later files win where features overlap, so put the most trusted or most specific labels last:

labels:
files:
- {path: landuse.gpkg, layer: landuse, label_field: landuse} # forest, crop, ...
- {path: buildings.geojson, class: building} # drawn over land use
- {path: roads.geojson, class: road, buffer: {line: 6}} # drawn over both
classes: {building: 1, road: 2, forest: 3, crop: 4}

How class IDs are chosen (label_field set, no classes): if every label is an integer from 1 to 255 (digits 0–9), the integer is the ID; otherwise the distinct labels are sorted, numbers by value before names, and numbered 1, 2, 3, …. 3 and 3.0 are the same class. More than 255 distinct labels is an error. Features without polygon geometry, without a label value or with coordinates that are not numbers or beyond ±90° latitude are skipped with a warning that counts them. Where polygons overlap, the one later in the file wins.

Validation: exactly one of path, files and osm; suffix must be one of the above (unzip a KMZ to its KML first; a .tif asks for type: raster); layer needs a .gpkg; classes keys must be non-empty and values integers in 1–255; with path, classes requires label_field. GeoJSON with a projected crs member is rejected. At run time a Shapefile without .prj, a layer without a CRS and a GeoParquet file with crs: null are errors (the CRS would be a guess), GeoParquet needs pip install "mapcv[parquet]", and a label_field that no feature has (in any format) lists the fields there are. Malformed GeoJSON is an error naming the feature; a GeoPackage layer that is a view is read in the order SQLite returns its rows, within work and memory bounded by the file’s size (a view whose query never ends, or that builds values far larger than the file, is an error). A GeoParquet file whose columns decode to more than 64 times its size plus 64 MiB is an error. The manifest records layer and buffer in target.labels when they are set (with files, each file’s settings without its path, and one hash over all files), and the annotated area by its file’s hash (annotated_area_sha256, so editing it is a resume mismatch); annotated_area must be a vector file, needs an ignore_index and applies to segmentation and classification only; buffer needs a distance above 0, and buffer.point and detection.point_box_size cannot both be set. Details: Prepare labels.

labels:
osm:
classes:
- {name: building, tags: {building: "*"}}
- {name: water, tags: {natural: water}}
- {name: road, tags: {highway: [motorway, primary, secondary, residential]}}
buffer: {line: 6} # roads become 6 m wide polygons
Field Type Default Description
classes list required Each class has a unique name and tags: a key with "*" (any value), one value, or a list of values. A feature belongs to the first class whose tags all match. Keys are letters, digits and _:.-; values hold no quotes or backslashes
overpass_url URL https://overpass-api.de/api/interpreter The Overpass API to ask (https://; http:// only for localhost). The public server limits heavy use: for large areas, run your own or use an extract with labels.path. A key in the query string is accepted: the manifest records its values as ***, and messages leave the query out
timeout int seconds, 1–3600 180 The query’s time limit on the server. The whole answer must arrive within timeout + 60 s, and be at most 512 MiB
bbox [west, south, east, north] the region The box to query

One Overpass query fetches every matching node, way and relation in the box. Closed ways become polygons (except highways, barriers, railways and waterways, and area=no, which stay lines), multipolygon relations become polygons with their holes (rings are assembled by nesting, as osmium and GDAL do, so an island in a lake is kept), a self-intersecting closed way is repaired keeping all its parts, other ways are lines and nodes are points; as with label files, lines and points become labels only with buffer. Classes get IDs 1, 2, 3, … in the order listed, or the IDs of labels.classes (by class name, so it may only name classes of osm.classes). label_field and layer don’t apply. buffer, all_touched, ignore_index and annotated_area work as for files, with every task that takes polygon labels.

The answer is stored as GeoJSON in the cache folder (<cache dir>/osm/, see the tile cache), with the data’s timestamp (osm_base) and the query, and reused until you delete it: rerunning or resuming does not ask Overpass again, and the labels’ SHA-256 in the manifest identifies the exact data. Fresh data (after deleting the file) has another hash, so a resumed run refuses to mix it with the old. mapcv plan fetches the labels too, to count them; the MCP server does not fetch them (it writes only inside its root).

OpenStreetMap data is © OpenStreetMap contributors under the Open Database License: a dataset whose masks come from it is a derived database, to be shared under the ODbL with that credit. mapcv card adds the credit.

A classified raster, such as a land-cover map (ESA WorldCover, Dynamic World), a national map or an earlier model’s output, used as the masks. It may have any CRS, resolution and origin: mapcv reads it on the imagery’s pixel grid.

labels:
type: raster
path: ESA_WorldCover_10m_2021_v200_N48E000_Map.tif # local, https:// or anonymous s3://
classes: # raster value -> mask ID, or -> {id, name}
10: {id: 1, name: tree_cover}
30: {id: 2, name: grassland}
40: {id: 3, name: cropland}
50: {id: 4, name: built_up}
80: {id: 5, name: water}
90: {id: 6, name: wetland}
95: {id: 6, name: wetland} # several values may share an ID
unmapped: background # other values: background | ignore
Field Type Default Description
type raster required Raster labels
path string required A GeoTIFF or COG: a local path (relative to the config file’s folder), an https:// URL or an anonymous s3://bucket/key, read like GeoTIFF imagery (only the tiles under each window). Its CRS must be an EPSG code and its values integers (uint8, uint16, int16, …)
band int ≥ 1 1 Band holding the classes (1-based)
classes map value → ID or {id, name} required Raster value → mask ID (0–255, 0 is background). The short form 10: 1 names the class value_10 (value_90_95 when several values share the ID); {id: 1, name: tree_cover} names it. One ID has one name. The names and IDs are the manifest’s class_map
unmapped background | ignore background What values missing from classes become: background (0), or ignore_index
nodata int | null the file’s NoData Label value meaning “no label”: such pixels get ignore_index. The file’s NoData does not apply to a value classes maps (a warning says so)
ignore_values list of int [] More label values that get ignore_index (for example “unclassified”)
resampling nearest nearest How a label pixel is picked; categorical values are never averaged, so nearest is the only choice
ignore_index int 1–255 or null 255 As for polygon labels; it also marks pixels outside the label raster, label NoData and ignore_values. null writes background (0) there

Alignment. Each imagery pixel takes the value of the label pixel that contains the imagery pixel’s centre. The centre (col + 0.5, row + 0.5 in the corner-based affine transform) is projected exactly into the label raster’s CRS (pyproj; computed in parallel threads, with exactly the coordinates a call per point gives; for a fixed PROJ pipeline, a 16-pixel grid of centres is projected and the rest interpolated, and every centre the interpolation leaves within its measured error, and at least 0.001 label pixel, of a label-pixel boundary is projected exactly, so the label pixels are the same), then the inverse of the label raster’s transform gives a fractional label pixel, which is rounded down (floor(x + 1e-10): a centre exactly on a boundary between two label pixels takes the right / lower one, as GDAL does). Files tagged PixelIsPoint are shifted by half a pixel to corner-based, as in GDAL. The result equals rasterio.warp.reproject(..., resampling=Resampling.nearest, tolerance=0) onto the patch’s transform. (GDAL’s default tolerance=0.125 approximates the transformation and can pick a neighbouring label pixel near boundaries.) When the label raster shares the imagery’s CRS and pixel grid, windows are copied directly, with the same result.

Validation: classes must list at least one value; no ID may be ignore_index; an ID has one name and a name one ID; ID 0 takes no name; ignore_values and nodata may not be in classes; unmapped: ignore needs an ignore_index. Raster labels make masks, so they work with task: segmentation, with task: classification (one label per patch) and as the change mask of task: change; task: detection and task: instance need polygon labels (one feature per object) and refuse type: raster. generate and plan refuse a label raster without an EPSG CRS, with non-integer values or without the configured band, and warn when it does not overlap the imagery. Details: Prepare labels.

The targets of task: regression: a GeoTIFF or COG of values to predict, such as canopy height, biomass or elevation, in any CRS and resolution. Each imagery pixel takes the value at its centre, with the same exact nearest-neighbour lookup as raster labels (values are never interpolated), and the target is value × scale + offset as float32.

task: regression
labels:
type: continuous
path: canopy_height_cm.tif
scale: 0.01 # centimetres to metres
valid_min: 0
writer: {staging_dir: ./dataset, image_format: tif, mask_format: tif}
Field Type Default Description
type "continuous" required Selects a raster of values
path string required Local path, file:// URL, or https:// / anonymous s3:// URL, as for raster labels
band int ≥ 1 1 Band holding the values
nodata number | .nan the file’s NoData Value meaning “no value”
scale, offset number 1, 0 The target is value × scale + offset, computed in float64 and stored as float32
valid_min, valid_max number — Raw values outside this range have no target

Target pixels are NaN where there is no value: outside the raster, on its NoData, NaN or out-of-range values, and where the imagery has no data (padding included). sampler.min_label_ratio keeps a patch only when at least that fraction of its pixels has a value. Each manifest entry’s summary.values holds the number of pixels with a value and their min, max and mean; mapcv info sums them up.

Validation: task: regression needs type: continuous labels, and type: continuous needs task: regression. scale must be finite and non-zero, offset, valid_min and valid_max finite, and valid_min at most valid_max. Float targets do not fit a PNG, so writer.mask_format defaults to tif for regression and png is refused. A resumed run must use the same raster (its fingerprint is recorded) and the same scaling.

Field Type Default Description
patch_size int, 1–16384 required Side of each square patch in pixels
stride int ≥ 0 0 Step between grid patches. 0 means patch_size (no overlap); smaller values overlap
mode grid | random grid Regular grid, or random_count random positions
edge_strategy pad | drop | shift pad What to do where a patch would cross the raster edge (see below)
pad_mode zero | reflect zero How pad fills the missing part of an edge patch
max_empty_ratio float, 0–1 1.0 Skip patches whose empty fraction (pixels without imagery: failed tiles, NoData, outside the raster) is above this. A patch without a single pixel of imagery is always skipped, and generate says how many were
min_label_ratio float, 0–1 0.0 Skip patches whose labeled fraction (mask pixels that are neither background 0 nor ignore_index) is below this. Only applies with labels. For detection, the labeled fraction is the area of the kept objects’ visible parts; for classification, the valid pixels of any class over the whole patch
random_seed int ≥ 0 42 Seed for mode: random
random_count int ≥ 0 100 Number of random patches in total (not per chunk). Capped at the number of distinct positions on the raster (a warning says so), and at most 3,000,000 (see below)

Patch limit. One run enumerates at most 3,000,000 patches (grid positions over the whole raster, or random_count), because every position is held in memory while the run lasts. plan still counts any grid, from the raster’s size alone, and lists the limit as a problem; generate refuses a larger one before it writes anything, with the number of patches, the limit and what to change: raise sampler.stride (or patch_size), shrink the region or split it into several smaller ones. With region.path the limit applies to the grid over the region’s bounding box, before the polygons are applied. Jobs over 1,000,000 patches ask for confirmation first.

Edge strategies:

edge_strategy Grid behaviour Random behaviour
pad Patches start at every stride step inside the raster; the part outside is filled by pad_mode and the patch is marked padded Same positions as drop; a raster smaller than the patch gives one padded patch at the top-left
drop Only patches that fit entirely inside Positions where the patch fits entirely inside; none if the raster is smaller than the patch
shift Like drop, plus one last patch shifted inward to cover the remaining edge (may overlap its neighbour) Same as pad

Empty pixels: for GeoTIFF, a pixel is empty when it is outside the file, equal to NoData in every selected band, or non-finite (floating-point files). For XYZ, a pixel is empty when all three channels are 0 (black tiles, failed tiles, padding). For Sentinel-2, a pixel is empty unless every band has data, i.e. when any band is NaN (NoData); a reflectance of 0 is valid data. Padding in Sentinel-2 patches is 0, not NaN.

Field Type Default Description
staging_dir path required Output folder, created if needed; a relative path is resolved against the config file’s folder. Re-running into a folder with a compatible manifest.json resumes
image_format png | jpg | npy | tif png png/jpg for XYZ (RGB uint8); npy for Sentinel-2 (bands-first: float32 for eopf_zarr, the COGs’ uint16 for stac_cog); tif for any source: a georeferenced GeoTIFF with the source’s own band count and data type. For GeoTIFF imagery png/jpg need uint8 in 1 or 3 bands, otherwise use npy (bands-first, the file’s data type) or tif
jpg_quality int, 1–100 95 JPEG quality when image_format: jpg
jpg_subsampling 4:2:0 | 4:4:4 4:2:0 JPEG chroma subsampling when image_format: jpg. 4:2:0 stores colour at half resolution in each direction, like Pillow’s default, and gives smaller files (about 10% on typical aerial imagery at quality 95, up to half on noisy colour imagery). Use 4:4:4 to keep full colour resolution. JPEG datasets made by mapcv 0.2 are 4:4:4, so resume them with jpg_subsampling: 4:4:4
mask_format png | npy | tif png (tif for regression) How masks are stored (with labels). png is a single-channel PNG (8-bit; 16-bit for 16-bit masks); npy a NumPy array; tif a GeoTIFF that opens aligned with the image. The mask values, including labels.ignore_index, are identical in every format
world_files bool false Write a world file next to every PNG (.pgw) and JPG (.jgw) patch, images and masks, so QGIS and GDAL place them on the map. World files hold no CRS (it is crs in the manifest). Not needed for tif, which carries its georeferencing inside; an error when neither images nor masks are PNG or JPG
footprints bool true Write patches.geojson after the split: one WGS-84 footprint polygon per patch with its file name, split, row and column, and class pixel counts. Open it in QGIS to see where each patch is
stack_sources bool false With several imagery sources (a segmentation or regression dataset), write them into one file per patch instead of one per source: npy stores a (T, C, H, W) array (T sources in config order, C bands each), tif a GeoTIFF of T × C bands named <source>_<band>. For time series and temporal models (several dates of one product). Every source needs the same band count and data type

The manifest records the writer as {layout: files, image_format, jpg_quality, jpg_subsampling, mask_format} (plus world_files: true and stack_sources: true when they are on). Changing image_format, mask_format or world_files of an existing dataset is a configuration mismatch, so resuming it is refused; footprints does not affect the patches and may change. Layout and naming: Dataset format. Detection and classification datasets record no mask_format (setting it is an error: they write no masks) and write images to images/ (lowercase, where Ultralytics looks for them) instead of Images/; Ultralytics reads PNG, JPG and TIF images, not NPY. Instance datasets also write images/, and record mask_format only with instance.id_mask (its PNGs go to masks/). Change datasets write A/, B/ and label/.

GeoTIFF output. tif patches (and masks) are written by mapcv itself: striped, Deflate-compressed with the horizontal predictor (the floating-point predictor for float32), one band per channel. They carry the source’s EPSG code and each patch’s own affine transform, so QGIS, rasterio and TerraTorch open them in place. Images declare NaN as no-data when they are floating point (and nothing for integer data, where 0 is a real value); masks declare labels.ignore_index as no-data, so GIS tools show the pixels without imagery as transparent. See Dataset format.

Validation: XYZ needs png, jpg or tif; EOPF and stac_cog need npy or tif; GeoTIFF imagery needs npy or tif unless it is uint8 in 1 or 3 bands (see GeoTIFF). world_files needs PNG or JPG files to write next to. stack_sources needs imagery as a list of two or more sources, task: segmentation or regression, and image_format npy or tif; sources with different band counts or data types are refused when they are opened, before anything is written.

Optional. When present, mapcv generate writes split lists at the end; mapcv split can redo them later with different settings.

Field Type Default Description
strategy spatial | stratified | random | region spatial spatial assigns whole raster blocks to one split and drops train/val patches overlapping a held-out patch. stratified and random split individual patches. region assigns whole regions of an area of interest to one split each (in a seeded random order), and drops train/val patches overlapping a held-out one where regions touch: the cleanest leakage-free split when the regions are apart. It needs a dataset made with region.path, and at least three regions to fill train, val and test
test_ratio float, 0–1 0.20 Fraction of patches for test.txt
val_ratio float, 0–1 0.10 Fraction of the remaining patches for val.txt
block_size int ≥ 1 auto spatial block side in pixels. Default 4 × patch_size, reduced (never below patch_size) so the raster has at least 10 blocks
labeled_ratios list of floats in (0, 1] [0.10, 0.20, 0.30] Write splits/<percent>/labeled.txt and unlabeled.txt subsets of train for each ratio. [] writes none
seed int 42 Seed for shuffling blocks or patches
sample_limit int ≥ 1 — Split only this many patches (subsampled, keeping the stratum mix unless strategy: random)

Validation: ratios within bounds; labeled_ratios may not contain duplicates (after conversion to folder names such as 0.125 → 12.5). With spatial, the achieved ratios are approximate because whole blocks move together. See Splits without leakage.

mapcv 0.2 still loaded the 0.1 layout (region.zoom plus a top-level tiles: block) with a deprecation notice. Both were removed in 0.3.0 and now fail validation, with the replacement named in the error:

Terminal window
$ mapcv validate old.yaml
Config error in old.yaml:
• config: `tiles:` was replaced by `imagery:` in mapcv 0.2 and removed in 0.3; move it under `imagery: {type: xyz, zoom: ..., source: ...}` and delete `region.zoom` (see MIGRATION.md)
Removed Use instead
region.zoom imagery.zoom
tiles: imagery: {type: xyz, zoom: …, source: …}

Full guide: Migrating.