Skip to content

Commit

Permalink
Better argument handling in resample_spatial/resample_cube_spatial
Browse files Browse the repository at this point in the history
  • Loading branch information
soxofaan committed Jan 21, 2025
1 parent df49864 commit b5f0388
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and start a new "In Progress" section above it.

## In progress

- Better argument validation in `resample_spatial`/`resample_cube_spatial` (related to [Open-EO/openeo-python-client#690](https://github.com/Open-EO/openeo-python-client/issues/690))

## 0.123.0

Expand Down
28 changes: 14 additions & 14 deletions openeo_driver/ProcessGraphDeserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
Processing,
OpenEoBackendImplementation,
)
from openeo_driver.constants import RESAMPLE_SPATIAL_METHODS, RESAMPLE_SPATIAL_ALIGNS
from openeo_driver.datacube import (
DriverDataCube,
DriverVectorCube,
Expand Down Expand Up @@ -1582,24 +1583,23 @@ def ndvi(args: dict, env: EvalEnv) -> DriverDataCube:
@process
def resample_spatial(args: ProcessArgs, env: EvalEnv) -> DriverDataCube:
cube: DriverDataCube = args.get_required("data", expected_type=DriverDataCube)
resolution = args.get_optional("resolution", 0)
projection = args.get_optional("projection", None)
method = args.get_optional("method", "near")
align = args.get_optional("align", "upper-left")
resolution = args.get_optional(
"resolution",
default=0,
validator=lambda v: isinstance(v, (int, float)) or (isinstance(v, (tuple, list)) and len(v) == 2),
)
projection = args.get_optional("projection", default=None)
method = args.get_enum("method", options=RESAMPLE_SPATIAL_METHODS, default="near")
align = args.get_enum("align", options=RESAMPLE_SPATIAL_ALIGNS, default="upper-left")
return cube.resample_spatial(resolution=resolution, projection=projection, method=method, align=align)


@process
def resample_cube_spatial(args: dict, env: EvalEnv) -> DriverDataCube:
image_collection = extract_arg(args, 'data')
target_image_collection = extract_arg(args, 'target')
method = args.get('method', 'near')
if not isinstance(image_collection, DriverDataCube):
raise ProcessParameterInvalidException(
parameter="data", process="resample_cube_spatial",
reason=f"Invalid data type {type(image_collection)!r} expected raster-cube."
)
return image_collection.resample_cube_spatial(target=target_image_collection, method=method)
def resample_cube_spatial(args: ProcessArgs, env: EvalEnv) -> DriverDataCube:
cube: DriverDataCube = args.get_required("data", expected_type=DriverDataCube)
target: DriverDataCube = args.get_required("target", expected_type=DriverDataCube)
method = args.get_enum("method", options=RESAMPLE_SPATIAL_METHODS, default="near")
return cube.resample_cube_spatial(target=target, method=method)


@process
Expand Down
27 changes: 27 additions & 0 deletions openeo_driver/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,30 @@ class JOB_STATUS:
CANCELED = "canceled"
FINISHED = "finished"
ERROR = "error"


# Resample methods as used in official specs of `resample_spatial` and `resample_cube_spatial`
RESAMPLE_SPATIAL_METHODS = [
"average",
"bilinear",
"cubic",
"cubicspline",
"lanczos",
"max",
"med",
"min",
"mode",
"near",
"q1",
"q3",
"rms",
"sum",
]

# Align options as used in official spec of `resample_spatial`
RESAMPLE_SPATIAL_ALIGNS = [
"lower-left",
"upper-left",
"lower-right",
"upper-right",
]
4 changes: 2 additions & 2 deletions tests/data/pg/1.0/resample_and_merge_cubes.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"target": {
"from_node": "collection2"
},
"method": "cube"
"method": "cubic"
},
"result": false
},
Expand Down Expand Up @@ -52,4 +52,4 @@
},
"result": true
}
}
}
126 changes: 126 additions & 0 deletions tests/test_views_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,131 @@ def test_execute_merge_cubes(api):
assert args[1:] == ('or',)


def test_execute_resample_spatial_defaults(api):
api.check_result(
{
"lc": {"process_id": "load_collection", "arguments": {"id": "S2_FOOBAR"}},
"resample": {
"process_id": "resample_spatial",
"arguments": {"data": {"from_node": "lc"}},
"result": True,
},
}
)
dummy = dummy_backend.get_collection("S2_FOOBAR")
assert dummy.resample_spatial.call_count == 1
assert dummy.resample_spatial.call_args.kwargs == {
"resolution": 0,
"projection": None,
"method": "near",
"align": "upper-left",
}


