openeo-gfmap 0.1.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openeo_gfmap/features/feature_extractor.py +9 -0
- openeo_gfmap/fetching/__init__.py +16 -4
- openeo_gfmap/fetching/commons.py +1 -0
- openeo_gfmap/fetching/generic.py +81 -73
- openeo_gfmap/fetching/s1.py +1 -3
- openeo_gfmap/fetching/s2.py +1 -0
- openeo_gfmap/inference/model_inference.py +5 -2
- openeo_gfmap/manager/job_manager.py +271 -84
- openeo_gfmap/manager/job_splitters.py +169 -21
- openeo_gfmap/preprocessing/sar.py +12 -33
- openeo_gfmap/stac/constants.py +1 -1
- openeo_gfmap/utils/__init__.py +16 -0
- openeo_gfmap/utils/catalogue.py +172 -35
- openeo_gfmap/utils/split_stac.py +125 -0
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/METADATA +5 -4
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/RECORD +18 -18
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/WHEEL +1 -1
- openeo_gfmap/fetching/meteo.py +0 -126
- {openeo_gfmap-0.1.0.dist-info → openeo_gfmap-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Feature extractor functionalities. Such as a base class to assist the
|
2
2
|
implementation of feature extractors of a UDF.
|
3
3
|
"""
|
4
|
+
|
4
5
|
import functools
|
5
6
|
import inspect
|
6
7
|
import logging
|
@@ -32,6 +33,8 @@ class FeatureExtractor(ABC):
|
|
32
33
|
"""
|
33
34
|
|
34
35
|
def __init__(self) -> None:
|
36
|
+
self._epsg = None
|
37
|
+
|
35
38
|
logging.basicConfig(level=logging.INFO)
|
36
39
|
self.logger = logging.getLogger(self.__class__.__name__)
|
37
40
|
|
@@ -88,6 +91,10 @@ class FeatureExtractor(ABC):
|
|
88
91
|
"""Returns the EPSG code of the datacube."""
|
89
92
|
return self._epsg
|
90
93
|
|
94
|
+
@epsg.setter
|
95
|
+
def epsg(self, value: int):
|
96
|
+
self._epsg = value
|
97
|
+
|
91
98
|
def dependencies(self) -> list:
|
92
99
|
"""Returns the additional dependencies such as wheels or zip files.
|
93
100
|
Dependencies should be returned as a list of string, which will set-up at the top of the
|
@@ -204,6 +211,7 @@ class PatchFeatureExtractor(FeatureExtractor):
|
|
204
211
|
arr.loc[dict(bands=s1_bands_to_select)] = data_to_rescale
|
205
212
|
return arr
|
206
213
|
|
214
|
+
# TODO to remove the fixed transpose as it contributes to unclear code.
|
207
215
|
def _execute(self, cube: XarrayDataCube, parameters: dict) -> XarrayDataCube:
|
208
216
|
arr = cube.get_array().transpose("bands", "t", "y", "x")
|
209
217
|
arr = self._common_preparations(arr, parameters)
|
@@ -371,6 +379,7 @@ def apply_feature_extractor_local(
|
|
371
379
|
)
|
372
380
|
|
373
381
|
feature_extractor = feature_extractor_class()
|
382
|
+
feature_extractor._parameters = parameters
|
374
383
|
output_labels = feature_extractor.output_labels()
|
375
384
|
dependencies = feature_extractor.dependencies()
|
376
385
|
|
@@ -7,15 +7,27 @@ component.
|
|
7
7
|
|
8
8
|
import logging
|
9
9
|
|
10
|
-
from .fetching import CollectionFetcher, FetchType
|
11
|
-
from .s1 import build_sentinel1_grd_extractor
|
12
|
-
from .s2 import build_sentinel2_l2a_extractor
|
13
|
-
|
14
10
|
_log = logging.getLogger(__name__)
|
11
|
+
_log.setLevel(logging.INFO)
|
12
|
+
|
13
|
+
ch = logging.StreamHandler()
|
14
|
+
ch.setLevel(logging.INFO)
|
15
|
+
|
16
|
+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
17
|
+
ch.setFormatter(formatter)
|
18
|
+
|
19
|
+
_log.addHandler(ch)
|
15
20
|
|
16
21
|
__all__ = [
|
17
22
|
"build_sentinel2_l2a_extractor",
|
18
23
|
"CollectionFetcher",
|
19
24
|
"FetchType",
|
20
25
|
"build_sentinel1_grd_extractor",
|
26
|
+
"build_generic_extractor",
|
27
|
+
"build_generic_extractor_stac",
|
21
28
|
]
|
29
|
+
|
30
|
+
from .fetching import CollectionFetcher, FetchType # noqa: E402
|
31
|
+
from .generic import build_generic_extractor, build_generic_extractor_stac # noqa: E402
|
32
|
+
from .s1 import build_sentinel1_grd_extractor # noqa: E402
|
33
|
+
from .s2 import build_sentinel2_l2a_extractor # noqa: E402
|
openeo_gfmap/fetching/commons.py
CHANGED
openeo_gfmap/fetching/generic.py
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
""" Generic extraction of features, supporting VITO backend.
|
2
2
|
"""
|
3
3
|
|
4
|
-
from
|
5
|
-
from typing import Callable
|
4
|
+
from typing import Callable, Optional
|
6
5
|
|
7
6
|
import openeo
|
8
|
-
from
|
7
|
+
from openeo.rest import OpenEoApiError
|
9
8
|
|
10
9
|
from openeo_gfmap.backend import Backend, BackendContext
|
11
10
|
from openeo_gfmap.fetching import CollectionFetcher, FetchType, _log
|
@@ -29,15 +28,32 @@ BASE_WEATHER_MAPPING = {
|
|
29
28
|
"vapour-pressure": "AGERA5-VAPOUR",
|
30
29
|
"wind-speed": "AGERA5-WIND",
|
31
30
|
}
|
31
|
+
AGERA5_STAC_MAPPING = {
|
32
|
+
"dewpoint_temperature_mean": "AGERA5-DEWTEMP",
|
33
|
+
"total_precipitation": "AGERA5-PRECIP",
|
34
|
+
"solar_radiation_flux": "AGERA5-SOLRAD",
|
35
|
+
"2m_temperature_max": "AGERA5-TMAX",
|
36
|
+
"2m_temperature_mean": "AGERA5-TMEAN",
|
37
|
+
"2m_temperature_min": "AGERA5-TMIN",
|
38
|
+
"vapour_pressure": "AGERA5-VAPOUR",
|
39
|
+
"wind_speed": "AGERA5-WIND",
|
40
|
+
}
|
41
|
+
KNOWN_UNTEMPORAL_COLLECTIONS = ["COPERNICUS_30"]
|
42
|
+
|
43
|
+
AGERA5_TERRASCOPE_STAC = "https://stac.openeo.vito.be/collections/agera5_daily"
|
44
|
+
|
32
45
|
|
46
|
+
def _get_generic_fetcher(
|
47
|
+
collection_name: str, fetch_type: FetchType, backend: Backend, is_stac: bool
|
48
|
+
) -> Callable:
|
49
|
+
band_mapping: Optional[dict] = None
|
33
50
|
|
34
|
-
def _get_generic_fetcher(collection_name: str, fetch_type: FetchType) -> Callable:
|
35
51
|
if collection_name == "COPERNICUS_30":
|
36
|
-
|
52
|
+
band_mapping = BASE_DEM_MAPPING
|
37
53
|
elif collection_name == "AGERA5":
|
38
|
-
|
39
|
-
|
40
|
-
|
54
|
+
band_mapping = BASE_WEATHER_MAPPING
|
55
|
+
elif is_stac and (AGERA5_TERRASCOPE_STAC in collection_name):
|
56
|
+
band_mapping = AGERA5_STAC_MAPPING
|
41
57
|
|
42
58
|
def generic_default_fetcher(
|
43
59
|
connection: openeo.Connection,
|
@@ -46,43 +62,58 @@ def _get_generic_fetcher(collection_name: str, fetch_type: FetchType) -> Callabl
|
|
46
62
|
bands: list,
|
47
63
|
**params,
|
48
64
|
) -> openeo.DataCube:
|
49
|
-
|
65
|
+
if band_mapping is not None:
|
66
|
+
bands = convert_band_names(bands, band_mapping)
|
50
67
|
|
51
|
-
if (collection_name
|
68
|
+
if (collection_name in KNOWN_UNTEMPORAL_COLLECTIONS) and (
|
69
|
+
temporal_extent is not None
|
70
|
+
):
|
52
71
|
_log.warning(
|
53
|
-
"
|
72
|
+
"Ignoring the temporal extent provided by the user as the collection %s is known to be untemporal.",
|
73
|
+
collection_name,
|
54
74
|
)
|
55
75
|
temporal_extent = None
|
56
76
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
77
|
+
try:
|
78
|
+
cube = _load_collection(
|
79
|
+
connection,
|
80
|
+
bands,
|
81
|
+
collection_name,
|
82
|
+
spatial_extent,
|
83
|
+
temporal_extent,
|
84
|
+
fetch_type,
|
85
|
+
is_stac=is_stac,
|
86
|
+
**params,
|
87
|
+
)
|
88
|
+
except OpenEoApiError as e:
|
89
|
+
if "CollectionNotFound" in str(e):
|
90
|
+
raise ValueError(
|
91
|
+
f"Collection {collection_name} not found in the selected backend {backend.value}."
|
92
|
+
) from e
|
93
|
+
raise e
|
66
94
|
|
67
|
-
# Apply if the collection is a GeoJSON Feature collection
|
68
|
-
if isinstance(spatial_extent, GeoJSON):
|
69
|
-
|
95
|
+
# # Apply if the collection is a GeoJSON Feature collection
|
96
|
+
# if isinstance(spatial_extent, GeoJSON):
|
97
|
+
# cube = cube.filter_spatial(spatial_extent)
|
70
98
|
|
71
99
|
return cube
|
72
100
|
|
73
101
|
return generic_default_fetcher
|
74
102
|
|
75
103
|
|
76
|
-
def _get_generic_processor(
|
104
|
+
def _get_generic_processor(
|
105
|
+
collection_name: str, fetch_type: FetchType, is_stac: bool
|
106
|
+
) -> Callable:
|
77
107
|
"""Builds the preprocessing function from the collection name as it stored
|
78
108
|
in the target backend.
|
79
109
|
"""
|
110
|
+
band_mapping: Optional[dict] = None
|
80
111
|
if collection_name == "COPERNICUS_30":
|
81
|
-
|
112
|
+
band_mapping = BASE_DEM_MAPPING
|
82
113
|
elif collection_name == "AGERA5":
|
83
|
-
|
84
|
-
|
85
|
-
|
114
|
+
band_mapping = BASE_WEATHER_MAPPING
|
115
|
+
elif is_stac and (AGERA5_TERRASCOPE_STAC in collection_name):
|
116
|
+
band_mapping = AGERA5_STAC_MAPPING
|
86
117
|
|
87
118
|
def generic_default_processor(cube: openeo.DataCube, **params):
|
88
119
|
"""Default collection preprocessing method for generic datasets.
|
@@ -100,51 +131,14 @@ def _get_generic_processor(collection_name: str, fetch_type: FetchType) -> Calla
|
|
100
131
|
if collection_name == "COPERNICUS_30":
|
101
132
|
cube = cube.min_time()
|
102
133
|
|
103
|
-
|
134
|
+
if band_mapping is not None:
|
135
|
+
cube = rename_bands(cube, band_mapping)
|
104
136
|
|
105
137
|
return cube
|
106
138
|
|
107
139
|
return generic_default_processor
|
108
140
|
|
109
141
|
|
110
|
-
OTHER_BACKEND_MAP = {
|
111
|
-
"AGERA5": {
|
112
|
-
Backend.TERRASCOPE: {
|
113
|
-
"fetch": partial(_get_generic_fetcher, collection_name="AGERA5"),
|
114
|
-
"preprocessor": partial(_get_generic_processor, collection_name="AGERA5"),
|
115
|
-
},
|
116
|
-
Backend.CDSE: {
|
117
|
-
"fetch": partial(_get_generic_fetcher, collection_name="AGERA5"),
|
118
|
-
"preprocessor": partial(_get_generic_processor, collection_name="AGERA5"),
|
119
|
-
},
|
120
|
-
Backend.FED: {
|
121
|
-
"fetch": partial(_get_generic_fetcher, collection_name="AGERA5"),
|
122
|
-
"preprocessor": partial(_get_generic_processor, collection_name="AGERA5"),
|
123
|
-
},
|
124
|
-
},
|
125
|
-
"COPERNICUS_30": {
|
126
|
-
Backend.TERRASCOPE: {
|
127
|
-
"fetch": partial(_get_generic_fetcher, collection_name="COPERNICUS_30"),
|
128
|
-
"preprocessor": partial(
|
129
|
-
_get_generic_processor, collection_name="COPERNICUS_30"
|
130
|
-
),
|
131
|
-
},
|
132
|
-
Backend.CDSE: {
|
133
|
-
"fetch": partial(_get_generic_fetcher, collection_name="COPERNICUS_30"),
|
134
|
-
"preprocessor": partial(
|
135
|
-
_get_generic_processor, collection_name="COPERNICUS_30"
|
136
|
-
),
|
137
|
-
},
|
138
|
-
Backend.FED: {
|
139
|
-
"fetch": partial(_get_generic_fetcher, collection_name="COPERNICUS_30"),
|
140
|
-
"preprocessor": partial(
|
141
|
-
_get_generic_processor, collection_name="COPERNICUS_30"
|
142
|
-
),
|
143
|
-
},
|
144
|
-
},
|
145
|
-
}
|
146
|
-
|
147
|
-
|
148
142
|
def build_generic_extractor(
|
149
143
|
backend_context: BackendContext,
|
150
144
|
bands: list,
|
@@ -152,14 +146,28 @@ def build_generic_extractor(
|
|
152
146
|
collection_name: str,
|
153
147
|
**params,
|
154
148
|
) -> CollectionFetcher:
|
155
|
-
"""Creates a generic extractor adapted to the given backend.
|
156
|
-
|
157
|
-
|
149
|
+
"""Creates a generic extractor adapted to the given backend. Provides band mappings for known
|
150
|
+
collections, such as AGERA5 available on Terrascope/FED and Copernicus 30m DEM in all backends.
|
151
|
+
"""
|
152
|
+
fetcher = _get_generic_fetcher(
|
153
|
+
collection_name, fetch_type, backend_context.backend, False
|
158
154
|
)
|
155
|
+
preprocessor = _get_generic_processor(collection_name, fetch_type, False)
|
159
156
|
|
160
|
-
fetcher, preprocessor
|
161
|
-
|
162
|
-
|
157
|
+
return CollectionFetcher(backend_context, bands, fetcher, preprocessor, **params)
|
158
|
+
|
159
|
+
|
160
|
+
def build_generic_extractor_stac(
|
161
|
+
backend_context: BackendContext,
|
162
|
+
bands: list,
|
163
|
+
fetch_type: FetchType,
|
164
|
+
collection_url: str,
|
165
|
+
**params,
|
166
|
+
) -> CollectionFetcher:
|
167
|
+
"""Creates a generic extractor adapted to the given backend. Currently only tested with VITO backend"""
|
168
|
+
fetcher = _get_generic_fetcher(
|
169
|
+
collection_url, fetch_type, backend_context.backend, True
|
163
170
|
)
|
171
|
+
preprocessor = _get_generic_processor(collection_url, fetch_type, True)
|
164
172
|
|
165
173
|
return CollectionFetcher(backend_context, bands, fetcher, preprocessor, **params)
|
openeo_gfmap/fetching/s1.py
CHANGED
@@ -67,8 +67,6 @@ def _get_s1_grd_default_fetcher(
|
|
67
67
|
"""
|
68
68
|
bands = convert_band_names(bands, BASE_SENTINEL1_GRD_MAPPING)
|
69
69
|
|
70
|
-
load_collection_parameters = params.get("load_collection", {})
|
71
|
-
|
72
70
|
cube = _load_collection(
|
73
71
|
connection,
|
74
72
|
bands,
|
@@ -76,7 +74,7 @@ def _get_s1_grd_default_fetcher(
|
|
76
74
|
spatial_extent,
|
77
75
|
temporal_extent,
|
78
76
|
fetch_type,
|
79
|
-
**
|
77
|
+
**params,
|
80
78
|
)
|
81
79
|
|
82
80
|
if fetch_type is not FetchType.POINT and isinstance(spatial_extent, GeoJSON):
|
openeo_gfmap/fetching/s2.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Inference functionalities. Such as a base class to assist the implementation
|
2
2
|
of inference models on an UDF.
|
3
3
|
"""
|
4
|
+
|
4
5
|
import functools
|
5
6
|
import inspect
|
6
7
|
import logging
|
@@ -74,8 +75,9 @@ class ModelInference(ABC):
|
|
74
75
|
|
75
76
|
return abs_path
|
76
77
|
|
78
|
+
@classmethod
|
77
79
|
@functools.lru_cache(maxsize=6)
|
78
|
-
def load_ort_session(
|
80
|
+
def load_ort_session(cls, model_url: str):
|
79
81
|
"""Loads an onnx session from a publicly available URL. The URL must be a direct
|
80
82
|
download link to the ONNX session file.
|
81
83
|
The `lru_cache` decorator avoids loading multiple time the model within the same worker.
|
@@ -180,7 +182,7 @@ class ONNXModelInference(ModelInference):
|
|
180
182
|
raise ValueError("The model_url must be defined in the parameters.")
|
181
183
|
|
182
184
|
# Load the model and the input_name parameters
|
183
|
-
session =
|
185
|
+
session = ModelInference.load_ort_session(self._parameters.get("model_url"))
|
184
186
|
|
185
187
|
input_name = self._parameters.get("input_name")
|
186
188
|
if input_name is None:
|
@@ -329,6 +331,7 @@ def apply_model_inference_local(
|
|
329
331
|
)
|
330
332
|
|
331
333
|
model_inference = model_inference_class()
|
334
|
+
model_inference._parameters = parameters
|
332
335
|
output_labels = model_inference.output_labels()
|
333
336
|
dependencies = model_inference.dependencies()
|
334
337
|
|