rio-tiler 8.0.4__py3-none-any.whl → 9.0.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rio_tiler/io/xarray.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import math
6
6
  import os
7
7
  import warnings
8
- from typing import Any, Dict, List, Tuple, cast
8
+ from typing import Any, cast
9
9
 
10
10
  import attr
11
11
  import numpy
@@ -81,7 +81,7 @@ class XarrayReader(BaseReader):
81
81
 
82
82
  tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
83
83
 
84
- _dims: List = attr.ib(init=False, factory=list)
84
+ _dims: list = attr.ib(init=False, factory=list)
85
85
 
86
86
  def __attrs_post_init__(self):
87
87
  """Set bounds and CRS."""
@@ -138,7 +138,7 @@ class XarrayReader(BaseReader):
138
138
  return self._maxzoom
139
139
 
140
140
  @property
141
- def band_descriptions(self) -> List[str]:
141
+ def band_descriptions(self) -> list[str]:
142
142
  """
143
143
  Return list of `band descriptions` in DataArray.
144
144
 
@@ -160,7 +160,7 @@ class XarrayReader(BaseReader):
160
160
  if coords_name:
161
161
  return [str(self.input.coords[coords_name[0]].data)]
162
162
 
163
- return [self.input.name or ""] # type: ignore
163
+ return [self.input.name or "b1"] # type: ignore
164
164
 
165
165
  return [str(band) for d in self._dims for band in self.input[d].values]
166
166
 
@@ -173,7 +173,8 @@ class XarrayReader(BaseReader):
173
173
  "crs": CRS_to_uri(self.crs) or self.crs.to_wkt(),
174
174
  "band_metadata": [(f"b{ix}", v) for ix, v in enumerate(metadata, 1)],
175
175
  "band_descriptions": [
176
- (f"b{ix}", v) for ix, v in enumerate(self.band_descriptions, 1)
176
+ (f"b{ix}", v or f"b{ix}")
177
+ for ix, v in enumerate(self.band_descriptions, 1)
177
178
  ],
178
179
  "dtype": str(self.input.dtype),
179
180
  "nodata_type": "Nodata" if self.input.rio.nodata is not None else "None",
@@ -193,11 +194,10 @@ class XarrayReader(BaseReader):
193
194
  def _sel_indexes(
194
195
  self,
195
196
  indexes: Indexes | None = None,
196
- ) -> Tuple[xarray.DataArray, List[str], List[str]]:
197
+ ) -> tuple[xarray.DataArray, list[str]]:
197
198
  """Select `band` indexes in DataArray."""
198
199
  da = self.input
199
200
  band_descriptions = self.band_descriptions
200
- band_names = [f"b{ix + 1}" for ix in range(self.input.rio.count)]
201
201
 
202
202
  if indexes := cast_to_sequence(indexes):
203
203
  assert all(v > 0 for v in indexes), "Indexes value must be >= 1"
@@ -207,30 +207,29 @@ class XarrayReader(BaseReader):
207
207
  f"Invalid indexes {indexes} for array of shape {da.shape}"
208
208
  )
209
209
 
210
- return da, band_names, band_descriptions
210
+ return da, band_descriptions
211
211
 
212
212
  indexes = [idx - 1 for idx in indexes]
213
213
 
214
214
  da = da[indexes]
215
215
  band_descriptions = [band_descriptions[idx] for idx in indexes]
216
- band_names = [band_names[idx] for idx in indexes]
217
216
 
218
- return da, band_names, band_descriptions
217
+ return da, band_descriptions
219
218
 
220
219
  def statistics(
221
220
  self,
222
221
  categorical: bool = False,
223
- categories: List[float] | None = None,
224
- percentiles: List[int] | None = None,
225
- hist_options: Dict | None = None,
222
+ categories: list[float] | None = None,
223
+ percentiles: list[int] | None = None,
224
+ hist_options: dict | None = None,
226
225
  nodata: NoData | None = None,
227
226
  indexes: Indexes | None = None,
228
227
  **kwargs: Any,
229
- ) -> Dict[str, BandStatistics]:
228
+ ) -> dict[str, BandStatistics]:
230
229
  """Return statistics from a dataset."""
231
230
  hist_options = hist_options or {}
232
231
 
233
- da, band_names, _ = self._sel_indexes(indexes)
232
+ da, band_descriptions = self._sel_indexes(indexes)
234
233
 
235
234
  if nodata is not None:
236
235
  da = da.rio.write_nodata(nodata)
@@ -246,14 +245,20 @@ class XarrayReader(BaseReader):
246
245
  **hist_options,
247
246
  )
248
247
 
249
- return {band_names[ix]: BandStatistics(**val) for ix, val in enumerate(stats)}
248
+ return {
249
+ f"b{ix +1}": BandStatistics(
250
+ **val,
251
+ description=band_descriptions[ix],
252
+ )
253
+ for ix, val in enumerate(stats)
254
+ }
250
255
 
251
256
  def tile(
252
257
  self,
253
258
  tile_x: int,
254
259
  tile_y: int,
255
260
  tile_z: int,
256
- tilesize: int = 256,
261
+ tilesize: int | None = None,
257
262
  reproject_method: WarpResampling = "nearest",
258
263
  auto_expand: bool = True,
259
264
  nodata: NoData | None = None,
@@ -281,16 +286,22 @@ class XarrayReader(BaseReader):
281
286
  f"Tile(x={tile_x}, y={tile_y}, z={tile_z}) is outside bounds"
282
287
  )
283
288
 
289
+ matrix = self.tms.matrix(tile_z)
290
+ bbox = cast(
291
+ BBox,
292
+ self.tms.xy_bounds(Tile(x=tile_x, y=tile_y, z=tile_z)),
293
+ )
294
+
284
295
  return self.part(
285
- cast(BBox, self.tms.xy_bounds(Tile(x=tile_x, y=tile_y, z=tile_z))),
296
+ bbox,
286
297
  dst_crs=self.tms.rasterio_crs,
287
298
  bounds_crs=self.tms.rasterio_crs,
288
299
  reproject_method=reproject_method,
289
300
  auto_expand=auto_expand,
290
301
  nodata=nodata,
291
302
  indexes=indexes,
292
- height=tilesize,
293
- width=tilesize,
303
+ height=tilesize or matrix.tileHeight,
304
+ width=tilesize or matrix.tileWidth,
294
305
  out_dtype=out_dtype,
295
306
  **kwargs,
296
307
  )
@@ -338,7 +349,7 @@ class XarrayReader(BaseReader):
338
349
 
339
350
  dst_crs = dst_crs or bounds_crs
340
351
 
341
- da, band_names, band_descriptions = self._sel_indexes(indexes)
352
+ da, band_descriptions = self._sel_indexes(indexes)
342
353
 
343
354
  if nodata is not None:
344
355
  da = da.rio.write_nodata(nodata)
@@ -448,7 +459,6 @@ class XarrayReader(BaseReader):
448
459
  bounds=bbox,
449
460
  crs=da.rio.crs,
450
461
  dataset_statistics=stats,
451
- band_names=band_names,
452
462
  band_descriptions=band_descriptions,
453
463
  nodata=da.rio.nodata,
454
464
  )
@@ -488,7 +498,7 @@ class XarrayReader(BaseReader):
488
498
  )
489
499
  max_size = None
490
500
 
491
- da, band_names, band_descriptions = self._sel_indexes(indexes)
501
+ da, band_descriptions = self._sel_indexes(indexes)
492
502
 
493
503
  if da.nbytes > MAX_ARRAY_SIZE:
494
504
  raise MaxArraySizeError(
@@ -578,7 +588,6 @@ class XarrayReader(BaseReader):
578
588
  bounds=da.rio.bounds(),
579
589
  crs=da.rio.crs,
580
590
  dataset_statistics=stats,
581
- band_names=band_names,
582
591
  band_descriptions=band_descriptions,
583
592
  nodata=da.rio.nodata,
584
593
  )
@@ -628,7 +637,7 @@ class XarrayReader(BaseReader):
628
637
  ):
629
638
  raise PointOutsideBounds("Point is outside dataset bounds")
630
639
 
631
- da, band_names, band_descriptions = self._sel_indexes(indexes)
640
+ da, band_descriptions = self._sel_indexes(indexes)
632
641
 
633
642
  if nodata is not None:
634
643
  da = da.rio.write_nodata(nodata)
@@ -648,7 +657,6 @@ class XarrayReader(BaseReader):
648
657
  arr,
649
658
  coordinates=(lon, lat),
650
659
  crs=coord_crs,
651
- band_names=band_names,
652
660
  band_descriptions=band_descriptions,
653
661
  pixel_location=(x, y),
654
662
  nodata=da.rio.nodata,
@@ -656,7 +664,7 @@ class XarrayReader(BaseReader):
656
664
 
657
665
  def feature(
658
666
  self,
659
- shape: Dict,
667
+ shape: dict,
660
668
  dst_crs: CRS | None = None,
661
669
  shape_crs: CRS = WGS84_CRS,
662
670
  reproject_method: WarpResampling = "nearest",
rio_tiler/models.py CHANGED
@@ -1,8 +1,10 @@
1
1
  """rio-tiler models."""
2
2
 
3
3
  import itertools
4
+ import re
4
5
  import warnings
5
- from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple, Union, cast
6
+ from collections.abc import Sequence
7
+ from typing import Any, Literal, cast
6
8
 
7
9
  import attr
8
10
  import numpy
@@ -34,7 +36,6 @@ from rio_tiler.types import (
34
36
  GDALColorMapType,
35
37
  IntervalTuple,
36
38
  NoData,
37
- NumType,
38
39
  RIOResampling,
39
40
  WarpResampling,
40
41
  )
@@ -58,14 +59,14 @@ class Bounds(BaseModel):
58
59
  class Info(Bounds):
59
60
  """Dataset Info."""
60
61
 
61
- band_metadata: List[Tuple[str, Dict]]
62
- band_descriptions: List[Tuple[str, str]]
62
+ band_metadata: list[tuple[str, dict]]
63
+ band_descriptions: list[tuple[str, str]]
63
64
  dtype: str
64
65
  nodata_type: Literal["Alpha", "Mask", "Internal", "Nodata", "None"]
65
- colorinterp: Optional[List[str]] = None
66
- scales: Optional[List[float]] = None
67
- offsets: Optional[List[float]] = None
68
- colormap: Optional[GDALColorMapType] = None
66
+ colorinterp: list[str] | None = None
67
+ scales: list[float] | None = None
68
+ offsets: list[float] | None = None
69
+ colormap: GDALColorMapType | None = None
69
70
 
70
71
  model_config = {"extra": "allow"}
71
72
 
@@ -83,15 +84,16 @@ class BandStatistics(BaseModel):
83
84
  majority: float
84
85
  minority: float
85
86
  unique: float
86
- histogram: List[List[NumType]]
87
+ histogram: list[list[float | int]]
87
88
  valid_percent: float
88
89
  masked_pixels: float
89
90
  valid_pixels: float
91
+ description: str
90
92
 
91
93
  model_config = {"extra": "allow"}
92
94
 
93
95
 
94
- def to_coordsbbox(bbox) -> Optional[BoundingBox]:
96
+ def to_coordsbbox(bbox) -> BoundingBox | None:
95
97
  """Convert bbox to CoordsBbox nameTuple."""
96
98
  return BoundingBox(*bbox) if bbox else None
97
99
 
@@ -100,7 +102,7 @@ def rescale_image(
100
102
  array: numpy.ma.MaskedArray,
101
103
  in_range: Sequence[IntervalTuple],
102
104
  out_range: Sequence[IntervalTuple] = ((0, 255),),
103
- out_dtype: Union[str, numpy.number] = "uint8",
105
+ out_dtype: str | numpy.number = "uint8",
104
106
  ) -> numpy.ma.MaskedArray:
105
107
  """Rescale image data in-place."""
106
108
  if len(array.shape) < 3:
@@ -156,16 +158,16 @@ class PointData:
156
158
  """
