-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add more robust version of ndvi (#147)
* add more robust version of ndvi * extend test coverage * make test data smaller * Update tests/test_indices.py Co-authored-by: ValentinaHutter <85164505+ValentinaHutter@users.noreply.github.com> * update tests * address feedback from review --------- Co-authored-by: ValentinaHutter <85164505+ValentinaHutter@users.noreply.github.com>
- Loading branch information
1 parent
d60a286
commit dfee6f6
Showing
6 changed files
with
167 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
openeo_processes_dask/process_implementations/cubes/indices.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import xarray as xr | ||
|
||
from openeo_processes_dask.process_implementations.data_model import RasterCube | ||
from openeo_processes_dask.process_implementations.exceptions import ( | ||
BandExists, | ||
DimensionAmbiguous, | ||
NirBandAmbiguous, | ||
RedBandAmbiguous, | ||
) | ||
from openeo_processes_dask.process_implementations.math import normalized_difference | ||
|
||
__all__ = ["ndvi"] | ||
|
||
|
||
def ndvi(data: RasterCube, nir="nir", red="red", target_band=None): | ||
if len(data.openeo.band_dims) == 0: | ||
raise DimensionAmbiguous( | ||
"Dimension of type `bands` is not available or is ambiguous." | ||
) | ||
band_dim = data.openeo.band_dims[0] | ||
available_bands = data.coords[band_dim] | ||
|
||
if nir not in available_bands or red not in available_bands: | ||
try: | ||
data = data.set_xindex("common_name") | ||
except (ValueError, KeyError): | ||
pass | ||
|
||
if ( | ||
nir not in available_bands | ||
and "common_name" in data.xindexes._coord_name_id.keys() | ||
and nir not in data.coords["common_name"].data | ||
): | ||
raise NirBandAmbiguous( | ||
"The NIR band can't be resolved, please specify the specific NIR band name." | ||
) | ||
elif ( | ||
red not in available_bands | ||
and "common_name" in data.xindexes._coord_name_id.keys() | ||
and red not in data.coords["common_name"].data | ||
): | ||
raise RedBandAmbiguous( | ||
"The Red band can't be resolved, please specify the specific Red band name." | ||
) | ||
|
||
nir_band_dim = "common_name" if nir not in available_bands else band_dim | ||
red_band_dim = "common_name" if red not in available_bands else band_dim | ||
|
||
nir_band = data.sel({nir_band_dim: nir}) | ||
red_band = data.sel({red_band_dim: red}) | ||
|
||
nd = normalized_difference(nir_band, red_band) | ||
if target_band is not None: | ||
if target_band in data.coords: | ||
raise BandExists("A band with the specified target name exists.") | ||
nd = nd.expand_dims(band_dim).assign_coords({band_dim: [target_band]}) | ||
nd = xr.merge([data, nd]) | ||
nd.attrs = data.attrs | ||
return nd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import numpy as np | ||
import pytest | ||
|
||
from openeo_processes_dask.process_implementations.cubes.indices import ndvi | ||
from openeo_processes_dask.process_implementations.cubes.load import load_stac | ||
from openeo_processes_dask.process_implementations.exceptions import ( | ||
BandExists, | ||
DimensionAmbiguous, | ||
NirBandAmbiguous, | ||
RedBandAmbiguous, | ||
) | ||
from tests.conftest import _random_raster_data | ||
from tests.general_checks import general_output_checks | ||
|
||
|
||
def test_ndvi(bounding_box): | ||
url = "./tests/data/stac/s2_l2a_test_item.json" | ||
input_cube = load_stac( | ||
url=url, | ||
spatial_extent=bounding_box, | ||
bands=["red", "nir"], | ||
).isel({"x": slice(0, 20), "y": slice(0, 20)}) | ||
|
||
# Test whether this works with different band names | ||
input_cube = input_cube.rename({"band": "b"}) | ||
|
||
import dask.array as da | ||
|
||
numpy_data = _random_raster_data(input_cube.data.shape, dtype=np.float64) | ||
|
||
input_cube.data = da.from_array(numpy_data, chunks=("auto", "auto", "auto", -1)) | ||
|
||
output = ndvi(input_cube) | ||
|
||
band_dim = input_cube.openeo.band_dims[0] | ||
assert band_dim not in output.dims | ||
|
||
expected_results = ( | ||
input_cube.sel({band_dim: "nir"}) - input_cube.sel({band_dim: "red"}) | ||
) / (input_cube.sel({band_dim: "nir"}) + input_cube.sel({band_dim: "red"})) | ||
|
||
general_output_checks( | ||
input_cube=input_cube, output_cube=output, expected_results=expected_results | ||
) | ||
|
||
cube_with_resolvable_coords = input_cube.assign_coords( | ||
{band_dim: ["blue", "yellow"]} | ||
) | ||
output = ndvi(cube_with_resolvable_coords) | ||
general_output_checks( | ||
input_cube=cube_with_resolvable_coords, | ||
output_cube=output, | ||
expected_results=expected_results, | ||
) | ||
|
||
with pytest.raises(DimensionAmbiguous): | ||
ndvi(output) | ||
|
||
cube_with_nir_unresolvable = cube_with_resolvable_coords | ||
cube_with_nir_unresolvable.common_name.data = np.array(["blue", "red"]) | ||
|
||
with pytest.raises(NirBandAmbiguous): | ||
ndvi(cube_with_nir_unresolvable) | ||
|
||
cube_with_red_unresolvable = cube_with_resolvable_coords | ||
cube_with_red_unresolvable.common_name.data = np.array(["nir", "yellow"]) | ||
|
||
with pytest.raises(RedBandAmbiguous): | ||
ndvi(cube_with_red_unresolvable) | ||
|
||
cube_with_nothing_resolvable = cube_with_resolvable_coords | ||
cube_with_nothing_resolvable = cube_with_nothing_resolvable.drop_vars("common_name") | ||
with pytest.raises(KeyError): | ||
ndvi(cube_with_nothing_resolvable) | ||
|
||
target_band = "yay" | ||
output_with_extra_dim = ndvi(input_cube, target_band=target_band) | ||
assert len(output_with_extra_dim.dims) == len(output.dims) + 1 | ||
assert ( | ||
len(output_with_extra_dim.coords[band_dim]) | ||
== len(input_cube.coords[band_dim]) + 1 | ||
) | ||
|
||
with pytest.raises(BandExists): | ||
output_with_extra_dim = ndvi(input_cube, target_band="time") |