Skip to content

Samplers API

Auto-generated from source — a multi-backend Keras port of torchgeo.samplers. Samplers operate purely on geospatial indices (pandas/geopandas/shapely) and yield plain Python slices, so they have no dependency on the active Keras tensor backend at all. See Data for a narrative guide.

Base classes

keras_climate.samplers.base.GeoSampler

Bases: ABC

Abstract base class for sampling from a :class:~keras_climate.datasets.GeoDataset.

Returns a GeoSlice that can uniquely index any :class:~keras_climate.datasets.GeoDataset.

__iter__ abstractmethod

__iter__()

Iterate over generated sample locations for each epoch.

Source code in keras_climate/samplers/base.py
28
29
30
@abc.abstractmethod
def __iter__(self):
    """Iterate over generated sample locations for each epoch."""

keras_climate.samplers.base.SpatialSampler

SpatialSampler(dataset, *, roi=None)

Bases: GeoSampler

Abstract base class for all spatial sampling strategies.

Initialize a new SpatialSampler instance.

Args: dataset: Dataset to sample from. roi: Region of interest to sample from (defaults to the bounds of dataset.index).

Source code in keras_climate/samplers/base.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def __init__(self, dataset, *, roi=None):
    """Initialize a new SpatialSampler instance.

    Args:
        dataset: Dataset to sample from.
        roi: Region of interest to sample from (defaults to the bounds
            of ``dataset.index``).
    """
    # Create one single MultiPolygon of all objects
    # Allows all locations to be equally weighted, regardless of # time stamps
    self.geometry = dataset.index.geometry.union_all()
    self.bounds = self.geometry.bounds
    self.res = dataset.res

    if roi is not None:
        self.geometry &= roi

strategy abstractmethod property

strategy

Sampling strategy: one of 'random' or 'sequential'.

This distinction only matters when combining samplers via :class:SpatioTemporalSampler, where either a zip (random) or product (sequential) of all sample locations is taken during each epoch.

__iter__ abstractmethod

__iter__()

Iterate over generated sample locations for each epoch.

Source code in keras_climate/samplers/base.py
70
71
72
@abc.abstractmethod
def __iter__(self):
    """Iterate over generated sample locations for each epoch."""

__matmul__

__matmul__(other)

Compute the product of two samplers.

Source code in keras_climate/samplers/base.py
74
75
76
def __matmul__(self, other):
    """Compute the product of two samplers."""
    return SpatioTemporalSampler(self, other)

plot

plot()

Plot a visualization of the sampling strategy.

Source code in keras_climate/samplers/base.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def plot(self):
    """Plot a visualization of the sampling strategy."""
    geometry = self.geometry
    assert isinstance(geometry, (Polygon, MultiPolygon))
    xmin, ymin, xmax, ymax = geometry.bounds

    fig, ax = plt.subplots()
    ax.set_title(self.__class__.__name__)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    ax.axis("equal")

    def init_func():
        return shapely.plotting.plot_polygon(geometry, ax=ax)

    def func(index):
        x, y = index
        xy = (x.start, y.start)
        width = x.stop - x.start
        height = y.stop - y.start
        patch = Rectangle(xy, width, height, color="tab:orange", alpha=0.3)
        ax.add_patch(patch)
        return [patch]

    return FuncAnimation(fig, func=func, frames=self, init_func=init_func)

keras_climate.samplers.base.SpatioTemporalSampler

SpatioTemporalSampler(spatial_sampler, temporal_sampler)

Bases: GeoSampler

Product of a spatial and a temporal sampler.

Initialize a new SpatioTemporalSampler instance.

Args: spatial_sampler: A spatial sampling strategy. temporal_sampler: A temporal sampling strategy.

Source code in keras_climate/samplers/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
def __init__(self, spatial_sampler, temporal_sampler):
    """Initialize a new SpatioTemporalSampler instance.

    Args:
        spatial_sampler: A spatial sampling strategy.
        temporal_sampler: A temporal sampling strategy.
    """
    self.spatial_sampler = spatial_sampler
    self.temporal_sampler = temporal_sampler

    if (
        self.spatial_sampler.strategy == "random"
        and self.temporal_sampler.strategy == "sequential"
    ):
        msg = "random_sampler @ sequential_sampler may result in a different "
        msg += "number of samples per epoch if different random locations have "
        msg += "a different number of timestamps"
        warnings.warn(msg, UserWarning)

plot

plot()

Plot a visualization of the sampling strategy.

