meteor_maths.sampling.sampling module#
Module of resampling functions
- class meteor_maths.sampling.sampling.RegridCache(x_source: ndarray, y_source: ndarray, x_target: ndarray, y_target: ndarray, regular_grid: bool = False)[source]#
Bases:
objectNearest-neighbour
Resampler: precomputed regridding state for a fixed pair of source/target grids - the KDTree over the target grid, the mapping of each source pixel to its nearest target pixel, and the expected-source-count threshold (n_min_source) used to flag under-sampled target pixels. Note that which pixels actually end up flagged invalid also depends on the data being regridded (a NaN source pixel doesn’t count towards a bin’s contributing-pixel count), so that final mask is computed per data array inregrid(), not cached here.Building a RegridCache is the expensive part of nearest-neighbour resampling. Build one instance for a given (x_source, y_source, x_target, y_target) and reuse it via
regrid()to resample multiple data arrays (e.g. different variables, or repeated calls in a loop) on the same grids, instead of recomputing the KDTree and index mapping each time - including across separate calls toresample(), by building a RegridCache upfront and passing it asresampler=.Two ways of deciding a target pixel’s own footprint (used both to decide which source pixels contribute to it, and how many are required for it to be considered valid) are available, via
regular_grid:regular_grid=True: a single grid-wide spacing estimate (median pixel step along each axis) is used for every target pixel. Cheap, but only accurate if both grids really do have uniform, axis-aligned spacing throughout.regular_grid=False(default): each target pixel’s footprint is estimated from its own array-adjacent neighbours instead of a single grid-wide value, and its expected source count from the local source point density around it. Correct for grids whose resolution varies across their extent (e.g. finer in one region than another, or anisotropic - different spacing in x than in y), at the cost of an extra KDTree query. Note this still assumes both grids are laid out as a structured 2D grid (as fromnumpy.meshgrid) - a genuinely rotated or scattered grid is not fully accounted for, since the footprint check is still axis-aligned.
Both modes fall back gracefully on a grid that’s degenerate (has only one distinct coordinate) along one axis - e.g. a single-column/single-row source or target - by treating that axis as carrying no width/density information rather than as zero width (see
_expected_source_count()).The (more expensive, source-density) part of this estimate is only computed lazily, the first time validity is actually needed - so it’s never paid for if you only ever call
regrid()withmask_invalid=False.- property n_min_source#
- regrid(data: ndarray, mask_invalid: bool = True) tuple[ndarray, ndarray | None][source]#
Resample 2D data onto this cache’s target grid, by averaging the source pixels nearest to each target pixel.
- Parameters:
data – source data as a 2D array, matching the source grid shape this cache was built from
mask_invalid – boolean for setting invalid target pixels (fewer than n_min_source non-NaN contributing source pixels) to nan. If False, validity is not computed at all (it can be the expensive part of the estimate for irregular grids), and None is returned in its place.
- Returns:
resampled 2D data, boolean mask that is True where a target pixel is valid (or None if mask_invalid is False)
- class meteor_maths.sampling.sampling.Resampler(*args, **kwargs)[source]#
Bases:
ProtocolCommon interface for resampling backends usable by
resample().A resampler is built once for a fixed pair of source/target grids (via whatever constructor arguments that particular algorithm needs), and can then be reused to regrid several 2D data arrays sharing those grids - e.g. different variables, or repeated calls in a loop - without redoing the expensive part (KDTree construction, triangulation, or whatever else a given algorithm needs to set up) each time.
resample()only ever callsregrid(). It has no knowledge of which concrete algorithm is in play, or of that algorithm’s own constructor arguments (e.g.RegridCache’sregular_grid) - so adding a new resampling method (e.g. a scipy-based interpolator) means writing a class that satisfies this interface and registering it in_RESAMPLERS, with no changes toresample()itself. Structural (Protocol) typing is used rather than a base class, so a resampler doesn’t need to inherit from anything - it just needs a matchingregridmethod.- regrid(data: ndarray, mask_invalid: bool = True) tuple[ndarray, ndarray | None][source]#
Resample 2D source data (matching the source grid this resampler was built for) onto its target grid.
- Parameters:
data – source data as a 2D array
mask_invalid – whether to compute and apply a validity mask, setting invalid target pixels to nan. Implementations should skip computing validity entirely when this is False, if that’s the expensive part of the algorithm.
- Returns:
resampled 2D data, and a boolean mask that is True where a target pixel is valid (or None if mask_invalid is False)
- meteor_maths.sampling.sampling.nearest_neighbour_resample(data: ndarray, x_source: ndarray, y_source: ndarray, x_target: ndarray, y_target: ndarray, mask_invalid: bool = True, regular_grid: bool = False) tuple[ndarray, ndarray | None][source]#
Resample 2D data by averaging nearest neighbour values. Invalid pixels set to nan if mask=True - invalid pixels defined as those with fewer than the automatically estimated expected number of source pixels binned to form the sample.
For repeated resampling of different data on the same source/target grids (e.g. multiple variables, or in a loop), build a
RegridCacheonce and use itsregrid()method instead, to avoid rebuilding the KDTree and nearest-neighbour mapping on every call.- Parameters:
data – data as 2D array
x_source – x coordinates of source grid
y_source – y coordinates of source grid
x_target – x coordinates of target grid
y_target – y coordinates of target grid
mask_invalid – boolean for setting invalid edge pixels to nan. If False, validity is never computed (skipping the more expensive part of the estimate for irregular grids)
regular_grid – whether both grids have uniform, axis-aligned pixel spacing - see
RegridCache
- Returns:
resampled 2D data, standard deviation of samples
- meteor_maths.sampling.sampling.resample(var: str, ds: Dataset, x_source: ndarray, y_source: ndarray, x_target: ndarray, y_target: ndarray, mask_invalid: bool = True, method: str = 'nearest_neighbour', resampler: Resampler | None = None) ndarray[source]#
Resample variable data.
This function itself is resampling-algorithm-agnostic: it only handles picking out the right 2D slices of
var(for 2D/3D/4D data), lining their axes up with x_source/y_source’s own layout, and building the output array - the actual resampling for each 2D slice is delegated to aResampler(seemethod/resamplerbelow). To add a new resampling algorithm, write a class satisfying theResamplerinterface and register it in_RESAMPLERS; this function does not need to change.- Parameters:
var – variable to resample
ds – dataset containing data to resample
x_source – x coordinates for source data
y_source – y coordinates for source data
x_target – x coordinates for target data
y_target – y coordinates for target data
mask_invalid – boolean for setting invalid edge pixels to nan
method – name of a registered resampling algorithm to use (see
_RESAMPLERS), built with its default settings. Ignored ifresampleris given.resampler – an optional, already-built
Resampler(e.g. aRegridCache) for the given source/target grids. Passing one in is how to use non-default settings for a given algorithm (e.g.RegridCache(..., regular_grid=True)) - those settings are specific to each algorithm, so aren’t exposed as arguments here. Building one once and reusing it also avoids recomputing e.g. a KDTree on every call, when resampling several variables that share the same grids.
- Returns:
array of resampled variable data