raster_tools.map_blocks#
- raster_tools.map_blocks(func, *rasters, dtype=None, null_value=None, meta=None, out_bands=None, return_mask=False, **kwargs)[source]#
Apply
funcblock-wise across one or more aligned rasters.Thin wrapper over
dask.array.map_blocks(). Each call tofuncreceives one block from each input raster, in input order. The outputRasteradopts its CRS, affine, and x/y coords from the first input; its mask is derived from the output data by default, or set explicitly byfuncviareturn_mask(see Notes).Available both as a module function and as a
Rastermethod:r1.map_blocks(func, r2, ...)is equivalent tomap_blocks(func, r1, r2, ...)– in the method form the calling raster is the first input. References to the “first input” below meanr1in either spelling.Per-block contract
Per-block kwargs (opt-in):
input_masks,input_null_values,block_info,block_id,out_null_value. Name any of these infunc’s signature to receive them per chunk; see below.By default the output Raster’s mask is rebuilt from the output data and the resolved output null value (
out_data == null_value, ornp.isnan(out_data)for NaN nulls) – write the sentinel only at cells you want masked. Cells your func happens to leave equal to the sentinel will appear masked even if you didn’t intend them to; cells you wanted masked but didn’t write the sentinel to will not. This is true regardless of the input rasters’ masks: input masks do not carry through unchanged. To set the output mask explicitly instead – decoupling nullness from the data values and avoiding both failure modes above – passreturn_mask=Trueand havefuncreturn a(data, mask)pair; seereturn_maskbelow.Always passed positionally:
func(*input_data, **kwargs)
where
input_datais a tuple of N NumPy blocks, one per input raster, in input order.The user can also opt in to receive per-block extras by including named parameters in
func’s signature. Detection mirrors dask’s ownblock_info=/block_id=mechanism and usesinspect.signature()(viadask.utils.has_keyword()). Recognized names:input_masks– tuple of Nnp.ndarray(bool) per-block mask arrays, parallel toinput_data.input_null_values– tuple of N scalars, each input raster’snull_value(Noneif unset).block_info– dask’s standard per-block info dict (seedask.array.map_blocks()).block_id– dask’s standard per-block index tuple: the block’schunk-location(equivalentlyblock_info[None]["chunk-location"]).Noneduring the meta inference call.out_null_value– scalar; the resolved output null value the wrapper will use to derive the output mask. Write this sentinel at cells you want masked. See “Output null value resolution” in Notes below.
A function whose only kwargs absorber is
**kwargs(e.g.def f(*args, **kwargs):) does NOT trigger any of these injections –inspect.signatureonly sees explicit parameter names. Name the kwargs you want.Reserved kwargs
input_masks,input_null_values,block_info,block_id, andout_null_valueare reserved. Passing any of them viamap_blocks’s own**kwargsraisesValueError– otherwise the wrapper’s injection would silently clobber the caller’s value.- Parameters
func (callable) – Per-block function. See “Per-block contract” above for the full signature rules. Must return an array-like that dask can ingest (NumPy ndarray, cupy ndarray, sparse array, etc.) with the same shape as a single input data block. For non-numpy backends, also pass
meta=so dask’s output meta is correct.*rasters (Raster or str) – The input rasters. In the method form the calling raster is the first input and
*rastersholds any additional ones; in the function form at least one is required. Path strings are accepted. Only the 3D shape is validated; CRS and affine are not checked. The caller is responsible for aligning inputs – typically viar2.reproject(r1.geobox). Inputs are auto-rechunked to the first input’s chunk structure, so same-shape inputs with differing chunking are handled. For a geo-aware variant that strictly requires matching grids, seegeo_map_blocks().dtype (dtype-like, optional) – Output dtype. When
None(default), dask infers the dtype by callingfuncon tiny meta samples (matches NumPy promotion for the typical elementwise case, and reflects any in-func cast such as.astype).null_value (scalar, optional) –
Output null value.
None(default): if there is exactly one input raster and the output dtype matches its dtype, the output inherits that input’s null value (preserves the sentinel for identity-like single-input ops). Otherwise, the value is a dtype-appropriate default fromraster_tools.masking.get_default_null_value()against the resolved output dtype – always representable, never overflows when the dtype changes.scalar: used as-is.
To force inheriting the first input’s null value across other cases, pass it explicitly:
null_value=r1.null_value.meta (array-like, optional) – Empty array with the desired output array type (e.g.
np.array((), dtype=np.float32), or a CuPy / sparse equivalent). Forwarded todask.array.map_blocks(). When provided, dask uses this as the output meta and skips the 0-shape sample call it would otherwise make to derive one – useful whenfunccannot tolerate 0-shape input. WhenNone(default), dask derives a NumPy meta by callingfuncon 0-shape inputs.out_bands (int, optional) –
Number of bands in the output.
None(default) is shape-preserving: the output has the same band count as the input andfuncreturns same-shape blocks. A positive integer letsfuncchange the band count (the y/x grid is unchanged), e.g.out_bands=3to emit three per-pixel statistics from a 1-band input, orout_bands=1to collapse a multi-band stack.funcmust return exactlyout_bandsbands per block (a mismatch raisesValueError). Consequences when set:The input band axis is collapsed to a single chunk so
funcsees every band of a spatial tile at once, and the y/x tiles are transparently re-sized so a block holds roughly onedaskarray.chunk-sizeworth of data – sofuncmay see different spatial tile sizes than the input’s native chunking.The output Raster is restored to the input’s original y/x chunking, with per-band band chunks and band coord
np.arange(out_bands) + 1.Passing
dtype=ormeta=is recommended, since dask’s 0-shape meta call still runs and a band-reshapingfuncmay not produce the right dtype/shape at 0-shape.
Setting
out_bandsto the input’s band count is a valid way to givefuncan all-bands view of each spatial tile: the default per-band blocking callsfunconce per band, whereas the band collapse above hands it every band of a tile at once. The count is unchanged, but the exact-out_bandsreturn guard and the y/x re-tile still apply.return_mask (bool, optional) –
If
True,funcmust return a(data, mask)pair instead of a single array. The returnedmask– not a sentinel comparison – defines the output Raster’s null cells.maskis a boolean array the same shape asdata; masked cells are set to the resolved output null value (burned in). Use this to decouple which cells are null from what value they hold, avoiding the sentinel-collision pitfalls described above. The default isFalse(sentinel-derived mask). Notes:The two arrays are carried through dask packed into a single NumPy structured-dtype block, then split apart again – an internal detail;
funcjust returns the plain pair.Passing
dtype=(ormeta=) describing the data dtype is recommended: it lets dask skip its 0-shape probe entirely. Without a hint the func must tolerate that probe (same caveat asout_bands).Requires NumPy-backed blocks (the structured-dtype carrier is a NumPy concept); cupy / sparse outputs are not supported with
return_mask. Composes without_bands(the returnedmaskmust also haveout_bandsbands).out_null_valueinjection is unnecessary (though harmless) whenreturn_mask=True.
**kwargs – Extra keyword arguments forwarded per-block to
func. The reserved names listed above are not allowed here.
- Returns
A new lazy Raster on the first input’s grid (with
out_bandsbands when that argument is set).- Return type
Notes
Cross-input CRS or affine mismatches are not validated; the caller is responsible.
The output mask is all-False if no null value is set (see the per-block contract above for how the mask is built when one is). To write the output mask directly, pass
return_mask=Trueand return a(data, mask)pair fromfunc(seereturn_mask).Dask invokes
funconce on 0-shape inputs to derive the output array meta – this happens whether or notdtype=is provided. Passmeta=to skip the call entirely;dtype=only skips the additional sample call dask would otherwise make to infer the output dtype, not the 0-shape meta call. (Exception: withreturn_mask=True,dtype=is folded into a structuredmeta=and so does skip the 0-shape meta call – seereturn_mask.) Most NumPy ops handle 0-shape inputs fine. If dask raisesdtype inference failed in map_blocks. Please specify the dtype explicitly using the dtype kwarg, that’s the sample call –dtype=per dask’s hint is usually enough; passmeta=instead if your func also can’t tolerate 0-shape inputs (dask silently swallows that crash, but your downstream output meta will be wrong).Output null value resolution
When
funcopts in toout_null_value, the wrapper resolves the scalar per-chunk without an extra dtype-inference pass:With
meta=ordtype=set, from that hint (dask leavesblock_info[None]["dtype"]asNonewhenevermeta=is set, which is why the hint is consulted first).Otherwise, from
block_info[None]["dtype"].
During dask’s meta inference call (where
block_infoisNone),out_null_valueis a typed zero of the first input’s dtype so funcs likenp.where(m, out_null_value, d)infer the same dtype as their input rather than collapsing to object. If your func’s output dtype depends on the specificout_null_valuescalar, passmeta=to skip dask’s 0-shape meta call entirely;dtype=skips only the additional sample call, not the meta call.Examples
Plain elementwise on one input (no special injection):
>>> def double(d): ... return d * 2 >>> doubled = r.map_blocks(double)
Mask-aware multi-input – write the null sentinel where either input was masked:
>>> def add_skip_nulls(a, b, *, input_masks, **kwargs): ... ma, mb = input_masks ... out = a + b ... out[ma | mb] = -9999 ... return out >>> summed = map_blocks( ... add_skip_nulls, r1, r2, null_value=-9999, ... )
Use dask’s per-block info inside
func:>>> def by_chunk_id(d, *, block_info, **kwargs): ... bi = block_info[None]["chunk-location"] ... ...
See also
raster_tools.geo_map_blocksGeo-aware variant that requires matching grids and hands
funcgeoreferencedxr.DataArrayblocks.raster_tools.Raster.reprojectPer-input alignment to a target grid; pass
r1.geoboxto alignr2tor1.