From dbc85394a2d876c32ee3a9700ba1779f01d1f6fc Mon Sep 17 00:00:00 2001 From: DRIESSEB Date: Fri, 5 Nov 2021 22:58:13 +0100 Subject: [PATCH] Incorporate review comments --- .../resources/spectral-indices-dict.json | 89 +--- .../resources/vito-indices-dict.json | 84 ++++ .../spectral_indices/spectral_indices.py | 195 +++++++-- .../spectral_indices/test_spectral_indices.py | 403 +++++++++++++++++- tests/test_spectral_indices.py | 43 -- 5 files changed, 647 insertions(+), 167 deletions(-) create mode 100644 openeo/extra/spectral_indices/resources/vito-indices-dict.json delete mode 100644 tests/test_spectral_indices.py diff --git a/openeo/extra/spectral_indices/resources/spectral-indices-dict.json b/openeo/extra/spectral_indices/resources/spectral-indices-dict.json index 71e3bda08..d473032e6 100644 --- a/openeo/extra/spectral_indices/resources/spectral-indices-dict.json +++ b/openeo/extra/spectral_indices/resources/spectral-indices-dict.json @@ -1,21 +1,5 @@ { "SpectralIndices": { - "ANIR": { - "bands": - [ - "R", - "N", - "S1" - ], - "contributor": "vito", - "date_of_addition": "2021-10-27", - "formula": "exec('import numpy as np') or exec('from openeo.processes import clip') or np.arccos(clip((( np.sqrt( (0.8328 - 0.6646)**2 + (N - R)**2 )**2 + np.sqrt( (1.610 - 0.8328)**2 + (S1 - N)**2 )**2 - np.sqrt( (1.610 - 0.6646)**2 + (S1 - R)**2 )**2 ) / (2 * np.sqrt( (0.8328 - 0.6646)**2 + (N - R)**2 ) * np.sqrt( (1.610 - 0.8328)**2 + (S1 - N)**2 ))), -1,1)) * (1. / np.pi)", - "long_name": "Angle at Near InfraRed", - "reference": "", - "short_name": "ANIR", - "type": "vegetation", - "range": "(0,1)" - }, "ARI": { "bands": [ "G", @@ -647,76 +631,6 @@ "short_name": "NDDI", "type": "drought" }, - "NDGI": { - "bands": [ - "G", - "R" - ], - "contributor": "vito", - "date_of_addition": "2021-10-27", - "formula": "(G - R) / (G + R)", - "long_name": "Normalized Difference Glacier Index", - "reference": "https://www.researchgate.net/publication/248978081_ASTER_ratio_indices_for_supraglacial_terrain_mapping", - "short_name": "NDGI", - "type": "glacier", - "range": "(-1,1)" - }, - "NDMI": { - "bands": [ - "N", - "S1" - ], - "contributor": "vito", - "date_of_addition": "2021-10-27", - "formula": "(N - S1) / (N + S1)", - "long_name": "Normalized Difference Moisture Index", - "reference": "https://rdrr.io/cran/LSRS/man/NDMI.html", - "short_name": "NDMI", - "type": "moisture", - "range": "(-1,1)" - }, - "NDRE1": { - "bands": [ - "N", - "RE1" - ], - "contributor": "vito", - "date_of_addition": "2021-10-27", - "formula": "(N - RE1) / (N + RE1)", - "long_name": "Normalized Difference Red Edge 1", - "reference": "", - "short_name": "NDRE1", - "type": "vegetation", - "range": "(-1,1)" - }, - "NDRE2": { - "bands": [ - "N", - "RE2" - ], - "contributor": "vito", - "date_of_addition": "2021-10-27", - "formula": "(N - RE2) / (N + RE2)", - "long_name": "Normalized Difference Red Edge 2", - "reference": "", - "short_name": "NDRE2", - "type": "vegetation", - "range": "(-1,1)" - }, - "NDRE5": { - "bands": [ - "RE1", - "RE3" - ], - "contributor": "vito", - "date_of_addition": "2021-10-27", - "formula": "(RE3 - RE1) / (RE3 + RE1)", - "long_name": "Normalized Difference Red Edge 5", - "reference": "", - "short_name": "NDRE5", - "type": "vegetation", - "range": "(-1,1)" - }, "NDREI": { "bands": [ "N", @@ -767,8 +681,7 @@ "long_name": "Normalized Difference Vegetation Index", "reference": "https://doi.org/10.1016/0034-4257(79)90013-0", "short_name": "NDVI", - "type": "vegetation", - "range": "(0,1)" + "type": "vegetation" }, "NDVIT": { "bands": [ diff --git a/openeo/extra/spectral_indices/resources/vito-indices-dict.json b/openeo/extra/spectral_indices/resources/vito-indices-dict.json new file mode 100644 index 000000000..202e896df --- /dev/null +++ b/openeo/extra/spectral_indices/resources/vito-indices-dict.json @@ -0,0 +1,84 @@ +{ + "SpectralIndices": { + "ANIR": { + "bands": + [ + "R", + "N", + "S1" + ], + "contributor": "vito", + "date_of_addition": "2021-10-27", + "formula": "exec('import numpy as np') or exec('from openeo.processes import clip') or np.arccos(clip((( np.sqrt( (0.8328 - 0.6646)**2 + (N - R)**2 )**2 + np.sqrt( (1.610 - 0.8328)**2 + (S1 - N)**2 )**2 - np.sqrt( (1.610 - 0.6646)**2 + (S1 - R)**2 )**2 ) / (2 * np.sqrt( (0.8328 - 0.6646)**2 + (N - R)**2 ) * np.sqrt( (1.610 - 0.8328)**2 + (S1 - N)**2 ))), -1,1)) * (1. / np.pi)", + "long_name": "Angle at Near InfraRed", + "reference": "", + "short_name": "ANIR", + "type": "vegetation" + }, + "NDGI": { + "bands": [ + "G", + "R" + ], + "contributor": "vito", + "date_of_addition": "2021-10-27", + "formula": "(G - R) / (G + R)", + "long_name": "Normalized Difference Glacier Index", + "reference": "https://www.researchgate.net/publication/248978081_ASTER_ratio_indices_for_supraglacial_terrain_mapping", + "short_name": "NDGI", + "type": "glacier" + }, + "NDMI": { + "bands": [ + "N", + "S1" + ], + "contributor": "vito", + "date_of_addition": "2021-10-27", + "formula": "(N - S1) / (N + S1)", + "long_name": "Normalized Difference Moisture Index", + "reference": "https://rdrr.io/cran/LSRS/man/NDMI.html", + "short_name": "NDMI", + "type": "moisture" + }, + "NDRE1": { + "bands": [ + "N", + "RE1" + ], + "contributor": "vito", + "date_of_addition": "2021-10-27", + "formula": "(N - RE1) / (N + RE1)", + "long_name": "Normalized Difference Red Edge 1", + "reference": "", + "short_name": "NDRE1", + "type": "vegetation" + }, + "NDRE2": { + "bands": [ + "N", + "RE2" + ], + "contributor": "vito", + "date_of_addition": "2021-10-27", + "formula": "(N - RE2) / (N + RE2)", + "long_name": "Normalized Difference Red Edge 2", + "reference": "", + "short_name": "NDRE2", + "type": "vegetation" + }, + "NDRE5": { + "bands": [ + "RE1", + "RE3" + ], + "contributor": "vito", + "date_of_addition": "2021-10-27", + "formula": "(RE3 - RE1) / (RE3 + RE1)", + "long_name": "Normalized Difference Red Edge 5", + "reference": "", + "short_name": "NDRE5", + "type": "vegetation" + } + } +} \ No newline at end of file diff --git a/openeo/extra/spectral_indices/spectral_indices.py b/openeo/extra/spectral_indices/spectral_indices.py index c239f9097..859dbbddc 100644 --- a/openeo/extra/spectral_indices/spectral_indices.py +++ b/openeo/extra/spectral_indices/spectral_indices.py @@ -3,19 +3,14 @@ import numpy as np -from openeo.processes import ProcessBuilder, array_modify +from openeo.processes import ProcessBuilder, array_modify, array_create from openeo.rest.datacube import DataCube def _get_expression_map(cube: DataCube, x: ProcessBuilder): - collection_id = cube.metadata.get("id") + collection_id = cube.metadata.get("id").upper() bands = [band.replace("0", "").upper() for band in cube.metadata.band_names] - def check_validity(): - if not all(band in band_mapping.keys() for band in bands): - raise ValueError( - "The bands in your cube {} are not all {} bands (the following are: {})".format(bands,collection_id,band_mapping.keys())) - def get_params(): return {band_mapping[key]: x.array_element(i) for i, key in enumerate(bands)} @@ -25,6 +20,7 @@ def get_params(): probav_mapping = {"BLUE":"B", "RED":"R", "NIR":"N", "SWIR":"S1"} sentinel2_mapping = {"B1":"A", "B2":"B", "B3":"G", "B4":"R", "B5":"RE1", "B6":"RE2", "B7":"RE3", "B8":"N", "B8A":"RE4", "B9":"WV", "B11":"S1", "B12":"S2"} + # TODO: See if we can use common band names from collections instead of hardcoded mapping if "LANDSAT8" in collection_id: band_mapping = landsat8_mapping elif "LANDSAT" in collection_id: @@ -38,45 +34,180 @@ def get_params(): else: raise Exception("Sorry, satellite platform "+collection_id+" is not supported for index computation!") - check_validity() + if not all(band in band_mapping.keys() for band in bands): + raise ValueError( + "The bands in your cube {} are not all {} bands (the following are: {})".format(bands, collection_id, + list(band_mapping.keys()))) return get_params() - -def _callback(x: ProcessBuilder, index_list: list, datacube: DataCube, uplim_rescale: int, index_specs) -> ProcessBuilder: +def _check_params(item,params): + range_vals = ["input_range","output_range"] + if set(params) != set(range_vals): + raise ValueError("You have set the following parameters {} on {}, while the following are required {}".format(params,item,range_vals)) + for rng in range_vals: + if params[rng] == None: + continue + if len(params[rng]) != 2: + raise ValueError("The list of values you have supplied {} for parameter {} for {} is not of length 2".format(params[rng], rng, item)) + if not all(isinstance(val, int) for val in params[rng]): + raise ValueError("The ranges you supplied are not all of type int") + if (params["input_range"] == None) != (params["output_range"] == None): + raise ValueError("The index_range and output_range of {} should either be both supplied, or both None".format(item)) + +def _check_validity_index_dict(index_dict: dict, index_specs: dict): + input_vals = ["collection", "indices"] + if set(index_dict.keys()) != set(input_vals): + raise ValueError("The first level of the dictionary should contain the keys 'collection' and 'indices', but they contain {}".format(index_dict.keys())) + _check_params("collection",index_dict["collection"]) + for index,params in index_dict["indices"].items(): + if index not in index_specs.keys(): + raise NotImplementedError("Index " + index + " has not been implemented.") + _check_params(index, params) + + +def _callback(x: ProcessBuilder, index_dict: list, datacube: DataCube, index_specs, append) -> ProcessBuilder: index_values = [] x_res = x - params = _get_expression_map(datacube, x) - for index_name in index_list: - if index_name not in index_specs.keys(): - raise NotImplementedError("Index " + index_name + " has not been implemented.") - index_result = eval(index_specs[index_name]["formula"], params) - if uplim_rescale is not None: - if "range" not in index_specs[index_name].keys(): - raise ValueError( - "You want to scale the " + index_name + ", however the range of this index has not been supplied in the indices json yet.") - index_result = index_result.linear_scale_range(*eval(index_specs[index_name]["range"]), 0, uplim_rescale) + idx_data = _get_expression_map(datacube, x) + for index, params in index_dict["indices"].items(): + index_result = eval(index_specs[index]["formula"], idx_data) + if params["input_range"] is not None: + index_result = index_result.linear_scale_range(*params["input_range"], *params["output_range"]) index_values.append(index_result) - if uplim_rescale is not None: - x_res = x_res.linear_scale_range(0, 8000, 0, uplim_rescale) - return array_modify(data=x_res, values=index_values, index=len(datacube.metadata.band_names)) + if index_dict["collection"]["input_range"] is not None: + x_res = x_res.linear_scale_range(*index_dict["collection"]["input_range"], *index_dict["collection"]["output_range"]) + if append: + return array_modify(data=x_res, values=index_values, index=len(datacube.metadata.band_names)) + else: + return array_create(data=index_values) -def compute_indices(datacube: DataCube, index_list: list, uplim_rescale: int = None) -> DataCube: +def compute_and_rescale_indices(datacube: DataCube, index_dict: dict, append=False) -> DataCube: """ Computes a list of indices from a datacube param datacube: an instance of openeo.rest.DataCube - param index_list: a list of indices. The indices that are implemented are derived from the eemont package created by davemlz: - https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json - param uplim_rescale: the upper range to which you want to scale the value (the result after rescaling will be [0,uplim_rescale]) + param index_dict: a dictionary that contains the input- and output range of the collection on which you calculate the indices + as well as the indices that you want to calculate with their responding input- and output ranges + It follows the following format: + { + "collection": { + "input_range": [0,8000], + "output_range": [0,250] + }, + "indices": { + "NDVI": { + "input_range": [-1,1], + "output_range": [0,250] + }, + } + } + The indices that are implemented are derived from the eemont package created by davemlz: + https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json and have been further supplemented by + Vito with the indices ANIR, NDGI, NDMI, NDRE1, NDRE2 and NDRE5. + If you don't want to rescale your data, you can fill the input-, index- and output range with None. return: the datacube with the indices attached as bands - """ # TODO: use pkg_resources here instead of direct file reading with (Path(__file__).parent / "resources/spectral-indices-dict.json").open() as f: index_specs = json.load(f)["SpectralIndices"] - return datacube.apply_dimension(dimension="bands", - process=lambda x: _callback(x, index_list, datacube, uplim_rescale, - index_specs)).rename_labels('bands', - target=datacube.metadata.band_names + index_list) \ No newline at end of file + with (Path(__file__).parent / "resources/vito-indices-dict.json").open() as f: + index_specs.update(json.load(f)["SpectralIndices"]) + + _check_validity_index_dict(index_dict, index_specs) + res = datacube.apply_dimension(dimension="bands",process=lambda x: _callback(x, index_dict, datacube, index_specs, append)) + if append: + return res.rename_labels('bands',target=datacube.metadata.band_names + list(index_dict["indices"].keys())) + else: + return res.rename_labels('bands',target=list(index_dict["indices"].keys())) + +def append_and_rescale_indices(datacube: DataCube, index_dict: dict) -> DataCube: + """ + Computes a list of indices from a datacube and appends them to the existing datacube + + param datacube: an instance of openeo.rest.DataCube + param index_dict: a dictionary that contains the input- and output range of the collection on which you calculate the indices + as well as the indices that you want to calculate with their responding input- and output ranges + It follows the following format: + { + "collection": { + "input_range": [0,8000], + "output_range": [0,250] + }, + "indices": { + "NDVI": { + "input_range": [-1,1], + "output_range": [0,250] + }, + } + } + The indices that are implemented are derived from the eemont package created by davemlz: + https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json and have been further supplemented by + Vito with the indices ANIR, NDGI, NDMI, NDRE1, NDRE2 and NDRE5. + If you don't want to rescale your data, you can fill the input-, index- and output range with None. + return: the datacube with the indices attached as bands + """ + return compute_and_rescale_indices(datacube, index_dict, True) + +def compute_indices(datacube: DataCube, indices: list, append=False): + """ + Computes an indefinite number of indices specified by the user from a datacube + + param datacube: an instance of openeo.rest.DataCube + param index: the index you want to calculate + The indices that are implemented are derived from the eemont package created by davemlz: + https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json and have been further supplemented by + Vito with the indices ANIR, NDGI, NDMI, NDRE1, NDRE2 and NDRE5. + return: a new datacube with the index as band + """ + index_dict = { + "collection": { + "input_range": None, + "output_range": None + }, + "indices": { + index: { "input_range": None, "output_range": None } for index in indices + } + } + return compute_and_rescale_indices(datacube, index_dict, append) + +def append_indices(datacube: DataCube, indices: list): + """ + Calculate an indefinite number of indices specified by the user and appends them to a datacube + + param datacube: an instance of openeo.rest.DataCube + param indices: the indices you want to calculate and append to the cube + The indices that are implemented are derived from the eemont package created by davemlz: + https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json and have been further supplemented by + Vito with the indices ANIR, NDGI, NDMI, NDRE1, NDRE2 and NDRE5. + return: the old datacube with the indices as bands attached at the last index + """ + + return compute_indices(datacube, indices, True) + +def compute_index(datacube: DataCube, index: str, append=False): + """ + Computes a single index from a datacube + + param datacube: an instance of openeo.rest.DataCube + param index: the index you want to calculate + The indices that are implemented are derived from the eemont package created by davemlz: + https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json and have been further supplemented by + Vito with the indices ANIR, NDGI, NDMI, NDRE1, NDRE2 and NDRE5. + return: a new datacube with the index as band + """ + return compute_indices(datacube, [index], append) + +def append_index(datacube: DataCube, index: str): + """ + Calculate a single index and appends it to a datacube + + param datacube: an instance of openeo.rest.DataCube + param index: the index you want to calculate and append to the cube + The indices that are implemented are derived from the eemont package created by davemlz: + https://github.com/davemlz/eemont/blob/master/eemont/data/spectral-indices-dict.json and have been further supplemented by + Vito with the indices ANIR, NDGI, NDMI, NDRE1, NDRE2 and NDRE5. + return: the old datacube with the index as a band attached at the last index + """ + return compute_index(datacube, index, True) \ No newline at end of file diff --git a/tests/extra/spectral_indices/test_spectral_indices.py b/tests/extra/spectral_indices/test_spectral_indices.py index d6fdf6304..c4ff56277 100644 --- a/tests/extra/spectral_indices/test_spectral_indices.py +++ b/tests/extra/spectral_indices/test_spectral_indices.py @@ -1,6 +1,7 @@ from typing import List, Union -from openeo.extra.spectral_indices.spectral_indices import compute_indices +from openeo.extra.spectral_indices.spectral_indices import append_and_rescale_indices, compute_and_rescale_indices, \ + compute_indices, append_indices, compute_index, append_index from openeo.rest.datacube import DataCube @@ -11,9 +12,401 @@ def _extract_process_nodes(cube: Union[dict, DataCube], process_id: str) -> List return [d for d in cube.values() if d["process_id"] == process_id] -def test_simple_ndvi(con): +def test_compute_and_rescale_indices(con): cube = con.load_collection("Sentinel2") - indices = compute_indices(cube, ["NDVI"]) + + index_dict = { + "collection": { + "input_range": [0,8000], + "output_range": [0,250] + }, + "indices": { + "NDVI": { + "input_range": [-1,1], + "output_range": [0,250] + }, + "NDMI": { + "input_range": [-1,1], + "output_range": [0,250] + }, + "NDRE1": { + "input_range": [-1,1], + "output_range": [0,250] + } + } + } + indices = compute_and_rescale_indices(cube, index_dict) + apply_dim, = _extract_process_nodes(indices, "apply_dimension") + assert apply_dim["arguments"]["process"]["process_graph"] == { + "arrayelement1": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 7}, + }, + "arrayelement2": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 3}, + }, + 'arrayelement3': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 10}, + }, + 'arrayelement4': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 4}, + }, + "subtract1": { + "process_id": "subtract", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'subtract2': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement3'}}, + }, + 'subtract3': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement4'}}, + }, + "add1": { + "process_id": "add", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'add2': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement3'}}, + }, + 'add3': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement4'}}, + }, + "divide1": { + "process_id": "divide", + "arguments": {"x": {"from_node": "subtract1"}, "y": {"from_node": "add1"}}, + }, + 'divide2': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract2'}, 'y': {'from_node': 'add2'}}, + }, + 'divide3': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract3'}, 'y': {'from_node': 'add3'}}, + }, + 'linearscalerange1': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMax': 1,'inputMin': -1,'outputMax': 250,'outputMin': 0,'x': {'from_node': 'divide1'}}, + }, + 'linearscalerange2': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMax': 1,'inputMin': -1,'outputMax': 250,'outputMin': 0,'x': {'from_node': 'divide2'}}, + }, + 'linearscalerange3': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMax': 1,'inputMin': -1,'outputMax': 250,'outputMin': 0,'x': {'from_node': 'divide3'}}, + }, + "arraycreate1": { + "process_id": "array_create", + "arguments": {"data": [ + {"from_node": "linearscalerange1"}, + {'from_node': 'linearscalerange2'}, + {'from_node': 'linearscalerange3'} + ]}, + "result": True, + }, + } + +def test_append_and_rescale_indices(con): + cube = con.load_collection("Sentinel2") + + index_dict = { + "collection": { + "input_range": [0,8000], + "output_range": [0,250] + }, + "indices": { + "NDVI": { + "input_range": [-1,1], + "output_range": [0,250] + }, + "NDMI": { + "input_range": [-1,1], + "output_range": [0,250] + }, + "NDRE1": { + "input_range": [-1,1], + "output_range": [0,250] + } + } + } + indices = append_and_rescale_indices(cube, index_dict) + apply_dim, = _extract_process_nodes(indices, "apply_dimension") + assert apply_dim["arguments"]["process"]["process_graph"] == { + "arrayelement1": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 7}, + }, + "arrayelement2": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 3}, + }, + 'arrayelement3': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 10}, + }, + 'arrayelement4': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 4}, + }, + "subtract1": { + "process_id": "subtract", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'subtract2': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement3'}}, + }, + 'subtract3': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement4'}}, + }, + "add1": { + "process_id": "add", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'add2': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement3'}}, + }, + 'add3': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement4'}}, + }, + "divide1": { + "process_id": "divide", + "arguments": {"x": {"from_node": "subtract1"}, "y": {"from_node": "add1"}}, + }, + 'divide2': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract2'}, 'y': {'from_node': 'add2'}}, + }, + 'divide3': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract3'}, 'y': {'from_node': 'add3'}}, + }, + 'linearscalerange1': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMin': 0, 'inputMax': 8000, 'outputMin': 0,'outputMax': 250, + 'x': {'from_parameter': 'data'}}, + }, + 'linearscalerange2': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMax': 1, 'inputMin': -1, 'outputMax': 250, 'outputMin': 0, + 'x': {'from_node': 'divide1'}}, + }, + 'linearscalerange3': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMax': 1, 'inputMin': -1, 'outputMax': 250, 'outputMin': 0, + 'x': {'from_node': 'divide2'}}, + }, + 'linearscalerange4': { + 'process_id': 'linear_scale_range', + 'arguments': {'inputMax': 1, 'inputMin': -1, 'outputMax': 250, 'outputMin': 0, + 'x': {'from_node': 'divide3'}}, + }, + "arraymodify1": { + "process_id": "array_modify", + "arguments": {"data": {"from_node": "linearscalerange1"}, "index": 12, "values": [ + {'from_node': 'linearscalerange2'}, + {'from_node': 'linearscalerange3'}, + {'from_node': 'linearscalerange4'}, + ]}, + "result": True, + }, + } + + +def test_compute_indices(con): + cube = con.load_collection("Sentinel2") + + index_list = ["NDVI", "NDMI", "NDRE1"] + indices = compute_indices(cube, index_list) + apply_dim, = _extract_process_nodes(indices, "apply_dimension") + assert apply_dim["arguments"]["process"]["process_graph"] == { + "arrayelement1": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 7}, + }, + "arrayelement2": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 3}, + }, + 'arrayelement3': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 10}, + }, + 'arrayelement4': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 4}, + }, + "subtract1": { + "process_id": "subtract", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'subtract2': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement3'}}, + }, + 'subtract3': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement4'}}, + }, + "add1": { + "process_id": "add", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'add2': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement3'}}, + }, + 'add3': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement4'}}, + }, + "divide1": { + "process_id": "divide", + "arguments": {"x": {"from_node": "subtract1"}, "y": {"from_node": "add1"}}, + }, + 'divide2': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract2'}, 'y': {'from_node': 'add2'}}, + }, + 'divide3': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract3'}, 'y': {'from_node': 'add3'}}, + }, + "arraycreate1": { + "process_id": "array_create", + "arguments": {"data": [ + {"from_node": "divide1"}, + {'from_node': 'divide2'}, + {'from_node': 'divide3'} + ]}, + "result": True, + }, + } + + +def test_append_indices(con): + cube = con.load_collection("Sentinel2") + + index_list = ["NDVI", "NDMI", "NDRE1"] + indices = append_indices(cube, index_list) + apply_dim, = _extract_process_nodes(indices, "apply_dimension") + assert apply_dim["arguments"]["process"]["process_graph"] == { + "arrayelement1": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 7}, + }, + "arrayelement2": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 3}, + }, + 'arrayelement3': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 10}, + }, + 'arrayelement4': { + 'process_id': 'array_element', + 'arguments': {'data': {'from_parameter': 'data'}, 'index': 4}, + }, + "subtract1": { + "process_id": "subtract", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'subtract2': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement3'}}, + }, + 'subtract3': { + 'process_id': 'subtract', + 'arguments': {'x': {'from_node': 'arrayelement1'}, 'y': {'from_node': 'arrayelement4'}}, + }, + "add1": { + "process_id": "add", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + 'add2': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement3'}}, + }, + 'add3': { + 'process_id': 'add', + 'arguments': {'x': {'from_node': 'arrayelement1'},'y': {'from_node': 'arrayelement4'}}, + }, + "divide1": { + "process_id": "divide", + "arguments": {"x": {"from_node": "subtract1"}, "y": {"from_node": "add1"}}, + }, + 'divide2': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract2'}, 'y': {'from_node': 'add2'}}, + }, + 'divide3': { + 'process_id': 'divide', + 'arguments': {'x': {'from_node': 'subtract3'}, 'y': {'from_node': 'add3'}}, + }, + "arraymodify1": { + "process_id": "array_modify", + "arguments": {"data": {"from_parameter": "data"}, "index": 12, "values": [ + {"from_node": "divide1"}, + {'from_node': 'divide2'}, + {'from_node': 'divide3'} + ]}, + "result": True, + }, + } + + +def test_compute_index(con): + cube = con.load_collection("Sentinel2") + + index = "NDVI" + indices = compute_index(cube, index) + apply_dim, = _extract_process_nodes(indices, "apply_dimension") + assert apply_dim["arguments"]["process"]["process_graph"] == { + "arrayelement1": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 7}, + }, + "arrayelement2": { + "process_id": "array_element", + "arguments": {"data": {"from_parameter": "data"}, "index": 3}, + }, + "subtract1": { + "process_id": "subtract", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + "add1": { + "process_id": "add", + "arguments": {"x": {"from_node": "arrayelement1"}, "y": {"from_node": "arrayelement2"}}, + }, + "divide1": { + "process_id": "divide", + "arguments": {"x": {"from_node": "subtract1"}, "y": {"from_node": "add1"}}, + }, + "arraycreate1": { + "process_id": "array_create", + "arguments": {"data": [ + {"from_node": "divide1"}, + ]}, + "result": True, + }, + } + + +def test_append_index(con): + cube = con.load_collection("Sentinel2") + + index = "NDVI" + indices = append_index(cube, index) apply_dim, = _extract_process_nodes(indices, "apply_dimension") assert apply_dim["arguments"]["process"]["process_graph"] == { "arrayelement1": { @@ -38,7 +431,9 @@ def test_simple_ndvi(con): }, "arraymodify1": { "process_id": "array_modify", - "arguments": {"data": {"from_parameter": "data"}, "index": 12, "values": [{"from_node": "divide1"}]}, + "arguments": {"data": {"from_parameter": "data"}, "index": 12, "values": [ + {"from_node": "divide1"}, + ]}, "result": True, }, } diff --git a/tests/test_spectral_indices.py b/tests/test_spectral_indices.py deleted file mode 100644 index f4c6e3e33..000000000 --- a/tests/test_spectral_indices.py +++ /dev/null @@ -1,43 +0,0 @@ -from unittest.mock import Mock, MagicMock -from extra.spectral_indices.spectral_indices import _callback, _get_expression_map -from openeo.rest.connection import Connection -from processes import array_create, ProcessBuilder -from tests.rest.datacube.conftest import con100 - -# cube = Mock() -# cube.metadata.band_names = ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B8A", "B9", "B11", "B12"] -# cube.metadata.get = MagicMock(return_value="TERRASCOPE_S2_TOC_V2") -index_specs = { - "NDRE1": { - "bands": ["N","RE1"], - "formula": "(N - RE1) / (N + RE1)", - "range": "(-1,1)" - }, - "NDGI": { - "bands": ["G","R"], - "formula": "(G - R) / (G + R)", - "range": "(-1,1)" - }, - "NDRE5": { - "bands": ["RE1","RE3"], - "formula": "(RE3 - RE1) / (RE3 + RE1)", - "range": "(-1,1)" - } -} - - -def test_get_expression_map(con100: Connection): - cube = con100.load_collection("S2",bands=["B02","B03","B04","B08"]).filter_bbox(*[3, 51, 4, 52]) - cube.metadata.get = MagicMock(return_value="TERRASCOPE_S2_TOC_V2") - assert True - # assert _get_expression_map(cube, x)["B"] == array_create([9]) - -def test_callback(): - index_name = "NDGI" - uplim_rescale = 250 - index_result = eval(index_specs[index_name]["formula"], {"G": 5, "R": 2, "RE1": 3}) - print(index_result) - index_result = index_result.linear_scale_range(*eval(index_specs[index_name]["range"]), 0, uplim_rescale) - print(index_result) - assert True -