Handling Partial Overlap between AOI and Scene
Measure coverage from valid pixels, not footprints, and write onto a fixed AOI grid so partial scenes still stack:
import numpy as np
from rasterio.mask import mask
arr, _ = mask(src, [aoi_geom], crop=True, filled=False)
coverage = arr[0].count() / aoi_px # valid pixels / pixels in the AOI
if coverage < 0.2:
skip(scene) # too little to be worth it
Scenes rarely line up with study areas, and the defaults for handling the mismatch produce outputs that do not stack. This page belongs to automated image clipping and cropping in Satellite Processing Workflows & Index Pipelines.
Footprint Coverage Is Not Data Coverage
Catalog footprints for tiled products are frequently the tile bounds rather than the data boundary, and even where they are the data boundary they are simplified polygons. The only reliable measure of whether a scene is useful for an AOI is the count of valid pixels inside it, and that can be computed cheaply from an overview before the full-resolution read.
Environment & Setup
| Package | Version pin | Used for |
|---|---|---|
rasterio |
>=1.3.0 |
Masked reads and fixed-grid writes |
numpy |
>=1.23 |
Coverage arithmetic and stitching |
shapely |
>=2.0 |
AOI geometry and bounds |
pip install "rasterio>=1.3.0" "numpy>=1.23" "shapely>=2.0"
Complete Working Example
import math
import numpy as np
import rasterio
from rasterio.features import geometry_mask
from rasterio.transform import from_origin
from rasterio.warp import reproject, Resampling
def aoi_grid(aoi_geom, res: float):
"""A fixed grid snapped to res that covers the AOI — shared by every output."""
l, b, r, t = aoi_geom.bounds
l, b = math.floor(l / res) * res, math.floor(b / res) * res
r, t = math.ceil(r / res) * res, math.ceil(t / res) * res
w, h = int(round((r - l) / res)), int(round((t - b) / res))
return from_origin(l, t, res, res), h, w
def onto_aoi_grid(scene_path: str, aoi_geom, crs, res: float = 10.0):
"""Warp a scene onto the AOI grid; return array, coverage and grid."""
transform, h, w = aoi_grid(aoi_geom, res)
inside = ~geometry_mask([aoi_geom], out_shape=(h, w), transform=transform)
dst = np.full((h, w), np.nan, dtype="float32")
with rasterio.open(scene_path) as src:
reproject(rasterio.band(src, 1), dst,
src_transform=src.transform, src_crs=src.crs,
dst_transform=transform, dst_crs=crs,
src_nodata=src.nodata, dst_nodata=np.nan,
resampling=Resampling.bilinear)
dst[~inside] = np.nan
coverage = float(np.isfinite(dst[inside]).mean())
return dst, coverage, (transform, h, w)
def complete_aoi(scene_paths: list[str], aoi_geom, crs, *, min_cov: float = 0.2):
"""Fill the AOI from several scenes of one pass; first valid pixel wins."""
out, grid, used = None, None, []
for path in scene_paths:
arr, cov, g = onto_aoi_grid(path, aoi_geom, crs)
if cov < min_cov:
continue
out = arr if out is None else np.where(np.isfinite(out), out, arr)
grid = g
used.append((path, round(cov, 3)))
return out, grid, used
Warping every scene onto one grid derived from the AOI — not from each scene — is what makes partial outputs usable. Two dates that each cover different halves of the AOI produce arrays of identical shape and transform, so they stack, difference and composite without a further alignment step. The same principle underlies aligning two rasters with reproject_match.
Crop to the Overlap, or Keep the AOI Grid
Cropping to the overlap minimises storage and is right for one-off outputs. For anything that will be combined — time series, composites, change detection — writing onto the fixed AOI grid is worth the nodata margins, because it moves alignment from every downstream step to one place. Compression makes the margins nearly free on disk.
Choosing Scenes to Complete an AOI
When no single scene covers the AOI, scenes from the same pass — adjacent tiles or path/row frames acquired minutes apart — fill it without introducing a date difference. Scenes from different dates also fill it, but create a seam where the atmosphere, illumination or the surface itself changed between acquisitions. Prefer same-pass completion, then nearest dates, and record which scene supplied each pixel if the output will be analysed rather than only viewed. The techniques for blending across such seams are covered in seamless mosaicking and edge blending.
Recording Which Scene Supplied Each Pixel
When an AOI is completed from several scenes, a small companion band recording the index of the source scene for each pixel is cheap and repeatedly useful. It explains seams, lets a later analysis exclude one problematic scene without reprocessing, and turns the question “which acquisition is this pixel from?” into a lookup. Store it as uint8 alongside the data, with the scene identifiers listed in the file’s tags in index order.
Verification
arr, grid, used = complete_aoi(scenes, aoi_geom, "EPSG:32636")
transform, h, w = grid
inside = ~geometry_mask([aoi_geom], out_shape=(h, w), transform=transform)
total = float(np.isfinite(arr[inside]).mean())
print("scenes used:", used, f"combined coverage {total:.1%}")
assert arr.shape == (h, w)
Common Errors
Outputs from two dates will not stack
Each was cropped to its own overlap. Write both onto the AOI grid.
A “full coverage” scene is mostly empty in the clip
Coverage was judged from the tile footprint. Measure valid pixels.
A visible seam where two scenes meet
They were from different dates. Prefer same-pass scenes, or blend.
The AOI grid is offset by a fraction of a pixel between runs
Bounds were not snapped to the resolution. Snap them as aoi_grid does.
Frequently Asked Questions
Q: Why does the footprint say full coverage but the clip has holes? Scene footprints are often the tile bounds, not the valid-data boundary. Sentinel-2 tiles at the edge of a swath are partly nodata, so a footprint can cover the AOI while the pixels do not. Measure coverage from valid pixels.
Q: Should the output keep the AOI’s full extent? Keep it when outputs from several dates or scenes must stack pixel for pixel, because then every file shares one grid. Crop to the overlap when the output stands alone and storage matters.
Q: What minimum coverage is worth processing? It depends on the product, but a threshold of around 20 to 30 per cent of the AOI is common for composites, since low-coverage scenes add little and cost as much to process as full ones.
Q: Can coverage be estimated before downloading anything? Yes, from an overview level of each candidate scene: a read of a few kilobytes gives the valid-data pattern to within a few per cent, which is enough to rank and filter scenes before any full-resolution read.
Related
- Automated Image Clipping and Cropping — the parent topic.
- Clipping a Raster with a GeoPackage Layer — getting the AOI geometry.
- Building a Mosaic from a STAC Search — completing larger areas from many scenes.
- Estimating Per-Scene Cloud Cover in Python — the other half of deciding whether a scene is usable.