Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore no-data values when scaling band data and allow band selection by color name for Sentinel-2 #87

Merged
merged 8 commits into from
Oct 27, 2023
21 changes: 16 additions & 5 deletions eodal/core/band.py
Original file line number Diff line number Diff line change
Expand Up @@ -1370,11 +1370,13 @@ def get_meta(self, driver: Optional[str] = "gTiff", **kwargs) -> Dict[str, Any]:
name of the ``rasterio`` driver. `gTiff` (GeoTiff) by default
:param kwargs:
additional keyword arguments to append to metadata dictionary
or to overwrite defaults such as the "compress" attribute.
:returns:
``rasterio`` compatible metadata dictionary to be used for
writing new raster datasets
"""
meta = {}
# set defaults
meta["height"] = self.nrows
meta["width"] = self.ncols
meta["crs"] = self.crs
Expand All @@ -1387,6 +1389,8 @@ def get_meta(self, driver: Optional[str] = "gTiff", **kwargs) -> Dict[str, Any]:
# "compress" as suggested here:
# https://github.com/rasterio/rasterio/discussions/2933#discussioncomment-7208578
meta["compress"] = "DEFLATE"

# defaults can be overwritten using custom kwargs
meta.update(kwargs)

return meta
Expand Down Expand Up @@ -1517,7 +1521,7 @@ def hist(

def plot(
self,
colormap: Optional[str] = "gray",
colormap: Optional[str] = "viridis",
discrete_values: Optional[bool] = False,
user_defined_colors: Optional[ListedColormap] = None,
user_defined_ticks: Optional[List[Union[str, int, float]]] = None,
Expand All @@ -1532,7 +1536,7 @@ def plot(

:param colormap:
String identifying one of matplotlib's colormaps.
The default will plot the band in gray values.
The default will plot the band using the viridis colormap.
:param discrete_values:
if True (Default) assumes that the band has continuous values
(i.e., ordinary spectral data). If False assumes that the
Expand Down Expand Up @@ -2201,21 +2205,28 @@ def reduce(
def scale_data(
self,
inplace: Optional[bool] = False,
pixel_values_to_ignore: Optional[List[Union[int, float]]] = None,
pixel_values_to_ignore: Optional[List[int | float]] = [],
):
"""
Applies scale and offset factors to the data.

.. versionadded:: 0.2.3
No-data values are ignored when applying scale and offset.

:param inplace:
if False (default) returns a copy of the ``Band`` instance
with the changes applied. If True overwrites the values
in the current instance.
:param pixel_values_to_ignore:
optional list of pixel values (e.g., nodata values) to ignore,
i.e., where scaling has no effect
optional list of pixel values to ignore, i.e., where scaling
has no effect. From version 0.2.3 onwards, no-data values
are *always* ignored.
:returns:
``Band`` instance if `inplace` is False, None instead.
"""
# add no-data to the `pixel_values_to_ignore` list
pixel_values_to_ignore.append(self.nodata)

scale, offset = self.scale, self.offset
if self.is_masked_array:
if pixel_values_to_ignore is None:
Expand Down
2 changes: 1 addition & 1 deletion eodal/core/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ def _bands_from_selection(
Selects bands in a multi-band raster dataset based on a custom
selection of band indices or band names.

.. versionadd:: 0.2.0
.. versionadded:: 0.2.0
works also with a dictionary of hrefs returned from a
STAC query

Expand Down
1 change: 1 addition & 0 deletions eodal/core/sensors/landsat.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ def from_usgs(
RasterCollection containing the Landsat bands.
"""
# check band selection and determine the platform and sensor
_band_selection = deepcopy(band_selection)
band_df = cls._preprocess_band_selection(
cls,
in_dir=in_dir,
Expand Down
22 changes: 19 additions & 3 deletions eodal/core/sensors/sentinel2.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import geopandas as gpd
import rasterio as rio

from copy import deepcopy
from matplotlib.pyplot import Figure
from matplotlib import colors
from numbers import Number
Expand Down Expand Up @@ -157,7 +158,7 @@ def _get_band_files(
if processing_level == ProcessingLevels.L1C:
is_l2a = False

# check if SCL should e read (L2A)
# check if SCL should be read (L2A)
if is_l2a and read_scl:
scl_in_selection = "scl" in band_selection or "SCL" in band_selection
if not scl_in_selection:
Expand Down Expand Up @@ -209,6 +210,20 @@ def _process_band_selection(
for band in bands_to_exclude:
band_selection.remove(band)

# check if band or color names are passed
color_names = set(band_selection).issubset(s2_band_mapping.values())
band_names = set(band_selection).issubset(s2_band_mapping.keys())

if not color_names and not band_names:
raise BandNotFoundError(
f'Invalid selection of bands: {band_selection}')

# internally, we use band names only. So we have to map the color names
# back to their band names
if color_names:
band_selection = [
k for k, v in s2_band_mapping.items() if v in band_selection]

# determine which spatial resolutions are selected and check processing level
band_df_safe = self._get_band_files(
in_dir=in_dir, band_selection=band_selection, read_scl=read_scl
Expand All @@ -224,7 +239,7 @@ def _process_band_selection(
if bands_not_found[0] == "SCL":
return band_df_safe
raise BandNotFoundError(
f"Couldnot find bands {bands_not_found} " "provided in selection"
f"Could not find bands {bands_not_found} " "provided in selection"
)
return band_df_safe

Expand Down Expand Up @@ -280,8 +295,9 @@ def from_safe(
`Sentinel2` instance with S2 bands loaded
"""
# load 10 and 20 bands by default
_band_selection = deepcopy(band_selection)
band_df_safe = cls._process_band_selection(
cls, in_dir=in_dir, band_selection=band_selection, read_scl=read_scl
cls, in_dir=in_dir, band_selection=_band_selection, read_scl=read_scl
)

# check the clipping extent of the raster with the lowest (coarsest) spatial
Expand Down
18 changes: 16 additions & 2 deletions tests/core/test_sentinel2.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from eodal.config import get_settings
from eodal.core.sensors import Sentinel2
from eodal.core.band import Band
from eodal.utils.exceptions import BandNotFoundError, InputError
from eodal.utils.exceptions import BandNotFoundError

settings = get_settings()
settings.USE_STAC = False
Expand Down Expand Up @@ -251,7 +251,7 @@ def test_ignore_scl(datadir, get_s2_safe_l2a, get_polygons_2):


def test_band_selections(datadir, get_s2_safe_l2a, get_polygons_2):
"""testing invalid band selections"""
"""testing valid and invalid band selections"""

in_dir = get_s2_safe_l2a()
in_file_aoi = get_polygons_2()
Expand All @@ -265,6 +265,20 @@ def test_band_selections(datadir, get_s2_safe_l2a, get_polygons_2):
band_selection=['B02', 'B13']
)

# test with color names instead of band names
band_selection = ['red', 'green', 'blue']
ds = Sentinel2.from_safe(
in_dir=in_dir,
band_selection=band_selection,
read_scl=False,
apply_scaling=False
)
assert ds.band_names == ['B02', 'B03', 'B04']
assert set(ds.band_aliases) == set(band_selection)
assert (ds['B02'] == ds['blue']).values.all()
assert (ds['B03'] == ds['green']).values.all()
assert (ds['B04'] == ds['red']).values.all()


@pytest.mark.skip(reason='too heavy test for Github workflows')
def test_read_from_safe_l2a(datadir, get_s2_safe_l2a):
Expand Down