Source code in keras_climate/samplers/base.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def plot(self):
    """Plot a visualization of the sampling strategy."""
    spatial = self.spatial_sampler
    temporal = self.temporal_sampler

    xmin, ymin, xmax, ymax = spatial.geometry.bounds
    tmin = temporal.index.index.left.min().timestamp()
    tmax = temporal.index.index.right.max().timestamp()

    fig = plt.figure()
    ax = fig.add_subplot(projection="3d")
    ax.set_title(f"{spatial.__class__.__name__} @ {temporal.__class__.__name__}")
    ax.set(xlabel="x", ylabel="y", zlabel="t")
    ax.set(xlim=[xmin, xmax], ylim=[ymin, ymax], zlim=[tmin, tmax])
    ax.set_aspect("equalxy")

    def init_func():
        verts = []
        for index, data in temporal.index.iterrows():
            x, y = data["geometry"].exterior.coords.xy
            tmin = index.left.timestamp()
            tmax = index.right.timestamp()
            t = np.array([tmin, tmax])
            verts.extend(prism(x, y, t))
        poly = Poly3DCollection(verts, color="tab:blue", alpha=0.3)
        ax.add_collection3d(poly)
        return (poly,)

    def func(index):
        x = np.array(
            [
                index[0].start,
                index[0].start,
                index[0].stop,
                index[0].stop,
                index[0].start,
            ]
        )
        y = np.array(
            [
                index[1].start,
                index[1].stop,
                index[1].stop,
                index[1].start,
                index[1].start,
            ]
        )
        t = np.array([index[2].start.timestamp(), index[2].stop.timestamp()])
        verts = prism(x, y, t)
        poly = Poly3DCollection(verts, color="tab:orange", alpha=0.3)
        ax.add_collection3d(poly)
        return (poly,)

    return FuncAnimation(fig, func=func, frames=self, init_func=init_func)

keras_climate.samplers.base.TemporalSampler

TemporalSampler(dataset, *, toi=None)

Bases: GeoSampler

Abstract base class for all temporal sampling strategies.

Initialize a new TemporalSampler instance.

Args: dataset: Dataset to sample from. toi: Time of interest to sample from (defaults to the bounds of dataset.index).

Source code in keras_climate/samplers/base.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def __init__(self, dataset, *, toi=None):
    """Initialize a new TemporalSampler instance.

    Args:
        dataset: Dataset to sample from.
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
    """
    self.index = dataset.index

    if toi is not None:
        import pandas as pd

        tmin = np.maximum(toi.left.to_datetime64(), self.index.index.left)
        tmax = np.minimum(toi.right.to_datetime64(), self.index.index.right)
        valid = tmax >= tmin
        tmin = tmin[valid]
        tmax = tmax[valid]
        self.index = self.index[valid]
        self.index.index = pd.IntervalIndex.from_arrays(
            tmin, tmax, closed="both", name="datetime"
        )

strategy abstractmethod property

strategy

Sampling strategy: one of 'random' or 'sequential'.

plot

plot()

Plot a visualization of the sampling strategy.

Source code in keras_climate/samplers/base.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def plot(self):
    """Plot a visualization of the sampling strategy."""
    tmin = self.index.index.left.min()
    tmax = self.index.index.right.max()

    fig, ax = plt.subplots()
    ax.set_title(self.__class__.__name__)
    ax.set_xlabel("t")
    ax.set_xlim(tmin, tmax)
    ax.yaxis.set_visible(False)
    ax.spines[["left", "top", "right"]].set_visible(False)
    fig.autofmt_xdate()

    def init_func():
        patches = []
        for interval in self.index.index:
            patch = ax.axvspan(
                interval.left, interval.right, color="tab:blue", alpha=0.3
            )
            patches.append(patch)
        return patches

    def func(index):
        _, _, t = index
        patch = ax.axvspan(t.start, t.stop, color="tab:orange", alpha=0.3)
        ax.add_patch(patch)
        return [patch]

    return FuncAnimation(fig, func=func, frames=self, init_func=init_func)

keras_climate.samplers.batch.BatchGeoSampler

BatchGeoSampler(dataset, roi=None, toi=None)

Bases: ABC

Abstract base class for sampling batches from a :class:~keras_climate.datasets.GeoDataset.

Initialize a new Sampler instance.

Args: dataset: dataset to index from roi: region of interest to sample from (defaults to the bounds of dataset.index) toi: time of interest to sample from (defaults to the bounds of dataset.index)

