Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
488262f
issue #693 add search for schema and find parameters from the backend
ElienVandermaesenVITO Jan 6, 2025
8699f3c
issue #693 use openeo.internal.process.parse intead of new functions
ElienVandermaesenVITO Jan 8, 2025
7440d71
issue #698 add exception and change documentation
ElienVandermaesenVITO Jan 9, 2025
b506a11
issue #698 add fallback situations
ElienVandermaesenVITO Jan 9, 2025
71b2b79
issue #693 add mock for sar_backscatter test for processes
ElienVandermaesenVITO Jan 9, 2025
53936a6
Merge branch 'master' into issue693-sar_backscatter-get-coefficients-…
ElienVandermaesenVITO Jan 9, 2025
ece05dc
issue #693 add test for search_list_for_dict_key
ElienVandermaesenVITO Jan 9, 2025
503154b
issue #693 add parse.get_parameter, to retrieve parameter in schema m…
ElienVandermaesenVITO Jan 14, 2025
a4b79a0
issue693 move adapt search_list_for_dict_key to get_enum_options for …
ElienVandermaesenVITO Feb 4, 2025
454cf79
issue693 run pre-commit
ElienVandermaesenVITO Feb 4, 2025
877c1a3
Merge branch 'master' into issue693-sar_backscatter-get-coefficients-…
ElienVandermaesenVITO Feb 4, 2025
d8a4718
issue693 add test for sar_backscatter with coefficient check
ElienVandermaesenVITO Feb 4, 2025
7712675
issue693 use default option from backend in datacube.sar_backscatter
ElienVandermaesenVITO Feb 11, 2025
754a399
issue693 replace default by unset and combine enum options
ElienVandermaesenVITO Feb 21, 2025
a2bda78
issue693 detect type null in schema
ElienVandermaesenVITO Feb 27, 2025
6120186
issue693 add changelog entry
ElienVandermaesenVITO Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- 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))
- `sar_backscatter`: try to retrieve coefficient options from backend ([#693](https://github.com/Open-EO/openeo-python-client/issues/693))

### Changed

Expand Down
24 changes: 24 additions & 0 deletions openeo/internal/processes/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import copy
import json
import re
import typing
Expand Down Expand Up @@ -43,6 +44,23 @@ def is_geojson_schema(schema) -> bool:
return any(is_geojson_schema(s) for s in self.schema)
return False

def get_enum_options(self):
result = None
if isinstance(self.schema,list):
for item in self.schema:
if "enum" in item:
if result is None:
result = copy.deepcopy(item["enum"])
else:
result += item["enum"]
elif "type" in item:
if item["type"] == "null":
result += [None]
elif isinstance(self.schema,dict):
if "enum" in self.schema:
result = self.schema["enum"]
return result


_NO_DEFAULT = object()

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

def get_parameter(self, name: str) -> Parameter:
for parameter in self.parameters:
if parameter.name == name:
return parameter
raise LookupError(f"Parameter {name!r} not found.")


def parse_all_from_dir(path: Union[str, Path], pattern="*.json") -> Iterator[Process]:
"""Parse all openEO process files in given directory"""
Expand Down
42 changes: 30 additions & 12 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
convert_callable_to_pgnode,
get_parameter_names,
)
from openeo.internal.processes.parse import Process
from openeo.internal.warnings import UserDeprecationWarning, deprecated, legacy_alias
from openeo.metadata import (
Band,
Expand Down Expand Up @@ -2724,15 +2725,15 @@ def ard_normalized_radar_backscatter(

@openeo_process
def sar_backscatter(
self,
coefficient: Union[str, None] = "gamma0-terrain",
elevation_model: Union[str, None] = None,
mask: bool = False,
contributing_area: bool = False,
local_incidence_angle: bool = False,
ellipsoid_incidence_angle: bool = False,
noise_removal: bool = True,
options: Optional[dict] = None
self,
coefficient: Union[str, None] = _UNSET,
elevation_model: Union[str, None] = None,
mask: bool = False,
contributing_area: bool = False,
local_incidence_angle: bool = False,
ellipsoid_incidence_angle: bool = False,
noise_removal: bool = True,
options: Optional[dict] = None,
) -> DataCube:
"""
Computes backscatter from SAR input.
Expand Down Expand Up @@ -2767,9 +2768,26 @@ def sar_backscatter(
.. versionadded:: 0.4.9
.. versionchanged:: 0.4.10 replace `orthorectify` and `rtc` arguments with `coefficient`.
"""
coefficient_options = [
"beta0", "sigma0-ellipsoid", "sigma0-terrain", "gamma0-ellipsoid", "gamma0-terrain", None
]
try:
parameter = Process.from_dict(self.connection.describe_process("sar_backscatter")).get_parameter(
"coefficient"
)
schema = parameter.schema
coefficient_options = schema.get_enum_options()
if coefficient == _UNSET:
coefficient = parameter.default
except Exception as e:
log.warning(f"Failed to extract coefficient options for process `sar_backscatter`: {e}")
coefficient_options = [
"beta0",
"sigma0-ellipsoid",
"sigma0-terrain",
"gamma0-ellipsoid",
"gamma0-terrain",
None,
]
if coefficient == _UNSET:
coefficient = "gamma0-terrain"
if coefficient not in coefficient_options:
raise OpenEoClientException("Invalid `sar_backscatter` coefficient {c!r}. Should be one of {o}".format(
c=coefficient, o=coefficient_options
Expand Down
88 changes: 88 additions & 0 deletions tests/internal/processes/test_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,49 @@ def test_schema_accepts_geojson(schema, expected):
assert Schema([{"type": "number"}, schema]).accepts_geojson() == expected


@pytest.mark.parametrize(
("schema", "expected"),
[
(
Schema(
{
"type": "string",
"enum": [
"average",
"bilinear",
"cubic",
"cubicspline",
"lanczos",
"max",
"med",
"min",
"mode",
"near",
],
},
),
["average", "bilinear", "cubic", "cubicspline", "lanczos", "max", "med", "min", "mode", "near"],
),
(
Schema([{"type": "string", "enum": ["replicate", "reflect", "reflect_pixel", "wrap"]}, {"type": "null"}]),
["replicate", "reflect", "reflect_pixel", "wrap", None],
),
(
Schema(
[
{"type": "string", "enum": ["replicate", "reflect"]},
{"type": "null"},
{"type": "number", "enum": ["reflect_pixel", "wrap"]},
]
),
["replicate", "reflect", None, "reflect_pixel", "wrap"],
),
],
)
def test_get_enum_options(schema, expected):
assert schema.get_enum_options() == expected


def test_parameter():
p = Parameter.from_dict({
"name": "foo",
Expand Down Expand Up @@ -138,6 +181,51 @@ def test_process_from_json():
assert p.returns.schema.schema == {"type": ["number", "null"], "minimum": 0}


@pytest.mark.parametrize(
("key", "expected"), [
("x", Parameter.from_dict({"name": "x", "description": "A number.", "schema": {"type": ["number", "null"]}})),
("y", Parameter.from_dict({"name": "y", "description": "A number.", "schema": {"type": ["number", "null"]}})),
]
)
def test_get_parameter(key, expected):
p = Process.from_dict({
"id": "absolute",
"summary": "Absolute value",
"description": "Computes the absolute value of a real number.",
"categories": ["math"],
"parameters": [
{"name": "x", "description": "A number.", "schema": {"type": ["number", "null"]}},
{"name": "y", "description": "A number.", "schema": {"type": ["number", "null"]}},
],
"returns": {
"description": "The computed absolute value.",
"schema": {"type": ["number", "null"], "minimum": 0}
},
"links": [{"rel": "about", "href": "http://example.com/abs.html"}],
})
assert p.get_parameter(key) == expected


def test_get_parameter_error():
p = Process.from_dict({
"id": "absolute",
"summary": "Absolute value",
"description": "Computes the absolute value of a real number.",
"categories": ["math"],
"parameters": [
{"name": "x", "description": "A number.", "schema": {"type": ["number", "null"]}},
{"name": "y", "description": "A number.", "schema": {"type": ["number", "null"]}},
],
"returns": {
"description": "The computed absolute value.",
"schema": {"type": ["number", "null"], "minimum": 0}
},
"links": [{"rel": "about", "href": "http://example.com/abs.html"}],
})
with pytest.raises(LookupError):
p.get_parameter("z")


def test_parse_remote_process_definition_minimal(requests_mock):
url = "https://example.com/ndvi.json"
requests_mock.get(url, json={"id": "ndvi"})
Expand Down
47 changes: 47 additions & 0 deletions tests/rest/datacube/test_datacube100.py
Original file line number Diff line number Diff line change
Expand Up @@ -2828,6 +2828,53 @@ def test_sar_backscatter_coefficient_invalid(con100):
cube.sar_backscatter(coefficient="unicorn")


def test_sar_backscatter_check_coefficient_backend(con100, requests_mock):
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
processes = [
{
"id": "sar_backscatter",
"description": "Computes backscatter from SAR input",
"summary": "Computes backscatter from SAR input",
"parameters": [
{
"default": "gamma0-ellipsoid",
"description": "Select the radiometric correction coefficient.",
"name": "coefficient",
"schema": [
{
"enum": [
"beta0",
"sigma0-ellipsoid",
"sigma0-terrain",
"gamma0-ellipsoid",
"gamma0-terrain",
],
"type": "string",
},
],
},
],
"returns": {"description": "incremented value", "schema": {"type": "integer"}},
}
]
requests_mock.get(API_URL + "/processes", json={"processes": processes})
cube = con100.load_collection("S2").sar_backscatter()
assert _get_leaf_node(cube) == {
"process_id": "sar_backscatter",
"arguments": {
"data": {"from_node": "loadcollection1"},
"coefficient": "gamma0-ellipsoid",
"elevation_model": None,
"mask": False,
"contributing_area": False,
"local_incidence_angle": False,
"ellipsoid_incidence_angle": False,
"noise_removal": True,
},
"result": True,
}


def test_datacube_from_process(con100):
cube = con100.datacube_from_process("colorize", color="red", size=4)
assert cube.flat_graph() == {
Expand Down