Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Patching — sliding-window inference (intro)

Patching intro — sliding-window inference on a Sentinel-2 chip

This notebook walks through the canonical SpatialPatcher use case — splitting a raster into chips, running an operator per chip, and stitching the results back into a global field. The substrate is a real Sentinel-2 L2A scene over Lake Tahoe pulled from Microsoft Planetary Computer via the geostack.load_s2_chip helper.

We track array shapes after every operation so the data flow stays explicit. The same machinery powers the 05_patching_grids and 06_ml_patches_augment applied walkthroughs at the parent project level — read those for end-to-end NDVI / dNBR / classification flows.

1. Load one real chip

load_s2_chip does the STAC search + multi-band stacking + bilinear reprojection in one line. The returned GeoTensor is BGRN ((4, H, W) uint16) — for this intro we collapse to a single band (B08, NIR) so the patching arithmetic is easy to read; the applied walkthroughs use the full multi-band stack.

S2 chip: shape=(4, 3935, 1599)  dtype=uint16  crs=EPSG:32610
<Figure size 700x1100 with 1 Axes>

2. The four-axis Patcher

256×256 SpatialRectangular patches, SpatialRegularStride of 256 (non-overlapping), SpatialBoxcar window (no taper), reconstruct via SpatialOverlapAdd.

len(patches): 90
patches[0].data.shape: (256, 256)
patches[0].anchor: (0, 0)
patches[0].indices: Window(col_off=0, row_off=0, width=256, height=256)

Before running an operator, let’s compare the five SpatialWindow classes. Each Window.weights(geometry) returns an array shaped like the patch — the multiplier OverlapAdd uses on the way in (per-cell) and the denominator on the way out (sum-of-weights).

<Figure size 800x350 with 1 Axes>
          Boxcar: weights.shape: (128, 128)
            Hann: weights.shape: (128, 128)
    Tukey(α=0.5): weights.shape: (128, 128)
 Gaussian(σ=0.4): weights.shape: (128, 128)
<Figure size 1300x330 with 5 Axes>
<Figure size 400x400 with 2 Axes>

4. Per-chip operator: invert the gradient

A tiny Lambda operator that maps x → 0.5 - x. Inside each 256×256 chip it inverts the NIR-reflectance gradient — vegetation becomes dark, water becomes bright. Watching this run gives us a clean visual sanity check that the patches land in the right anchors and stitch back without seams.

len(out_patches): 90
out_patches[0].data.shape: (256, 256)
stitched.shape: (3935, 1599)
<Figure size 1300x900 with 2 Axes>

5. Composition with Sequential

Same loop expressed as a three-operator linear pipeline using the gz.patch_ops wrappers. This is how the SpatialPatcher slots into the broader composition algebra.

result.shape: (3935, 1599)
`Sequential` and the manual loop produce identical output ✓

6. Hann window + overlap

Swap to a Hann window with stride < patch size. The OverlapAdd aggregation now normalises by the accumulated weights so the seams blend cleanly. Watch the seam between adjacent chips — it disappears entirely.

out2.shape: (3935, 1599)
<Figure size 700x1100 with 1 Axes>

7. get_config() round-trip

Every axis (and the SpatialPatcher itself) returns a JSON-serialisable get_config() dict — the audit-trail artifact that lets you persist a pipeline as YAML or hash it for regulatory reproducibility.

{
  "geometry": {
    "class": "SpatialRectangular",
    "config": {
      "size": [
        256,
        256
      ]
    }
  },
  "sampler": {
    "class": "SpatialRegularStride",
    "config": {
      "step": 256
    }
  },
  "window": {
    "class": "SpatialBoxcar",
    "config": {}
  },
  "aggregation": {
    "class": "SpatialOverlapAdd",
    "config": {
      "streaming": false,
      "target_path": null,
      "chunks": null,
      "normalize_by_window": true
    }
  }
}

Where next

  • 02_geometries — the five geometry classes (Rectangular, SphericalCap, KNNGraph, RadiusGraph, PolygonIntersection) on real heterogeneous substrates.
  • 03_sampling — the sampler gallery (RegularStride, JitteredStride, Random, PoissonDisk, Explicit).
  • 06_streaming — same patcher pattern but at full S2 tile scale (~10980² px) using a zarr-backed SpatialOverlapAdd.
  • ../05_patching_grids — the applied walkthrough version: NDVI over Lake Tahoe with a real per-chip operator stack.