Source code in keras_climate/samplers/batch.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def __init__(self, dataset, roi=None, toi=None):
    """Initialize a new Sampler instance.

    Args:
        dataset: dataset to index from
        roi: region of interest to sample from (defaults to the bounds
            of ``dataset.index``)
        toi: time of interest to sample from (defaults to the bounds of
            ``dataset.index``)
    """
    self.index = dataset.index
    self.res = dataset.res

    if roi:
        self.roi = roi
        self.index = self.index.clip(roi)
    else:
        x, y, t = dataset.bounds
        self.roi = shapely.box(x.start, y.start, x.stop, y.stop)

    if toi:
        self.toi = toi
        self.index = self.index.iloc[self.index.index.overlaps(toi)]
        tmin = np.maximum(self.index.index.left, np.datetime64(toi.left))
        tmax = np.minimum(self.index.index.right, np.datetime64(toi.right))
        self.index.index = pd.IntervalIndex.from_arrays(
            tmin, tmax, closed="both", name="datetime"
        )
    else:
        x, y, t = dataset.bounds
        self.toi = pd.Interval(t.start, t.stop)

__iter__ abstractmethod

__iter__()

Return a batch of indices of a dataset.

Source code in keras_climate/samplers/batch.py
48
49
50
@abc.abstractmethod
def __iter__(self):
    """Return a batch of indices of a dataset."""

Single-item samplers

keras_climate.samplers.single.GridGeoSampler

GridGeoSampler(dataset, size, stride=None, roi=None, toi=None, units=Units.PIXELS)

Bases: GeoSampler

Samples elements in a grid-like fashion.

.. deprecated:: Use :class:~keras_climate.samplers.GriddedPatchSampler instead.

This is particularly useful during evaluation when you want to make predictions for an entire region of interest. You want to minimize the amount of redundant computation by minimizing overlap between chips. Usually the stride should be slightly smaller than the chip size such that each chip has some small overlap with surrounding chips.

Initialize a new Sampler instance.

The size and stride arguments can either be:

  • a single float - in which case the same value is used for the height and width dimension
  • a tuple of two floats - in which case, the first float is used for the height dimension, and the second float for the width dimension

Args: dataset: dataset to index from size: dimensions of each patch stride: distance to skip between each patch (defaults to size) roi: region of interest to sample from (defaults to the bounds of dataset.index) toi: time of interest to sample from (defaults to the bounds of dataset.index) units: defines if size and stride are in pixel or CRS units

Source code in keras_climate/samplers/single.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def __init__(
    self, dataset, size, stride=None, roi=None, toi=None, units=Units.PIXELS
):
    """Initialize a new Sampler instance.

    The ``size`` and ``stride`` arguments can either be:

    * a single ``float`` - in which case the same value is used for the
      height and width dimension
    * a ``tuple`` of two floats - in which case, the first *float* is
      used for the height dimension, and the second *float* for the
      width dimension

    Args:
        dataset: dataset to index from
        size: dimensions of each patch
        stride: distance to skip between each patch (defaults to *size*)
        roi: region of interest to sample from (defaults to the bounds
            of ``dataset.index``)
        toi: time of interest to sample from (defaults to the bounds of
            ``dataset.index``)
        units: defines if ``size`` and ``stride`` are in pixel or CRS units
    """
    super().__init__(dataset, roi, toi)
    self.size = _to_tuple(size)
    if stride is not None:
        self.stride = _to_tuple(stride)
    else:
        self.stride = self.size

    if units == Units.PIXELS:
        self.size = (self.size[0] * self.res[1], self.size[1] * self.res[0])
        self.stride = (self.stride[0] * self.res[1], self.stride[1] * self.res[0])

    self.length = 0
    for i in range(len(self.index)):
        bounds = self.index.geometry.iloc[i].bounds
        xmin, ymin, xmax, ymax = bounds
        if xmax - xmin < self.size[1] or ymax - ymin < self.size[0]:
            continue
        rows, cols = tile_to_chips(bounds, self.size, self.stride)
        self.length += rows * cols

keras_climate.samplers.single.PreChippedGeoSampler

PreChippedGeoSampler(dataset, roi=None, toi=None, shuffle=False, generator=None)

Bases: GeoSampler

Samples entire files at a time.

.. deprecated:: Use :class:~keras_climate.samplers.RandomPatchSampler instead.

This is particularly useful for datasets that contain geospatial metadata and subclass :class:~keras_climate.datasets.GeoDataset but have already been pre-processed into chips.

Initialize a new Sampler instance.

Args: dataset: dataset to index from roi: region of interest to sample from (defaults to the bounds of dataset.index) toi: time of interest to sample from (defaults to the bounds of dataset.index) shuffle: if True, reshuffle data at every epoch generator: pseudo-random number generator (PRNG) used in combination with shuffle