def test_execute_resample_spatial_custom(api):
api.check_result(
{
"lc": {"process_id": "load_collection", "arguments": {"id": "S2_FOOBAR"}},
"resample": {
"process_id": "resample_spatial",
"arguments": {
"data": {"from_node": "lc"},
"resolution": [11, 123],
"projection": 3857,
"method": "cubic",
"align": "lower-right",
},
"result": True,
},
}
)
dummy = dummy_backend.get_collection("S2_FOOBAR")
assert dummy.resample_spatial.call_count == 1
assert dummy.resample_spatial.call_args.kwargs == {
"resolution": [11, 123],
"projection": 3857,
"method": "cubic",
"align": "lower-right",
}


@pytest.mark.parametrize(
"kwargs",
[
{"resolution": [1, 2, 3, 4, 5]},
{"method": "glossy"},
{"align": "justified"},
],
)
def test_execute_resample_spatial_invalid(api, kwargs):
res = api.result(
{
"lc": {"process_id": "load_collection", "arguments": {"id": "S2_FOOBAR"}},
"resample": {
"process_id": "resample_spatial",
"arguments": {"data": {"from_node": "lc"}, **kwargs},
"result": True,
},
}
)
res.assert_error(status_code=400, error_code="ProcessParameterInvalid")


def test_execute_resample_cube_spatial_defaults(api):
api.check_result(
{
"lc1": {"process_id": "load_collection", "arguments": {"id": "S2_FOOBAR"}},
"lc2": {"process_id": "load_collection", "arguments": {"id": "SENTINEL1_GRD"}},
"resample": {
"process_id": "resample_cube_spatial",
"arguments": {"data": {"from_node": "lc1"}, "target": {"from_node": "lc2"}},
"result": True,
},
}
)
cube1 = dummy_backend.get_collection("S2_FOOBAR")
cube2 = dummy_backend.get_collection("SENTINEL1_GRD")
assert cube1.resample_cube_spatial.call_count == 1
assert cube1.resample_cube_spatial.call_args.kwargs == {"target": cube2, "method": "near"}


def test_execute_resample_cube_spatial_custom(api):
api.check_result(
{
"lc1": {"process_id": "load_collection", "arguments": {"id": "S2_FOOBAR"}},
"lc2": {"process_id": "load_collection", "arguments": {"id": "SENTINEL1_GRD"}},
"resample": {
"process_id": "resample_cube_spatial",
"arguments": {"data": {"from_node": "lc1"}, "target": {"from_node": "lc2"}, "method": "lanczos"},
"result": True,
},
}
)
cube1 = dummy_backend.get_collection("S2_FOOBAR")
cube2 = dummy_backend.get_collection("SENTINEL1_GRD")
assert cube1.resample_cube_spatial.call_count == 1
assert cube1.resample_cube_spatial.call_args.kwargs == {"target": cube2, "method": "lanczos"}


def test_execute_resample_cube_spatial_invalid(api):
res = api.result(
{
"lc1": {"process_id": "load_collection", "arguments": {"id": "S2_FOOBAR"}},
"lc2": {"process_id": "load_collection", "arguments": {"id": "SENTINEL1_GRD"}},
"resample": {
"process_id": "resample_cube_spatial",
"arguments": {"data": {"from_node": "lc1"}, "target": {"from_node": "lc2"}, "method": "du chef"},
"result": True,
},
}
)
res.assert_error(
status_code=400,
error_code="ProcessParameterInvalid",
message=re.compile(r"Invalid enum value 'du chef'\. Expected one of.*cubic.*near"),
)


def test_execute_resample_and_merge_cubes(api):
api.check_result("resample_and_merge_cubes.json")
dummy = dummy_backend.get_collection("S2_FAPAR_CLOUDCOVER")
Expand All @@ -522,6 +647,7 @@ def test_execute_resample_and_merge_cubes(api):
assert last_load_collection_call.target_resolution == [10, 10]
assert dummy.merge_cubes.call_count == 1
assert dummy.resample_cube_spatial.call_count == 1
assert dummy.resample_cube_spatial.call_args.kwargs["method"] == "cubic"
args, kwargs = dummy.merge_cubes.call_args
assert args[1:] == ('or',)

Expand Down

0 comments on commit b5f0388

Please sign in to comment.