Skip to content

Commit 8ce52ce

Browse files
authored
Merge pull request #698 from Open-EO/issue693-sar_backscatter-get-coefficients-from-schema
issue #693 add search for schema and find parameters from the backend
2 parents 2e04f3e + 6120186 commit 8ce52ce

File tree

5 files changed

+190
-12
lines changed

5 files changed

+190
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4646
### Added
4747

4848
- Add initial support for accessing [Federation Extension](https://github.com/Open-EO/openeo-api/tree/master/extensions/federation) related metadata ([#668](https://github.com/Open-EO/openeo-python-client/issues/668))
49+
- `sar_backscatter`: try to retrieve coefficient options from backend ([#693](https://github.com/Open-EO/openeo-python-client/issues/693))
4950

5051
### Changed
5152

openeo/internal/processes/parse.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from __future__ import annotations
77

8+
import copy
89
import json
910
import re
1011
import typing
@@ -43,6 +44,23 @@ def is_geojson_schema(schema) -> bool:
4344
return any(is_geojson_schema(s) for s in self.schema)
4445
return False
4546

47+
def get_enum_options(self):
48+
result = None
49+
if isinstance(self.schema,list):
50+
for item in self.schema:
51+
if "enum" in item:
52+
if result is None:
53+
result = copy.deepcopy(item["enum"])
54+
else:
55+
result += item["enum"]
56+
elif "type" in item:
57+
if item["type"] == "null":
58+
result += [None]
59+
elif isinstance(self.schema,dict):
60+
if "enum" in self.schema:
61+
result = self.schema["enum"]
62+
return result
63+
4664

4765
_NO_DEFAULT = object()
4866

@@ -127,6 +145,12 @@ def from_json_file(cls, path: Union[str, Path]) -> Process:
127145
with Path(path).open("r") as f:
128146
return cls.from_json(f.read())
129147

148+
def get_parameter(self, name: str) -> Parameter:
149+
for parameter in self.parameters:
150+
if parameter.name == name:
151+
return parameter
152+
raise LookupError(f"Parameter {name!r} not found.")
153+
130154

131155
def parse_all_from_dir(path: Union[str, Path], pattern="*.json") -> Iterator[Process]:
132156
"""Parse all openEO process files in given directory"""

openeo/rest/datacube.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
convert_callable_to_pgnode,
3636
get_parameter_names,
3737
)
38+
from openeo.internal.processes.parse import Process
3839
from openeo.internal.warnings import UserDeprecationWarning, deprecated, legacy_alias
3940
from openeo.metadata import (
4041
Band,
@@ -2771,15 +2772,15 @@ def ard_normalized_radar_backscatter(
27712772

27722773
@openeo_process
27732774
def sar_backscatter(
2774-
self,
2775-
coefficient: Union[str, None] = "gamma0-terrain",
2776-
elevation_model: Union[str, None] = None,
2777-
mask: bool = False,
2778-
contributing_area: bool = False,
2779-
local_incidence_angle: bool = False,
2780-
ellipsoid_incidence_angle: bool = False,
2781-
noise_removal: bool = True,
2782-
options: Optional[dict] = None
2775+
self,
2776+
coefficient: Union[str, None] = _UNSET,
2777+
elevation_model: Union[str, None] = None,
2778+
mask: bool = False,
2779+
contributing_area: bool = False,
2780+
local_incidence_angle: bool = False,
2781+
ellipsoid_incidence_angle: bool = False,
2782+
noise_removal: bool = True,
2783+
options: Optional[dict] = None,
27832784
) -> DataCube:
27842785
"""
27852786
Computes backscatter from SAR input.
@@ -2814,9 +2815,26 @@ def sar_backscatter(
28142815
.. versionadded:: 0.4.9
28152816
.. versionchanged:: 0.4.10 replace `orthorectify` and `rtc` arguments with `coefficient`.
28162817
"""
2817-
coefficient_options = [
2818-
"beta0", "sigma0-ellipsoid", "sigma0-terrain", "gamma0-ellipsoid", "gamma0-terrain", None
2819-
]
2818+
try:
2819+
parameter = Process.from_dict(self.connection.describe_process("sar_backscatter")).get_parameter(
2820+
"coefficient"
2821+
)
2822+
schema = parameter.schema
2823+
coefficient_options = schema.get_enum_options()
2824+
if coefficient == _UNSET:
2825+
coefficient = parameter.default
2826+
except Exception as e:
2827+
log.warning(f"Failed to extract coefficient options for process `sar_backscatter`: {e}")
2828+
coefficient_options = [
2829+
"beta0",
2830+
"sigma0-ellipsoid",
2831+
"sigma0-terrain",
2832+
"gamma0-ellipsoid",
2833+
"gamma0-terrain",
2834+
None,
2835+
]
2836+
if coefficient == _UNSET:
2837+
coefficient = "gamma0-terrain"
28202838
if coefficient not in coefficient_options:
28212839
raise OpenEoClientException("Invalid `sar_backscatter` coefficient {c!r}. Should be one of {o}".format(
28222840
c=coefficient, o=coefficient_options

tests/internal/processes/test_parse.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,49 @@ def test_schema_accepts_geojson(schema, expected):
3636
assert Schema([{"type": "number"}, schema]).accepts_geojson() == expected
3737

3838

39+
@pytest.mark.parametrize(
40+
("schema", "expected"),
41+
[
42+
(
43+
Schema(
44+
{
45+
"type": "string",
46+
"enum": [
47+
"average",
48+
"bilinear",
49+
"cubic",
50+
"cubicspline",
51+
"lanczos",
52+
"max",
53+
"med",
54+
"min",
55+
"mode",
56+
"near",
57+
],
58+
},
59+
),
60+
["average", "bilinear", "cubic", "cubicspline", "lanczos", "max", "med", "min", "mode", "near"],
61+
),
62+
(
63+
Schema([{"type": "string", "enum": ["replicate", "reflect", "reflect_pixel", "wrap"]}, {"type": "null"}]),
64+
["replicate", "reflect", "reflect_pixel", "wrap", None],
65+
),
66+
(
67+
Schema(
68+
[
69+
{"type": "string", "enum": ["replicate", "reflect"]},
70+
{"type": "null"},
71+
{"type": "number", "enum": ["reflect_pixel", "wrap"]},
72+
]
73+
),
74+
["replicate", "reflect", None, "reflect_pixel", "wrap"],
75+
),
76+
],
77+
)
78+
def test_get_enum_options(schema, expected):
79+
assert schema.get_enum_options() == expected
80+
81+
3982
def test_parameter():
4083
p = Parameter.from_dict({
4184
"name": "foo",
@@ -138,6 +181,51 @@ def test_process_from_json():
138181
assert p.returns.schema.schema == {"type": ["number", "null"], "minimum": 0}
139182

140183

184+
@pytest.mark.parametrize(
185+
("key", "expected"), [
186+
("x", Parameter.from_dict({"name": "x", "description": "A number.", "schema": {"type": ["number", "null"]}})),
187+
("y", Parameter.from_dict({"name": "y", "description": "A number.", "schema": {"type": ["number", "null"]}})),
188+
]
189+
)
190+
def test_get_parameter(key, expected):
191+
p = Process.from_dict({
192+
"id": "absolute",
193+
"summary": "Absolute value",
194+
"description": "Computes the absolute value of a real number.",
195+
"categories": ["math"],
196+
"parameters": [
197+
{"name": "x", "description": "A number.", "schema": {"type": ["number", "null"]}},
198+
{"name": "y", "description": "A number.", "schema": {"type": ["number", "null"]}},
199+
],
200+
"returns": {
201+
"description": "The computed absolute value.",
202+
"schema": {"type": ["number", "null"], "minimum": 0}
203+
},
204+
"links": [{"rel": "about", "href": "http://example.com/abs.html"}],
205+
})
206+
assert p.get_parameter(key) == expected
207+
208+
209+
def test_get_parameter_error():
210+
p = Process.from_dict({
211+
"id": "absolute",
212+
"summary": "Absolute value",
213+
"description": "Computes the absolute value of a real number.",
214+
"categories": ["math"],
215+
"parameters": [
216+
{"name": "x", "description": "A number.", "schema": {"type": ["number", "null"]}},
217+
{"name": "y", "description": "A number.", "schema": {"type": ["number", "null"]}},
218+
],
219+
"returns": {
220+
"description": "The computed absolute value.",
221+
"schema": {"type": ["number", "null"], "minimum": 0}
222+
},
223+
"links": [{"rel": "about", "href": "http://example.com/abs.html"}],
224+
})
225+
with pytest.raises(LookupError):
226+
p.get_parameter("z")
227+
228+
141229
def test_parse_remote_process_definition_minimal(requests_mock):
142230
url = "https://example.com/ndvi.json"
143231
requests_mock.get(url, json={"id": "ndvi"})

tests/rest/datacube/test_datacube100.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2828,6 +2828,53 @@ def test_sar_backscatter_coefficient_invalid(con100):
28282828
cube.sar_backscatter(coefficient="unicorn")
28292829

28302830

2831+
def test_sar_backscatter_check_coefficient_backend(con100, requests_mock):
2832+
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
2833+
processes = [
2834+
{
2835+
"id": "sar_backscatter",
2836+
"description": "Computes backscatter from SAR input",
2837+
"summary": "Computes backscatter from SAR input",
2838+
"parameters": [
2839+
{
2840+
"default": "gamma0-ellipsoid",
2841+
"description": "Select the radiometric correction coefficient.",
2842+
"name": "coefficient",
2843+
"schema": [
2844+
{
2845+
"enum": [
2846+
"beta0",
2847+
"sigma0-ellipsoid",
2848+
"sigma0-terrain",
2849+
"gamma0-ellipsoid",
2850+
"gamma0-terrain",
2851+
],
2852+
"type": "string",
2853+
},
2854+
],
2855+
},
2856+
],
2857+
"returns": {"description": "incremented value", "schema": {"type": "integer"}},
2858+
}
2859+
]
2860+
requests_mock.get(API_URL + "/processes", json={"processes": processes})
2861+
cube = con100.load_collection("S2").sar_backscatter()
2862+
assert _get_leaf_node(cube) == {
2863+
"process_id": "sar_backscatter",
2864+
"arguments": {
2865+
"data": {"from_node": "loadcollection1"},
2866+
"coefficient": "gamma0-ellipsoid",
2867+
"elevation_model": None,
2868+
"mask": False,
2869+
"contributing_area": False,
2870+
"local_incidence_angle": False,
2871+
"ellipsoid_incidence_angle": False,
2872+
"noise_removal": True,
2873+
},
2874+
"result": True,
2875+
}
2876+
2877+
28312878
def test_datacube_from_process(con100):
28322879
cube = con100.datacube_from_process("colorize", color="red", size=4)
28332880
assert cube.flat_graph() == {

0 commit comments

Comments
 (0)