Source code in keras_climate/samplers/single.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def __init__(self, dataset, roi=None, toi=None, shuffle=False, generator=None):
    """Initialize a new Sampler instance.

    Args:
        dataset: dataset to index from
        roi: region of interest to sample from (defaults to the bounds
            of ``dataset.index``)
        toi: time of interest to sample from (defaults to the bounds of
            ``dataset.index``)
        shuffle: if True, reshuffle data at every epoch
        generator: pseudo-random number generator (PRNG) used in
            combination with shuffle
    """
    super().__init__(dataset, roi, toi)
    self.shuffle = shuffle
    self.generator = np.random.default_rng(generator)

keras_climate.samplers.single.RandomGeoSampler

RandomGeoSampler(dataset, size, length=None, roi=None, toi=None, units=Units.PIXELS, generator=None)

Bases: GeoSampler

Samples elements from a region of interest randomly.

.. deprecated:: Use :class:~keras_climate.samplers.RandomPatchSampler instead.

This is particularly useful during training when you want to maximize the size of the dataset and return as many random chips as possible. Note that randomly sampled chips may overlap. This sampler is not recommended for use with tile-based datasets; use :class:RandomBatchGeoSampler instead.

Initialize a new Sampler instance.

The size argument can either be:

  • a single float - in which case the same value is used for the height and width dimension
  • a tuple of two floats - in which case, the first float is used for the height dimension, and the second float for the width dimension

Args: dataset: dataset to index from size: dimensions of each patch length: number of random samples to draw per epoch (defaults to approximately the maximal number of non-overlapping chips of size size that could be sampled from the dataset) roi: region of interest to sample from (defaults to the bounds of dataset.index) toi: time of interest to sample from (defaults to the bounds of dataset.index) units: defines if size is in pixel or CRS units generator: pseudo-random number generator (PRNG)

Source code in keras_climate/samplers/single.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def __init__(
    self,
    dataset,
    size,
    length=None,
    roi=None,
    toi=None,
    units=Units.PIXELS,
    generator=None,
):
    """Initialize a new Sampler instance.

    The ``size`` argument can either be:

    * a single ``float`` - in which case the same value is used for the
      height and width dimension
    * a ``tuple`` of two floats - in which case, the first *float* is
      used for the height dimension, and the second *float* for the
      width dimension

    Args:
        dataset: dataset to index from
        size: dimensions of each patch
        length: number of random samples to draw per epoch (defaults to
            approximately the maximal number of non-overlapping chips of
            size ``size`` that could be sampled from the dataset)
        roi: region of interest to sample from (defaults to the bounds
            of ``dataset.index``)
        toi: time of interest to sample from (defaults to the bounds of
            ``dataset.index``)
        units: defines if ``size`` is in pixel or CRS units
        generator: pseudo-random number generator (PRNG)
    """
    super().__init__(dataset, roi, toi)
    self.size = _to_tuple(size)

    if units == Units.PIXELS:
        self.size = (self.size[0] * self.res[1], self.size[1] * self.res[0])

    self.generator = np.random.default_rng(generator)
    self.length = 0
    self.bounds = []
    self.intervals = []
    areas = []
    for hit in range(len(self.index)):
        bounds = self.index.geometry.iloc[hit].bounds
        xmin, ymin, xmax, ymax = bounds
        tmin, tmax = self.index.index[hit].left, self.index.index[hit].right
        if xmax - xmin >= self.size[1] and ymax - ymin >= self.size[0]:
            if xmax > xmin and ymax > ymin:
                rows, cols = tile_to_chips(bounds, self.size)
                self.length += rows * cols
            else:
                self.length += 1
            self.bounds.append(bounds)
            self.intervals.append(pd.Interval(tmin, tmax))
            areas.append((xmax - xmin) * (ymax - ymin))

    if length is not None:
        self.length = length

    # Sampling requires float probabilities > 0
    self.areas = np.array(areas, dtype="float64")
    if self.areas.sum() == 0:
        self.areas += 1

keras_climate.samplers.spatial.GriddedPatchSampler

GriddedPatchSampler(dataset, *, size, stride=None, roi=None, units=Units.PIXELS)

Bases: SpatialSampler

Sample locations from a region of interest in a grid-like fashion.

This is particularly useful during evaluation when you want to make predictions for an entire region of interest. You want to minimize the amount of redundant computation by minimizing overlap between chips.

Initialize a new GriddedPatchSampler instance.

The size and stride arguments can either be:

  • a single float - in which case the same value is used for the height and width dimension
  • a tuple of two floats - in which case, the first float is used for the height dimension, and the second float for the width dimension

