"""Metadata and data loading model classes."""
from dataclasses import astuple, dataclass
from typing import Any, ContextManager, Dict, Mapping, Optional, Protocol, Tuple, Union
import numpy as np
from odc.geo.geobox import GeoBox
from odc.geo.roi import NormalizedROI
[docs]@dataclass(eq=True, frozen=True)
class RasterSource:
"""
Captures known information about a single band.
"""
uri: str
"""Asset location."""
band: int = 1
"""One based band index (default=1)."""
subdataset: Optional[str] = None
"""Used for netcdf/hdf5 sources."""
geobox: Optional[GeoBox] = None
"""Data footprint/shape/projection if known."""
meta: Optional[RasterBandMetadata] = None
"""Expected raster dtype/nodata."""
def strip(self) -> "RasterSource":
"""
Copy with minimal data only.
Removes geobox and meta info as they are not needed for data loading.
"""
return RasterSource(self.uri, self.band, self.subdataset)
def __dask_tokenize__(self):
return (self.uri, self.band, self.subdataset)
MultiBandRasterSource = Mapping[Union[str, Tuple[str, int]], RasterSource]
"""Mapping from band name to RasterSource."""
[docs]@dataclass
class RasterLoadParams:
"""
Captures data loading configuration.
"""
dtype: Optional[str] = None
"""Output dtype, default same as source."""
fill_value: Optional[float] = None
"""Value to use for missing pixels."""
src_nodata_fallback: Optional[float] = None
"""
Fallback ``nodata`` marker for source.
Used to deal with broken data sources. If file is missing ``nodata`` marker and
``src_nodata_fallback`` is set then treat source pixels with that value as missing.
"""
src_nodata_override: Optional[float] = None
"""
Override ``nodata`` marker for source.
Used to deal with broken data sources. Ignore ``nodata`` marker of the source file even if
present and use this value instead.
"""
use_overviews: bool = True
"""
Disable use of overview images.
Set to ``False`` to always read from the main image ignoring overview images
even when present in the data source.
"""
resampling: str = "nearest"
"""Resampling method to use."""
fail_on_error: bool = True
"""Quit on the first error or continue."""
@staticmethod
def same_as(src: Union[RasterBandMetadata, RasterSource]) -> "RasterLoadParams":
"""Construct from source object."""
if isinstance(src, RasterBandMetadata):
meta = src
else:
meta = src.meta or RasterBandMetadata()
dtype = meta.data_type
if dtype is None:
dtype = "float32"
return RasterLoadParams(dtype=dtype, fill_value=meta.nodata)
@property
def nearest(self) -> bool:
"""Report True if nearest resampling is used."""
return self.resampling == "nearest"
def __dask_tokenize__(self):
return astuple(self)
class SomeReader(Protocol):
"""
Protocol for readers.
"""
def capture_env(self) -> Dict[str, Any]: ...
def restore_env(self, env: Dict[str, Any]) -> ContextManager[Any]: ...
def read(
self,
src: RasterSource,
cfg: RasterLoadParams,
dst_geobox: GeoBox,
dst: Optional[np.ndarray] = None,
) -> Tuple[NormalizedROI, np.ndarray]: ...
BAND_DEFAULTS = RasterBandMetadata("float32", None, "1")
def norm_band_metadata(
v: Union[RasterBandMetadata, Dict[str, Any]],
fallback: RasterBandMetadata = BAND_DEFAULTS,
) -> RasterBandMetadata:
if isinstance(v, RasterBandMetadata):
return v
return RasterBandMetadata(
v.get("data_type", fallback.data_type),
v.get("nodata", fallback.nodata),
v.get("unit", fallback.unit),
)