157
159
 
158
160
  array: numpy.ma.MaskedArray = attr.ib(converter=to_masked)
159
- band_names: List[str] = attr.ib(kw_only=True)
160
- band_descriptions: List[str] = attr.ib(kw_only=True)
161
- coordinates: Optional[Tuple[float, float]] = attr.ib(default=None, kw_only=True)
162
- crs: Optional[CRS] = attr.ib(default=None, kw_only=True)
163
- assets: Optional[List] = attr.ib(default=None, kw_only=True)
164
- metadata: Optional[Dict] = attr.ib(factory=dict, kw_only=True)
165
- nodata: Optional[NoData] = attr.ib(default=None, kw_only=True)
166
- scales: List[NumType] = attr.ib(kw_only=True)
167
- offsets: List[NumType] = attr.ib(kw_only=True)
168
- pixel_location: Optional[Tuple[NumType, NumType]] = attr.ib(
161
+ band_names: list[str] = attr.ib(kw_only=True)
162
+ band_descriptions: list[str] = attr.ib(kw_only=True)
163
+ coordinates: tuple[float, float] | None = attr.ib(default=None, kw_only=True)
164
+ crs: CRS | None = attr.ib(default=None, kw_only=True)
165
+ assets: list | None = attr.ib(default=None, kw_only=True)
166
+ metadata: dict | None = attr.ib(factory=dict, kw_only=True)
167
+ nodata: NoData | None = attr.ib(default=None, kw_only=True)
168
+ scales: list[float | int] = attr.ib(kw_only=True)
169
+ offsets: list[float | int] = attr.ib(kw_only=True)
170
+ pixel_location: tuple[float | int, float | int] | None = attr.ib(
169
171
  default=None, kw_only=True
170
172
  )
171
173
 
@@ -187,7 +189,7 @@ class PointData:
187
189
 
188
190
  @band_descriptions.default
189
191
  def _default_band_descriptions(self):
190
- return ["" for ix in range(self.count)]
192
+ return ["" or f"b{ix + 1}" for ix in range(self.count)]
191
193
 
192
194
  @scales.default
193
195
  def _default_scales(self):
@@ -290,14 +292,20 @@ class PointData:
290
292
  # Using numexpr do not preserve mask info
291
293
  data.mask = False
292
294
 
293
- # TODO: Update band descriptions
295
+ mapexpr = {
296
+ self.band_names[idx]: desc for idx, desc in enumerate(self.band_descriptions)
297
+ }
298
+ _re = re.compile(r"\bb[0-9A-Z]+\b")
299
+ band_descriptions = [
300
+ _re.sub(lambda x: mapexpr[x.group()], block) for block in blocks
301
+ ]
294
302
 
295
303
  return PointData(
296
304
  data,
297
305
  assets=self.assets,
298
306
  crs=self.crs,
299
307
  coordinates=self.coordinates,
300
- band_names=blocks,
308
+ band_descriptions=band_descriptions,
301
309
  metadata=self.metadata,
302
310
  pixel_location=self.pixel_location,
303
311
  )
@@ -340,22 +348,22 @@ class ImageData:
340
348
  """
341
349
 
342
350
  array: numpy.ma.MaskedArray = attr.ib(converter=masked_and_3d)
343
- assets: Optional[List] = attr.ib(default=None, kw_only=True)
344
- bounds: Optional[BoundingBox] = attr.ib(
351
+ assets: list | None = attr.ib(default=None, kw_only=True)
352
+ bounds: BoundingBox | None = attr.ib(
345
353
  default=None, converter=to_coordsbbox, kw_only=True
346
354
  )
347
- crs: Optional[CRS] = attr.ib(default=None, kw_only=True)
348
- metadata: Optional[Dict] = attr.ib(factory=dict, kw_only=True)
349
- nodata: Optional[NoData] = attr.ib(default=None, kw_only=True)
350
- scales: List[NumType] = attr.ib(kw_only=True)
351
- offsets: List[NumType] = attr.ib(kw_only=True)
352
- band_names: List[str] = attr.ib(kw_only=True)
353
- band_descriptions: List[str] = attr.ib(kw_only=True)
354
- dataset_statistics: Optional[Sequence[Tuple[float, float]]] = attr.ib(
355
+ crs: CRS | None = attr.ib(default=None, kw_only=True)
356
+ metadata: dict | None = attr.ib(factory=dict, kw_only=True)
357
+ nodata: NoData | None = attr.ib(default=None, kw_only=True)
358
+ scales: list[float | int] = attr.ib(kw_only=True)
359
+ offsets: list[float | int] = attr.ib(kw_only=True)
360
+ band_names: list[str] = attr.ib(kw_only=True)
361
+ band_descriptions: list[str] = attr.ib(kw_only=True)
362
+ dataset_statistics: Sequence[tuple[float, float]] | None = attr.ib(
355
363
  default=None, kw_only=True
356
364
  )
357
- cutline_mask: Optional[numpy.ndarray] = attr.ib(default=None)
358
- alpha_mask: Optional[numpy.ndarray] = attr.ib(default=None)
365
+ cutline_mask: numpy.ndarray | None = attr.ib(default=None)
366
+ alpha_mask: numpy.ndarray | None = attr.ib(default=None)
359
367
 
360
368
  @band_names.default
361
369
  def _default_band_names(self):
@@ -363,7 +371,7 @@ class ImageData:
363
371
 
364
372
  @band_descriptions.default
365
373
  def _default_band_descriptions(self):
366
- return ["" for ix in range(self.count)]
374
+ return ["" or f"b{ix + 1}" for ix in range(self.count)]
367
375
 
368
376
  @scales.default
369
377
  def _default_scales(self):
@@ -596,7 +604,7 @@ class ImageData:
596
604
  self,
597
605
  in_range: Sequence[IntervalTuple],
598
606
  out_range: Sequence[IntervalTuple] = ((0, 255),),
599
- out_dtype: Union[str, numpy.number] = "uint8",
607
+ out_dtype: str | numpy.number = "uint8",
600
608
  ) -> Self:
601
609
  """Rescale data in place."""
602
610
  self.array = rescale_image(
@@ -618,7 +626,7 @@ class ImageData:
618
626
 
619
627
  return self
620
628
 
621
- def apply_color_formula(self, color_formula: Optional[str]) -> Self:
629
+ def apply_color_formula(self, color_formula: str | None) -> Self:
622
630
  """Apply color-operations formula in place."""
623
631
  out = self.array.data
624
632
  out[out < 0] = 0
@@ -691,14 +699,20 @@ class ImageData:
691
699
  # NOTE: We use dataset mask when mixing bands
692
700
  data.mask = numpy.logical_or.reduce(self.array.mask)
693
701
 
694
- # TODO: update band descriptions
702
+ mapexpr = {
703
+ self.band_names[idx]: desc for idx, desc in enumerate(self.band_descriptions)
704
+ }
705
+ _re = re.compile(r"\bb[0-9A-Z]+\b")
706
+ band_descriptions = [
707
+ _re.sub(lambda x: mapexpr[x.group()], block) for block in blocks
708
+ ]
695
709
 
696
710
  return ImageData(
697
711
  data,
698
712
  assets=self.assets,
699
713
  crs=self.crs,
700
714
  bounds=self.bounds,
701
- band_names=blocks,
715
+ band_descriptions=band_descriptions,
702
716
  metadata=self.metadata,
703
717
  dataset_statistics=stats,
704
718
  alpha_mask=self.alpha_mask,
@@ -759,9 +773,9 @@ class ImageData:
759
773
 
760
774
  def post_process(
761
775
  self,
762
- in_range: Optional[Sequence[IntervalTuple]] = None,
763
- out_dtype: Union[str, numpy.number] = "uint8",
764
- color_formula: Optional[str] = None,
776
+ in_range: Sequence[IntervalTuple] | None = None,
777
+ out_dtype: str | numpy.number = "uint8",
778
+ color_formula: str | None = None,
765
779
  **kwargs: Any,
766
780
  ) -> "ImageData":
767
781
  """Post-process image data.
@@ -803,7 +817,7 @@ class ImageData:
803
817
  self,
804
818
  add_mask: bool = True,
805
819
  img_format: str = "PNG",
806
- colormap: Optional[ColorMapType] = None,
820
+ colormap: ColorMapType | None = None,
807
821
  **kwargs,
808
822
  ) -> bytes:
809
823
  """Render data to image blob.
@@ -899,11 +913,11 @@ class ImageData:
899
913
  def statistics(
900
914
  self,
901
915
  categorical: bool = False,
902
- categories: Optional[List[float]] = None,
903
- percentiles: Optional[List[int]] = None,
904
- hist_options: Optional[Dict] = None,
905
- coverage: Optional[numpy.ndarray] = None,
906
- ) -> Dict[str, BandStatistics]:
916
+ categories: list[float] | None = None,
917
+ percentiles: list[int] | None = None,
918
+ hist_options: dict | None = None,
919
+ coverage: numpy.ndarray | None = None,
920
+ ) -> dict[str, BandStatistics]:
907
921
  """Return statistics from ImageData."""
908
922
  hist_options = hist_options or {}
909
923
 
@@ -917,13 +931,16 @@ class ImageData:
917
931
  )
918
932
 
919
933
  return {
920
- f"{self.band_names[ix]}": BandStatistics(**stats[ix])
934
+ f"{self.band_names[ix]}": BandStatistics(
935
+ **stats[ix],
936
+ description=self.band_descriptions[ix],
937
+ )
921
938
  for ix in range(len(stats))
922
939
  }
923
940
 
924
941
  def get_coverage_array(
925
942
  self,
926
- shape: Dict,
943
+ shape: dict,
927
944
  shape_crs: CRS = WGS84_CRS,
928
945
  cover_scale: int = 10,
929
946
  ) -> NDArray[numpy.floating]:
@@ -972,7 +989,7 @@ class ImageData:
972
989
  def reproject(
973
990
  self,
974
991
  dst_crs: CRS,
975
- resolution: Optional[Tuple[float, float]] = None,
992
+ resolution: tuple[float, float] | None = None,
976
993
  reproject_method: WarpResampling = "nearest",
977
994
  ) -> "ImageData":
978
995
  """Reproject data and mask."""
@@ -2,7 +2,7 @@
2
2
 
3
3
  import abc
4
4
  import logging
5
- from typing import Any, Type
5
+ from typing import Any
6
6
 
7
7
  import attr
8
8
  from morecantile import TileMatrixSet
@@ -51,7 +51,7 @@ class BaseBackend(BaseReader):
51
51
  input: str = attr.ib()
52
52
  tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
53
53
 
54
- reader: Type[BaseReader] | Type[MultiBaseReader] | Type[MultiBandReader] = attr.ib(
54
+ reader: type[BaseReader] | type[MultiBaseReader] | type[MultiBandReader] = attr.ib(
55
55
  default=Reader
56
56
  )
57
57
  reader_options: dict = attr.ib(factory=dict)
@@ -2,7 +2,6 @@
2
2
 
3
3
  import abc
4
4
  from dataclasses import dataclass, field
5
- from typing import Optional
6
5
 
7
6
  import numpy
8
7
 
@@ -11,9 +10,9 @@ import numpy
11
10
  class MosaicMethodBase(abc.ABC):
12
11
  """Abstract base class for rio-tiler-mosaic methods objects."""
13
12
 
14
- mosaic: Optional[numpy.ma.MaskedArray] = field(default=None, init=False)
13
+ mosaic: numpy.ma.MaskedArray | None = field(default=None, init=False)
15
14
  exit_when_filled: bool = field(default=False, init=False)
16
- cutline_mask: Optional[numpy.ndarray] = field(default=None, init=False)
15
+ cutline_mask: numpy.ndarray | None = field(default=None, init=False)
17
16
  width: int = field(init=False)
18
17
  height: int = field(init=False)
19
18
  count: int = field(init=False)
@@ -42,7 +41,7 @@ class MosaicMethodBase(abc.ABC):
42
41
  return False
43
42
 
44
43
  @property
45
- def data(self) -> Optional[numpy.ma.MaskedArray]:
44
+ def data(self) -> numpy.ma.MaskedArray | None:
46
45
  """Return data."""
47
46
  return self.mosaic
48
47
 
@@ -1,7 +1,6 @@
1
1
  """rio_tiler.mosaic.methods.defaults: default mosaic filling methods."""
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import List, Optional
5
4
 
6
5
  import numpy
7
6
 
@@ -18,7 +17,7 @@ class FirstMethod(MosaicMethodBase):
18
17
  """Mosaic Method repr."""
19
18
  return "<Mosaic: FirstMethod>"
20
19
 
21
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
20
+ def feed(self, array: numpy.ma.MaskedArray | None):
22
21
  """Add data to the mosaic array."""
23
22
  if self.mosaic is None:
24
23
  self.mosaic = array
@@ -39,7 +38,7 @@ class HighestMethod(MosaicMethodBase):
39
38
  """Mosaic Method repr."""
40
39
  return "<Mosaic: HighestMethod>"
41
40
 
42
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
41
+ def feed(self, array: numpy.ma.MaskedArray | None):
43
42
  """Add data to the mosaic array."""
44
43
  if self.mosaic is None:
45
44
  self.mosaic = array
@@ -63,7 +62,7 @@ class LowestMethod(MosaicMethodBase):
63
62
  """Mosaic Method repr."""
64
63
  return "<Mosaic: LowestMethod>"
65
64
 
66
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
65
+ def feed(self, array: numpy.ma.MaskedArray | None):
67
66
  """Add data to the mosaic array."""
68
67
  if self.mosaic is None:
69
68
  self.mosaic = array
@@ -84,14 +83,14 @@ class MeanMethod(MosaicMethodBase):
84
83
  """Stack the arrays and return the Mean pixel value."""
85
84
 
86
85
  enforce_data_type: bool = True
87
- stack: List[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
86
+ stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
88
87
 
89
88
  def __repr__(self):
90
89
  """Mosaic Method repr."""
91
90
  return "<Mosaic: MeanMethod>"
92
91
 
93
92
  @property
94
- def data(self) -> Optional[numpy.ma.MaskedArray]:
93
+ def data(self) -> numpy.ma.MaskedArray | None:
95
94
  """Return Mean of the data stack."""
96
95
  if self.stack:
97
96
  array = numpy.ma.mean(numpy.ma.stack(self.stack, axis=0), axis=0)
@@ -112,14 +111,14 @@ class MedianMethod(MosaicMethodBase):
112
111
  """Stack the arrays and return the Median pixel value."""
113
112
 
114
113
  enforce_data_type: bool = True
115
- stack: List[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
114
+ stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
116
115
 
117
116
  def __repr__(self):
118
117
  """Mosaic Method repr."""
119
118
  return "<Mosaic: MedianMethod>"
120
119
 
121
120
  @property
122
- def data(self) -> Optional[numpy.ma.MaskedArray]:
121
+ def data(self) -> numpy.ma.MaskedArray | None:
123
122
  """Return Median of the data stack."""
124
123
  if self.stack:
125
124
  array = numpy.ma.median(numpy.ma.stack(self.stack, axis=0), axis=0)
@@ -130,7 +129,7 @@ class MedianMethod(MosaicMethodBase):
130
129
 
131
130
  return None
132
131
 
133
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
132
+ def feed(self, array: numpy.ma.MaskedArray | None):
134
133
  """Add array to the stack."""
135
134
  self.stack.append(array)
136
135
 
@@ -139,21 +138,21 @@ class MedianMethod(MosaicMethodBase):
139
138
  class StdevMethod(MosaicMethodBase):
140
139
  """Stack the arrays and return the Standard Deviation value."""
141
140
 
142
- stack: List[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
141
+ stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
143
142
 
144
143
  def __repr__(self):
145
144
  """Mosaic Method repr."""
146
145
  return "<Mosaic: StdevMethod>"
147
146
 
148
147
  @property
149
- def data(self) -> Optional[numpy.ma.MaskedArray]:
148
+ def data(self) -> numpy.ma.MaskedArray | None:
150
149
  """Return STDDEV of the data stack."""
151
150
  if self.stack:
152
151
  return numpy.ma.std(numpy.ma.stack(self.stack, axis=0), axis=0)
153
152
 
154
153
  return None
155
154
 
156
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
155
+ def feed(self, array: numpy.ma.MaskedArray | None):
157
156
  """Add array to the stack."""
158
157
  self.stack.append(array)
159
158
 
@@ -167,14 +166,14 @@ class LastBandHighMethod(MosaicMethodBase):
167
166
  return "<Mosaic: LastBandHighMethod>"
168
167
 
169
168
  @property
170
- def data(self) -> Optional[numpy.ma.MaskedArray]:
169
+ def data(self) -> numpy.ma.MaskedArray | None:
171
170
  """Return data."""
172
171
  if self.mosaic is not None:
173
172
  return self.mosaic[:-1].copy()
174
173
 
175
174
  return None
176
175
 
177
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
176
+ def feed(self, array: numpy.ma.MaskedArray | None):
178
177
  """Add data to the mosaic array."""
179
178
  if self.mosaic is None:
180
179
  self.mosaic = array
@@ -199,14 +198,14 @@ class LastBandLowMethod(MosaicMethodBase):
199
198
  return "<Mosaic: LastBandLowMethod>"
200
199
 
201
200
  @property
202
- def data(self) -> Optional[numpy.ma.MaskedArray]:
201
+ def data(self) -> numpy.ma.MaskedArray | None:
203
202
  """Return data."""
204
203
  if self.mosaic is not None:
205
204
  return self.mosaic[:-1].copy()
206
205
 
207
206
  return None
208
207
 
209
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
208
+ def feed(self, array: numpy.ma.MaskedArray | None):
210
209
  """Add data to the mosaic array."""
211
210
  if self.mosaic is None:
212
211
  self.mosaic = array
@@ -226,14 +225,14 @@ class LastBandLowMethod(MosaicMethodBase):
226
225
  class CountMethod(MosaicMethodBase):
227
226
  """Stack the arrays and return the valid pixel count."""
228
227
 
229
- stack: List[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
228
+ stack: list[numpy.ma.MaskedArray] = field(default_factory=list, init=False)
230
229
 
231
230
  def __repr__(self):
232
231
  """Mosaic Method repr."""
233
232
  return "<Mosaic: CountMethod>"
234
233
 
235
234
  @property
236
- def data(self) -> Optional[numpy.ma.MaskedArray]:
235
+ def data(self) -> numpy.ma.MaskedArray | None:
237
236
  """Return valid data count of the data stack."""
238
237
  if self.stack:
239
238
  data = numpy.ma.count(numpy.ma.stack(self.stack, axis=0), axis=0)
@@ -254,6 +253,6 @@ class CountMethod(MosaicMethodBase):
254
253
 
255
254
  return None
256
255
 
257
- def feed(self, array: Optional[numpy.ma.MaskedArray]):
256
+ def feed(self, array: numpy.ma.MaskedArray | None):
258
257
  """Add array to the stack."""
259
258
  self.stack.append(array)