Args: dataset: Dataset to sample from. size: Dimensions of each patch. stride: Distance to skip between each patch (defaults to size). roi: Region of interest to sample from (defaults to the bounds of dataset.index). units: Defines if size and stride are in pixel or CRS units.

Source code in keras_climate/samplers/spatial.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def __init__(self, dataset, *, size, stride=None, roi=None, units=Units.PIXELS):
    """Initialize a new GriddedPatchSampler instance.

    The ``size`` and ``stride`` arguments can either be:

    * a single ``float`` - in which case the same value is used for the
      height and width dimension
    * a ``tuple`` of two floats - in which case, the first *float* is
      used for the height dimension, and the second *float* for the
      width dimension

    Args:
        dataset: Dataset to sample from.
        size: Dimensions of each patch.
        stride: Distance to skip between each patch (defaults to
            *size*).
        roi: Region of interest to sample from (defaults to the bounds
            of ``dataset.index``).
        units: Defines if ``size`` and ``stride`` are in pixel or CRS
            units.
    """
    super().__init__(dataset, roi=roi)

    self.size = _to_tuple(size)
    self.stride = _to_tuple(stride or size)

    # Convert from pixel units to CRS units
    if units == Units.PIXELS:
        self.size = (self.size[0] * dataset.res[1], self.size[1] * dataset.res[0])
        self.stride = (
            self.stride[0] * dataset.res[1],
            self.stride[1] * dataset.res[0],
        )

keras_climate.samplers.spatial.RandomPatchSampler

RandomPatchSampler(dataset, *, size, length=None, roi=None, units=Units.PIXELS, generator=None)

Bases: SpatialSampler

Sample locations from a region of interest randomly.

This is particularly useful during training when you want to maximize the size of the dataset and return as many random chips as possible. Note that randomly sampled chips may overlap.

Initialize a new RandomPatchSampler instance.

The size argument can either be:

  • a single float - in which case the same value is used for the height and width dimension
  • a tuple of two floats - in which case, the first float is used for the height dimension, and the second float for the width dimension

Args: dataset: Dataset to sample from. size: Dimensions of each patch. length: Number of random samples to draw per epoch (defaults to approximately the maximal number of non-overlapping chips of size size that could be sampled from the dataset). roi: Region of interest to sample from (defaults to the bounds of dataset.index). units: Defines if size is in pixel or CRS units. generator: Pseudo-random number generator (PRNG).

Source code in keras_climate/samplers/spatial.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(
    self, dataset, *, size, length=None, roi=None, units=Units.PIXELS, generator=None
):
    """Initialize a new RandomPatchSampler instance.

    The ``size`` argument can either be:

    * a single ``float`` - in which case the same value is used for the
      height and width dimension
    * a ``tuple`` of two floats - in which case, the first *float* is
      used for the height dimension, and the second *float* for the
      width dimension

    Args:
        dataset: Dataset to sample from.
        size: Dimensions of each patch.
        length: Number of random samples to draw per epoch (defaults to
            approximately the maximal number of non-overlapping chips of
            size ``size`` that could be sampled from the dataset).
        roi: Region of interest to sample from (defaults to the bounds
            of ``dataset.index``).
        units: Defines if ``size`` is in pixel or CRS units.
        generator: Pseudo-random number generator (PRNG).
    """
    super().__init__(dataset, roi=roi)

    self.size = _to_tuple(size)
    self.generator = np.random.default_rng(generator)

    # Convert from pixel units to CRS units
    if units == Units.PIXELS:
        self.size = (self.size[0] * dataset.res[1], self.size[1] * dataset.res[0])

    # Default to approximate number of non-overlapping patches
    total_area = shapely.area(self.geometry)
    patch_area = self.size[0] * self.size[1]
    self._length = length or convolution_arithmetic(total_area, patch_area)

    # Erosion to avoid out-of-bounds sampling
    # Purposefully conservative radius calculation
    distance = math.sqrt((self.size[0] / 2) ** 2 + (self.size[1] / 2) ** 2)
    self.series = GeoSeries([shapely.buffer(self.geometry, -distance)])

Batch samplers

keras_climate.samplers.batch.RandomBatchGeoSampler

RandomBatchGeoSampler(dataset, size, batch_size, length=None, roi=None, toi=None, units=Units.PIXELS, generator=None)

Bases: BatchGeoSampler

Samples batches of elements from a region of interest randomly.

.. deprecated:: Use :class:~keras_climate.samplers.RandomPatchSampler instead.

This is particularly useful during training when you want to maximize the size of the dataset and return as many random chips as possible. Note that randomly sampled chips may overlap.

Initialize a new Sampler instance.

The size argument can either be:

  • a single float - in which case the same value is used for the height and width dimension
  • a tuple of two floats - in which case, the first float is used for the height dimension, and the second float for the width dimension

Args: dataset: dataset to index from size: dimensions of each patch batch_size: number of samples per batch length: number of samples per epoch (defaults to approximately the maximal number of non-overlapping chips of size size that could be sampled from the dataset) roi: region of interest to sample from (defaults to the bounds of dataset.index) toi: time of interest to sample from (defaults to the bounds of dataset.index) units: defines if size is in pixel or CRS units generator: pseudo-random number generator (PRNG)

Source code in keras_climate/samplers/batch.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def __init__(
    self,
    dataset,
    size,
    batch_size,
    length=None,
    roi=None,
    toi=None,
    units=Units.PIXELS,
    generator=None,
):
    """Initialize a new Sampler instance.

    The ``size`` argument can either be:

    * a single ``float`` - in which case the same value is used for the
      height and width dimension
    * a ``tuple`` of two floats - in which case, the first *float* is
      used for the height dimension, and the second *float* for the
      width dimension

    Args:
        dataset: dataset to index from
        size: dimensions of each patch
        batch_size: number of samples per batch
        length: number of samples per epoch (defaults to approximately
            the maximal number of non-overlapping chips of size ``size``
            that could be sampled from the dataset)
        roi: region of interest to sample from (defaults to the bounds
            of ``dataset.index``)
        toi: time of interest to sample from (defaults to the bounds of
            ``dataset.index``)
        units: defines if ``size`` is in pixel or CRS units
        generator: pseudo-random number generator (PRNG)
    """
    super().__init__(dataset, roi, toi)
    self.size = _to_tuple(size)
    self.generator = np.random.default_rng(generator)

    if units == Units.PIXELS:
        self.size = (self.size[0] * self.res[1], self.size[1] * self.res[0])

    self.batch_size = batch_size
    self.length = 0
    self.bounds = []
    self.intervals = []
    areas = []
    for hit in range(len(self.index)):
        bounds = self.index.geometry.iloc[hit].bounds
        xmin, ymin, xmax, ymax = bounds
        tmin, tmax = self.index.index[hit].left, self.index.index[hit].right
        if xmax - xmin >= self.size[1] and ymax - ymin >= self.size[0]:
            if xmax > xmin and ymax > ymin:
                rows, cols = tile_to_chips(bounds, self.size)
                self.length += rows * cols
            else:
                self.length += 1
            self.bounds.append(bounds)
            self.intervals.append(pd.Interval(tmin, tmax))
            areas.append((xmax - xmin) * (ymax - ymin))
    if length is not None:
        self.length = length

    # Sampling requires float probabilities > 0
    self.areas = np.array(areas, dtype="float64")
    if self.areas.sum() == 0:
        self.areas += 1

Temporal samplers

keras_climate.samplers.temporal.RandomPeriodSampler

RandomPeriodSampler(dataset, *, freq, length=None, toi=None, generator=None)

Bases: TemporalSampler

Random sampling of fixed-length fixed windows (:class:~pandas.Period).

Initialize a new RandomPeriodSampler instance.

Args: dataset: Dataset to sample from. freq: Temporal frequencies to sample. Accepts any valid pandas period alias. length: Number of random samples to draw per epoch (defaults to approximately the maximal number of non-overlapping periods that could be sampled from the dataset). toi: Time of interest to sample from (defaults to the bounds of dataset.index). generator: Pseudo-random number generator (PRNG).

Source code in keras_climate/samplers/temporal.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def __init__(self, dataset, *, freq, length=None, toi=None, generator=None):
    """Initialize a new RandomPeriodSampler instance.

    Args:
        dataset: Dataset to sample from.
        freq: Temporal frequencies to sample. Accepts any valid pandas
            period alias.
        length: Number of random samples to draw per epoch (defaults to
            approximately the maximal number of non-overlapping periods
            that could be sampled from the dataset).
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
        generator: Pseudo-random number generator (PRNG).
    """
    super().__init__(dataset, toi=toi)
    self.freq = freq
    left = self.index.index.left.min()
    right = self.index.index.right.max()
    period = Period(left, freq=freq)
    window = period.end_time - period.start_time
    self._length = length or convolution_arithmetic(right - left, window)
    self.generator = np.random.default_rng(generator)

keras_climate.samplers.temporal.RandomTimedeltaSampler

RandomTimedeltaSampler(dataset, *, delta, length=None, toi=None, generator=None)

Bases: TemporalSampler

Random sampling of fixed-length sliding windows (:class:~pandas.Timedelta).

Initialize a new RandomTimedeltaSampler instance.

Args: dataset: Dataset to sample from. delta: Duration of time. length: Number of random samples to draw per epoch (defaults to approximately the maximal number of non-overlapping intervals that could be sampled from the dataset). toi: Time of interest to sample from (defaults to the bounds of dataset.index). generator: Pseudo-random number generator (PRNG).

Source code in keras_climate/samplers/temporal.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(self, dataset, *, delta, length=None, toi=None, generator=None):
    """Initialize a new RandomTimedeltaSampler instance.

    Args:
        dataset: Dataset to sample from.
        delta: Duration of time.
        length: Number of random samples to draw per epoch (defaults to
            approximately the maximal number of non-overlapping
            intervals that could be sampled from the dataset).
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
        generator: Pseudo-random number generator (PRNG).
    """
    super().__init__(dataset, toi=toi)
    self.delta = delta
    left = self.index.index.left.min()
    right = self.index.index.right.max()
    self._length = length or convolution_arithmetic(right - left, delta)
    self.generator = np.random.default_rng(generator)

keras_climate.samplers.temporal.RandomTimestampSampler

RandomTimestampSampler(dataset, *, toi=None, generator=None)

Bases: TemporalSampler

Random sampling of single images (:class:~pandas.Timestamp).

Initialize a new RandomTimestampSampler instance.

Args: dataset: Dataset to sample from. toi: Time of interest to sample from (defaults to the bounds of dataset.index). generator: Pseudo-random number generator (PRNG).

Source code in keras_climate/samplers/temporal.py
16
17
18
19
20
21
22
23
24
25
26
def __init__(self, dataset, *, toi=None, generator=None):
    """Initialize a new RandomTimestampSampler instance.

    Args:
        dataset: Dataset to sample from.
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
        generator: Pseudo-random number generator (PRNG).
    """
    super().__init__(dataset, toi=toi)
    self.generator = np.random.default_rng(generator)

keras_climate.samplers.temporal.SequentialPeriodSampler

SequentialPeriodSampler(dataset, *, freq, toi=None)

Bases: TemporalSampler

Sequential sampling of fixed-length fixed windows (:class:~pandas.Period).

Initialize a new SequentialPeriodSampler instance.

Args: dataset: Dataset to sample from. freq: Temporal frequencies to sample. Accepts any valid pandas period alias. toi: Time of interest to sample from (defaults to the bounds of dataset.index).

Source code in keras_climate/samplers/temporal.py
195
196
197
198
199
200
201
202
203
204
205
206
def __init__(self, dataset, *, freq, toi=None):
    """Initialize a new SequentialPeriodSampler instance.

    Args:
        dataset: Dataset to sample from.
        freq: Temporal frequencies to sample. Accepts any valid pandas
            period alias.
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
    """
    super().__init__(dataset, toi=toi)
    self.freq = freq

keras_climate.samplers.temporal.SequentialTimedeltaSampler

SequentialTimedeltaSampler(dataset, *, delta, stride=None, toi=None)

Bases: TemporalSampler

Sequential sampling of fixed-length sliding windows (:class:~pandas.Timedelta).

Initialize a new SequentialTimedeltaSampler instance.

Args: dataset: Dataset to sample from. delta: Duration of time. stride: Duration to skip between each sample (defaults to delta). toi: Time of interest to sample from (defaults to the bounds of dataset.index).

Source code in keras_climate/samplers/temporal.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def __init__(self, dataset, *, delta, stride=None, toi=None):
    """Initialize a new SequentialTimedeltaSampler instance.

    Args:
        dataset: Dataset to sample from.
        delta: Duration of time.
        stride: Duration to skip between each sample (defaults to
            *delta*).
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
    """
    super().__init__(dataset, toi=toi)
    self.delta = delta
    self.stride = stride or delta

keras_climate.samplers.temporal.SequentialTimestampSampler

SequentialTimestampSampler(dataset, *, toi=None)

Bases: TemporalSampler

Sequential sampling of single images (:class:~pandas.Timestamp).

Source code in keras_climate/samplers/base.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def __init__(self, dataset, *, toi=None):
    """Initialize a new TemporalSampler instance.

    Args:
        dataset: Dataset to sample from.
        toi: Time of interest to sample from (defaults to the bounds of
            ``dataset.index``).
    """
    self.index = dataset.index

    if toi is not None:
        import pandas as pd

        tmin = np.maximum(toi.left.to_datetime64(), self.index.index.left)
        tmax = np.minimum(toi.right.to_datetime64(), self.index.index.right)
        valid = tmax >= tmin
        tmin = tmin[valid]
        tmax = tmax[valid]
        self.index = self.index[valid]
        self.index.index = pd.IntervalIndex.from_arrays(
            tmin, tmax, closed="both", name="datetime"
        )

Utilities

keras_climate.samplers.constants.Units

Bases: Enum

Enumeration defining units of size parameter.

Used by :class:~keras_climate.samplers.SpatialSampler.

keras_climate.samplers.utils.get_random_bounding_box

get_random_bounding_box(bounds, size, res, generator=None)

Returns a random bounding box within a given bounding box.

.. deprecated:: Use geopandas.GeoSeries.sample_points instead.

The size argument can either be:

* a single ``float`` - in which case the same value is used for the
  height and width dimension
* a ``tuple`` of two floats - in which case, the first *float* is
  used for the height dimension, and the second *float* for the
  width dimension

Args: bounds: the larger bounding box to sample from size: the size of the bounding box to sample res: the resolution of the image generator: pseudo-random number generator (PRNG), a :class:numpy.random.Generator or seed.

Returns: randomly sampled bounding box from the extent of the input

Source code in keras_climate/samplers/utils.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def get_random_bounding_box(bounds, size, res, generator=None):
    """Returns a random bounding box within a given bounding box.

    .. deprecated:: Use geopandas.GeoSeries.sample_points instead.

    The ``size`` argument can either be:

        * a single ``float`` - in which case the same value is used for the
          height and width dimension
        * a ``tuple`` of two floats - in which case, the first *float* is
          used for the height dimension, and the second *float* for the
          width dimension

    Args:
        bounds: the larger bounding box to sample from
        size: the size of the bounding box to sample
        res: the resolution of the image
        generator: pseudo-random number generator (PRNG), a
            :class:`numpy.random.Generator` or seed.

    Returns:
        randomly sampled bounding box from the extent of the input
    """
    rng = generator if isinstance(generator, np.random.Generator) else np.random.default_rng(generator)

    xmin, ymin, xmax, ymax = bounds
    t_size = _to_tuple(size)
    t_res = _to_tuple(res)

    # May be negative if bounding box is smaller than patch size
    width = (xmax - xmin - t_size[1]) / t_res[0]
    height = (ymax - ymin - t_size[0]) / t_res[1]

    # Use an integer multiple of res to avoid resampling
    xmin += int(rng.random() * width) * t_res[0]
    ymin += int(rng.random() * height) * t_res[1]

    xmax = xmin + t_size[1]
    ymax = ymin + t_size[0]

    return slice(xmin, xmax), slice(ymin, ymax)

keras_climate.samplers.utils.tile_to_chips

tile_to_chips(bounds, size, stride=None)

Compute number of chips that can be sampled from a tile.

Let :math:i be the size of the input tile. Let :math:k be the requested size of the output patch. Let :math:s be the requested stride. Let :math:o be the number of output chips sampled from each tile:

o = ceil((i - k) / s) + 1

We use ceiling instead of floor to include the final remaining chip in each row/column when bounds is not an integer multiple of stride.

.. deprecated:: Use :func:convolution_arithmetic instead.

Args: bounds: bounding box of tile size: size of output patch stride: stride with which to sample (defaults to size)

Returns: the number of rows/columns that can be sampled

Source code in keras_climate/samplers/utils.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def tile_to_chips(bounds, size, stride=None):
    r"""Compute number of chips that can be sampled from a tile.

    Let :math:`i` be the size of the input tile. Let :math:`k` be the
    requested size of the output patch. Let :math:`s` be the requested
    stride. Let :math:`o` be the number of output chips sampled from each
    tile:

        o = ceil((i - k) / s) + 1

    We use ceiling instead of floor to include the final remaining chip in
    each row/column when bounds is not an integer multiple of stride.

    .. deprecated:: Use :func:`convolution_arithmetic` instead.

    Args:
        bounds: bounding box of tile
        size: size of output patch
        stride: stride with which to sample (defaults to ``size``)

    Returns:
        the number of rows/columns that can be sampled
    """
    if stride is None:
        stride = size

    assert stride[0] > 0
    assert stride[1] > 0

    xmin, ymin, xmax, ymax = bounds

    rows = math.ceil((ymax - ymin - size[0]) / stride[0]) + 1
    cols = math.ceil((xmax - xmin - size[1]) / stride[1]) + 1

    return rows, cols