openeo-gfmap 0.1.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.
Files changed (40) hide show
  1. openeo_gfmap/__init__.py +23 -0
  2. openeo_gfmap/backend.py +122 -0
  3. openeo_gfmap/features/__init__.py +17 -0
  4. openeo_gfmap/features/feature_extractor.py +389 -0
  5. openeo_gfmap/fetching/__init__.py +21 -0
  6. openeo_gfmap/fetching/commons.py +213 -0
  7. openeo_gfmap/fetching/fetching.py +98 -0
  8. openeo_gfmap/fetching/generic.py +165 -0
  9. openeo_gfmap/fetching/meteo.py +126 -0
  10. openeo_gfmap/fetching/s1.py +195 -0
  11. openeo_gfmap/fetching/s2.py +236 -0
  12. openeo_gfmap/inference/__init__.py +3 -0
  13. openeo_gfmap/inference/model_inference.py +347 -0
  14. openeo_gfmap/manager/__init__.py +31 -0
  15. openeo_gfmap/manager/job_manager.py +469 -0
  16. openeo_gfmap/manager/job_splitters.py +144 -0
  17. openeo_gfmap/metadata.py +24 -0
  18. openeo_gfmap/preprocessing/__init__.py +22 -0
  19. openeo_gfmap/preprocessing/cloudmasking.py +268 -0
  20. openeo_gfmap/preprocessing/compositing.py +74 -0
  21. openeo_gfmap/preprocessing/interpolation.py +12 -0
  22. openeo_gfmap/preprocessing/sar.py +64 -0
  23. openeo_gfmap/preprocessing/scaling.py +65 -0
  24. openeo_gfmap/preprocessing/udf_cldmask.py +36 -0
  25. openeo_gfmap/preprocessing/udf_rank.py +37 -0
  26. openeo_gfmap/preprocessing/udf_score.py +103 -0
  27. openeo_gfmap/spatial.py +53 -0
  28. openeo_gfmap/stac/__init__.py +2 -0
  29. openeo_gfmap/stac/constants.py +51 -0
  30. openeo_gfmap/temporal.py +22 -0
  31. openeo_gfmap/utils/__init__.py +23 -0
  32. openeo_gfmap/utils/build_df.py +48 -0
  33. openeo_gfmap/utils/catalogue.py +248 -0
  34. openeo_gfmap/utils/intervals.py +64 -0
  35. openeo_gfmap/utils/netcdf.py +25 -0
  36. openeo_gfmap/utils/tile_processing.py +64 -0
  37. openeo_gfmap-0.1.0.dist-info/METADATA +57 -0
  38. openeo_gfmap-0.1.0.dist-info/RECORD +40 -0
  39. openeo_gfmap-0.1.0.dist-info/WHEEL +4 -0
  40. openeo_gfmap-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,23 @@
1
+ """OpenEO General Framework for Mapping.
2
+
3
+ Simplify the development of mapping applications through Remote Sensing data
4
+ by leveraging the power of OpenEO (http://openeo.org/).
5
+
6
+ More information available in the README.md file.
7
+ """
8
+
9
+ from .backend import Backend, BackendContext
10
+ from .fetching import FetchType
11
+ from .metadata import FakeMetadata
12
+ from .spatial import BoundingBoxExtent, SpatialContext
13
+ from .temporal import TemporalContext
14
+
15
+ __all__ = [
16
+ "Backend",
17
+ "BackendContext",
18
+ "SpatialContext",
19
+ "BoundingBoxExtent",
20
+ "TemporalContext",
21
+ "FakeMetadata",
22
+ "FetchType",
23
+ ]
@@ -0,0 +1,122 @@
1
+ """Backend Contct.
2
+
3
+ Defines on which backend the pipeline is being currently used.
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import Callable, Dict, Optional
11
+
12
+ import openeo
13
+
14
+ _log = logging.getLogger(__name__)
15
+
16
+
17
+ class Backend(Enum):
18
+ """Enumerating the backends supported by the Mapping Framework."""
19
+
20
+ TERRASCOPE = "terrascope"
21
+ EODC = "eodc" # Dask implementation. Do not test on this yet.
22
+ CDSE = "cdse" # Terrascope implementation (pyspark) #URL: openeo.dataspace.copernicus.eu (need to register)
23
+ CDSE_STAGING = "cdse-staging"
24
+ LOCAL = "local" # Based on the same components of EODc
25
+ FED = "fed" # Federation backend
26
+
27
+
28
+ @dataclass
29
+ class BackendContext:
30
+ """Backend context and information.
31
+
32
+ Containing backend related information useful for the framework to
33
+ adapt the process graph.
34
+ """
35
+
36
+ backend: Backend
37
+
38
+
39
+ def _create_connection(
40
+ url: str, *, env_var_suffix: str, connect_kwargs: Optional[dict] = None
41
+ ):
42
+ """
43
+ Generic helper to create an openEO connection
44
+ with support for multiple client credential configurations from environment variables
45
+ """
46
+ connection = openeo.connect(url, **(connect_kwargs or {}))
47
+
48
+ if (
49
+ os.environ.get("OPENEO_AUTH_METHOD") == "client_credentials"
50
+ and f"OPENEO_AUTH_CLIENT_ID_{env_var_suffix}" in os.environ
51
+ ):
52
+ # Support for multiple client credentials configs from env vars
53
+ client_id = os.environ[f"OPENEO_AUTH_CLIENT_ID_{env_var_suffix}"]
54
+ client_secret = os.environ[f"OPENEO_AUTH_CLIENT_SECRET_{env_var_suffix}"]
55
+ provider_id = os.environ.get(f"OPENEO_AUTH_PROVIDER_ID_{env_var_suffix}")
56
+ _log.info(
57
+ f"Doing client credentials from env var with {env_var_suffix=} {provider_id} {client_id=} {len(client_secret)=} "
58
+ )
59
+
60
+ connection.authenticate_oidc_client_credentials(
61
+ client_id=client_id, client_secret=client_secret, provider_id=provider_id
62
+ )
63
+ else:
64
+ # Standard authenticate_oidc procedure: refresh token, device code or default env var handling
65
+ # See https://open-eo.github.io/openeo-python-client/auth.html#oidc-authentication-dynamic-method-selection
66
+
67
+ # Use a shorter max poll time by default to alleviate the default impression that the test seem to hang
68
+ # because of the OIDC device code poll loop.
69
+ max_poll_time = int(
70
+ os.environ.get("OPENEO_OIDC_DEVICE_CODE_MAX_POLL_TIME") or 30
71
+ )
72
+ connection.authenticate_oidc(max_poll_time=max_poll_time)
73
+ return connection
74
+
75
+
76
+ def vito_connection() -> openeo.Connection:
77
+ """Performs a connection to the VITO backend using the oidc authentication."""
78
+ return _create_connection(
79
+ url="openeo.vito.be",
80
+ env_var_suffix="VITO",
81
+ )
82
+
83
+
84
+ def cdse_connection() -> openeo.Connection:
85
+ """Performs a connection to the CDSE backend using oidc authentication."""
86
+ return _create_connection(
87
+ url="openeo.dataspace.copernicus.eu",
88
+ env_var_suffix="CDSE",
89
+ )
90
+
91
+
92
+ def cdse_staging_connection() -> openeo.Connection:
93
+ """Performs a connection to the CDSE backend using oidc authentication."""
94
+ return _create_connection(
95
+ url="openeo-staging.dataspace.copernicus.eu",
96
+ env_var_suffix="CDSE_STAGING",
97
+ )
98
+
99
+
100
+ def eodc_connection() -> openeo.Connection:
101
+ """Perfroms a connection to the EODC backend using the oidc authentication."""
102
+ return _create_connection(
103
+ url="https://openeo.eodc.eu/openeo/1.1.0",
104
+ env_var_suffix="EODC",
105
+ )
106
+
107
+
108
+ def fed_connection() -> openeo.Connection:
109
+ """Performs a connection to the OpenEO federated backend using the oidc
110
+ authentication."""
111
+ return _create_connection(
112
+ url="http://openeofed.dataspace.copernicus.eu/",
113
+ env_var_suffix="FED",
114
+ )
115
+
116
+
117
+ BACKEND_CONNECTIONS: Dict[Backend, Callable] = {
118
+ Backend.TERRASCOPE: vito_connection,
119
+ Backend.CDSE: cdse_connection,
120
+ Backend.CDSE_STAGING: cdse_staging_connection,
121
+ Backend.FED: fed_connection,
122
+ }
@@ -0,0 +1,17 @@
1
+ from openeo_gfmap.features.feature_extractor import (
2
+ LAT_HARMONIZED_NAME,
3
+ LON_HARMONIZED_NAME,
4
+ PatchFeatureExtractor,
5
+ PointFeatureExtractor,
6
+ apply_feature_extractor,
7
+ apply_feature_extractor_local,
8
+ )
9
+
10
+ __all__ = [
11
+ "PatchFeatureExtractor",
12
+ "PointFeatureExtractor",
13
+ "LAT_HARMONIZED_NAME",
14
+ "LON_HARMONIZED_NAME",
15
+ "apply_feature_extractor",
16
+ "apply_feature_extractor_local",
17
+ ]
@@ -0,0 +1,389 @@
1
+ """Feature extractor functionalities. Such as a base class to assist the
2
+ implementation of feature extractors of a UDF.
3
+ """
4
+ import functools
5
+ import inspect
6
+ import logging
7
+ import re
8
+ import shutil
9
+ import urllib.request
10
+ from abc import ABC, abstractmethod
11
+ from pathlib import Path
12
+
13
+ import numpy as np
14
+ import openeo
15
+ import xarray as xr
16
+ from openeo.udf import XarrayDataCube
17
+ from openeo.udf.udf_data import UdfData
18
+ from pyproj import Transformer
19
+ from pyproj.crs import CRS
20
+
21
+ LAT_HARMONIZED_NAME = "GEO-LAT"
22
+ LON_HARMONIZED_NAME = "GEO-LON"
23
+ EPSG_HARMONIZED_NAME = "GEO-EPSG"
24
+
25
+
26
+ class FeatureExtractor(ABC):
27
+ """Base class for all feature extractor UDFs. It provides some common
28
+ methods and attributes to be used by other feature extractor.
29
+
30
+ The inherited classes are supposed to take care of VectorDataCubes for
31
+ point based extraction or dense Cubes for tile/polygon based extraction.
32
+ """
33
+
34
+ def __init__(self) -> None:
35
+ logging.basicConfig(level=logging.INFO)
36
+ self.logger = logging.getLogger(self.__class__.__name__)
37
+
38
+ @classmethod
39
+ @functools.lru_cache(maxsize=6)
40
+ def extract_dependencies(cls, base_url: str, dependency_name: str) -> str:
41
+ """Extract the dependencies from the given URL. Unpacking a zip
42
+ file in the current working directory and return the path to the
43
+ unpacked directory.
44
+
45
+ Parameters:
46
+ - base_url: The base public URL where the dependencies are stored.
47
+ - dependency_name: The name of the dependency file to download. This
48
+ parameter is added to `base_url` as a download path to the .zip
49
+ archive
50
+ Returns:
51
+ - The absolute path to the extracted dependencies directory, to be added
52
+ to the python path with the `sys.path.append` method.
53
+ """
54
+
55
+ # Generate absolute path for the dependencies folder
56
+ dependencies_dir = Path.cwd() / "dependencies"
57
+
58
+ # Create the directory if it doesn't exist
59
+ dependencies_dir.mkdir(exist_ok=True, parents=True)
60
+
61
+ # Download and extract the model file
62
+ modelfile_url = f"{base_url}/{dependency_name}"
63
+ modelfile, _ = urllib.request.urlretrieve(
64
+ modelfile_url, filename=dependencies_dir / Path(modelfile_url).name
65
+ )
66
+ shutil.unpack_archive(modelfile, extract_dir=dependencies_dir)
67
+
68
+ # Add the model directory to system path if it's not already there
69
+ abs_path = str(
70
+ dependencies_dir / Path(modelfile_url).name.split(".zip")[0]
71
+ ) # NOQA
72
+
73
+ return abs_path
74
+
75
+ def _common_preparations(
76
+ self, inarr: xr.DataArray, parameters: dict
77
+ ) -> xr.DataArray:
78
+ """Common preparations to be executed before the feature extractor is
79
+ executed. This method should be called by the `_execute` method of the
80
+ feature extractor.
81
+ """
82
+ self._epsg = parameters.pop(EPSG_HARMONIZED_NAME)
83
+ self._parameters = parameters
84
+ return inarr
85
+
86
+ @property
87
+ def epsg(self) -> int:
88
+ """Returns the EPSG code of the datacube."""
89
+ return self._epsg
90
+
91
+ def dependencies(self) -> list:
92
+ """Returns the additional dependencies such as wheels or zip files.
93
+ Dependencies should be returned as a list of string, which will set-up at the top of the
94
+ generated UDF. More information can be found at:
95
+ https://open-eo.github.io/openeo-python-client/udf.html#standard-for-declaring-python-udf-dependencies
96
+ """
97
+ self.logger.warning(
98
+ "No additional dependencies are defined. If you wish to add "
99
+ "dependencies to your feature extractor, override the "
100
+ "`dependencies` method in your class."
101
+ )
102
+ return []
103
+
104
+ @abstractmethod
105
+ def output_labels(self) -> list:
106
+ """Returns a list of output labels to be assigned on the output bands,
107
+ needs to be overriden by the user."""
108
+ raise NotImplementedError(
109
+ "FeatureExtractor is a base abstract class, please implement the "
110
+ "output_labels property."
111
+ )
112
+
113
+ @abstractmethod
114
+ def _execute(self, cube: XarrayDataCube, parameters: dict) -> XarrayDataCube:
115
+ raise NotImplementedError(
116
+ "FeatureExtractor is a base abstract class, please implement the "
117
+ "_execute method."
118
+ )
119
+
120
+
121
+ class PatchFeatureExtractor(FeatureExtractor):
122
+ """Base class for all the tile/polygon based feature extractors. An user
123
+ implementing a feature extractor should take care of
124
+ """
125
+
126
+ def get_latlons(self, inarr: xr.DataArray) -> xr.DataArray:
127
+ """Returns the latitude and longitude coordinates of the given array in
128
+ a dataarray. Returns a dataarray with the same width/height of the input
129
+ array, but with two bands, one for latitude and one for longitude. The
130
+ metadata coordinates of the output array are the same as the input
131
+ array, as the array wasn't reprojected but instead new features were
132
+ computed.
133
+
134
+ The latitude and longitude band names are standardized to the names
135
+ `LAT_HARMONIZED_NAME` and `LON_HARMONIZED_NAME` respectively.
136
+ """
137
+
138
+ lon = inarr.coords["x"]
139
+ lat = inarr.coords["y"]
140
+ lon, lat = np.meshgrid(lon, lat)
141
+
142
+ if self.epsg is None:
143
+ raise Exception(
144
+ "EPSG code was not defined, cannot extract lat/lon array "
145
+ "as the CRS is unknown."
146
+ )
147
+
148
+ # If the coordiantes are not in EPSG:4326, we need to reproject them
149
+ if self.epsg != 4326:
150
+ # Initializes a pyproj reprojection object
151
+ transformer = Transformer.from_crs(
152
+ crs_from=CRS.from_epsg(self.epsg),
153
+ crs_to=CRS.from_epsg(4326),
154
+ always_xy=True,
155
+ )
156
+ lon, lat = transformer.transform(xx=lon, yy=lat)
157
+
158
+ # Create a two channel numpy array of the lat and lons together by stacking
159
+ latlon = np.stack([lat, lon])
160
+
161
+ # Repack in a dataarray
162
+ return xr.DataArray(
163
+ latlon,
164
+ dims=["bands", "y", "x"],
165
+ coords={
166
+ "bands": [LAT_HARMONIZED_NAME, LON_HARMONIZED_NAME],
167
+ "y": inarr.coords["y"],
168
+ "x": inarr.coords["x"],
169
+ },
170
+ )
171
+
172
+ def _rescale_s1_backscatter(self, arr: xr.DataArray) -> xr.DataArray:
173
+ """Rescales the input array from uint16 to float32 decibel values.
174
+ The input array should be in uint16 format, as this optimizes memory usage in Open-EO
175
+ processes. This function is called automatically on the bands of the input array, except
176
+ if the parameter `rescale_s1` is set to False.
177
+ """
178
+ s1_bands = ["S1-SIGMA0-VV", "S1-SIGMA0-VH", "S1-SIGMA0-HV", "S1-SIGMA0-HH"]
179
+ s1_bands_to_select = list(set(arr.bands.values) & set(s1_bands))
180
+
181
+ if len(s1_bands_to_select) == 0:
182
+ return arr
183
+
184
+ data_to_rescale = arr.sel(bands=s1_bands_to_select).astype(np.float32).data
185
+
186
+ # Assert that the values are set between 1 and 65535
187
+ if data_to_rescale.min().item() < 1 or data_to_rescale.max().item() > 65535:
188
+ raise ValueError(
189
+ "The input array should be in uint16 format, with values between 1 and 65535. "
190
+ "This restriction assures that the data was processed according to the S1 fetcher "
191
+ "preprocessor. The user can disable this scaling manually by setting the "
192
+ "`rescale_s1` parameter to False in the feature extractor."
193
+ )
194
+
195
+ # Converting back to power values
196
+ data_to_rescale = 20.0 * np.log10(data_to_rescale) - 83.0
197
+ data_to_rescale = np.power(10, data_to_rescale / 10.0)
198
+ data_to_rescale[~np.isfinite(data_to_rescale)] = np.nan
199
+
200
+ # Converting power values to decibels
201
+ data_to_rescale = 10.0 * np.log10(data_to_rescale)
202
+
203
+ # Change the bands within the array
204
+ arr.loc[dict(bands=s1_bands_to_select)] = data_to_rescale
205
+ return arr
206
+
207
+ def _execute(self, cube: XarrayDataCube, parameters: dict) -> XarrayDataCube:
208
+ arr = cube.get_array().transpose("bands", "t", "y", "x")
209
+ arr = self._common_preparations(arr, parameters)
210
+ if self._parameters.get("rescale_s1", True):
211
+ arr = self._rescale_s1_backscatter(arr)
212
+
213
+ arr = self.execute(arr).transpose("bands", "y", "x")
214
+ return XarrayDataCube(arr)
215
+
216
+ @abstractmethod
217
+ def execute(self, inarr: xr.DataArray) -> xr.DataArray:
218
+ pass
219
+
220
+
221
+ class PointFeatureExtractor(FeatureExtractor):
222
+ def __init__(self):
223
+ raise NotImplementedError(
224
+ "Point based feature extraction on Vector Cubes is not supported yet."
225
+ )
226
+
227
+ def _execute(self, cube: XarrayDataCube, parameters: dict) -> XarrayDataCube:
228
+ arr = cube.get_array().transpose("bands", "t")
229
+
230
+ arr = self._common_preparations(arr, parameters)
231
+
232
+ outarr = self.execute(cube.to_array()).transpose("bands", "t")
233
+ return XarrayDataCube(outarr)
234
+
235
+ @abstractmethod
236
+ def execute(self, inarr: xr.DataArray) -> xr.DataArray:
237
+ pass
238
+
239
+
240
+ def apply_udf_data(udf_data: UdfData) -> XarrayDataCube:
241
+ feature_extractor_class = "<feature_extractor_class>"
242
+
243
+ # User-defined, feature extractor class initialized here
244
+ feature_extractor = feature_extractor_class()
245
+
246
+ is_pixel_based = issubclass(feature_extractor_class, PointFeatureExtractor)
247
+
248
+ if not is_pixel_based:
249
+ assert (
250
+ len(udf_data.datacube_list) == 1
251
+ ), "OpenEO GFMAP Feature extractor pipeline only supports single input cubes for the tile."
252
+
253
+ cube = udf_data.datacube_list[0]
254
+ parameters = udf_data.user_context
255
+
256
+ proj = udf_data.proj
257
+ if proj is not None:
258
+ proj = proj["EPSG"]
259
+
260
+ parameters[EPSG_HARMONIZED_NAME] = proj
261
+
262
+ cube = feature_extractor._execute(cube, parameters=parameters)
263
+
264
+ udf_data.datacube_list = [cube]
265
+
266
+ return udf_data
267
+
268
+
269
+ def _get_imports() -> str:
270
+ with open(__file__, "r", encoding="UTF-8") as f:
271
+ script_source = f.read()
272
+
273
+ lines = script_source.split("\n")
274
+
275
+ imports = []
276
+ static_globals = []
277
+
278
+ for line in lines:
279
+ if line.strip().startswith(("import ", "from ")):
280
+ imports.append(line)
281
+ # All the global variables with the style
282
+ # UPPER_CASE_GLOBAL_VARIABLE = "constant"
283
+ elif re.match("^[A-Z_0-9]+\s*=.*$", line):
284
+ static_globals.append(line)
285
+
286
+ return "\n".join(imports) + "\n\n" + "\n".join(static_globals)
287
+
288
+
289
+ def _get_apply_udf_data(feature_extractor: FeatureExtractor) -> str:
290
+ source_lines = inspect.getsource(apply_udf_data)
291
+ source = "".join(source_lines)
292
+ # replace in the source function the `feature_extractor_class`
293
+ return source.replace('"<feature_extractor_class>"', feature_extractor.__name__)
294
+
295
+
296
+ def _generate_udf_code(
297
+ feature_extractor_class: FeatureExtractor, dependencies: list
298
+ ) -> openeo.UDF:
299
+ """Generates the udf code by packing imports of this file, the necessary
300
+ superclass and subclasses as well as the user defined feature extractor
301
+ class and the apply_datacube function.
302
+ """
303
+
304
+ # UDF code that will be built here
305
+ udf_code = ""
306
+
307
+ assert issubclass(
308
+ feature_extractor_class, FeatureExtractor
309
+ ), "The feature extractor class must be a subclass of FeatureExtractor."
310
+
311
+ dependencies_code = ""
312
+ dependencies_code += "# /// script\n"
313
+ dependencies_code += "# dependencies = [\n"
314
+ for dep in dependencies:
315
+ dependencies_code += f'# "{dep}",\n'
316
+ dependencies_code += "# ]\n"
317
+ dependencies_code += "# ///\n"
318
+
319
+ udf_code += dependencies_code + "\n"
320
+ udf_code += _get_imports() + "\n\n"
321
+ udf_code += f"{inspect.getsource(FeatureExtractor)}\n\n"
322
+ udf_code += f"{inspect.getsource(PatchFeatureExtractor)}\n\n"
323
+ udf_code += f"{inspect.getsource(PointFeatureExtractor)}\n\n"
324
+ udf_code += f"{inspect.getsource(feature_extractor_class)}\n\n"
325
+ udf_code += _get_apply_udf_data(feature_extractor_class)
326
+ return udf_code
327
+
328
+
329
+ def apply_feature_extractor(
330
+ feature_extractor_class: FeatureExtractor,
331
+ cube: openeo.rest.datacube.DataCube,
332
+ parameters: dict,
333
+ size: list,
334
+ overlap: list = [],
335
+ ) -> openeo.rest.datacube.DataCube:
336
+ """Applies an user-defined feature extractor on the cube by using the
337
+ `openeo.Cube.apply_neighborhood` method. The defined class as well as the
338
+ required subclasses will be packed into a generated UDF file that will be
339
+ executed.
340
+
341
+ Optimization can be achieved by passing integer values for the cube. By
342
+ default, the feature extractor expects to receive S1 and S2 data stored in
343
+ uint16 with the harmonized naming as implemented in the fetching module.
344
+ """
345
+ feature_extractor = feature_extractor_class()
346
+ feature_extractor._parameters = parameters
347
+ output_labels = feature_extractor.output_labels()
348
+ dependencies = feature_extractor.dependencies()
349
+
350
+ udf_code = _generate_udf_code(feature_extractor_class, dependencies)
351
+
352
+ udf = openeo.UDF(code=udf_code, context=parameters)
353
+
354
+ cube = cube.apply_neighborhood(process=udf, size=size, overlap=overlap)
355
+ return cube.rename_labels(dimension="bands", target=output_labels)
356
+
357
+
358
+ def apply_feature_extractor_local(
359
+ feature_extractor_class: FeatureExtractor, cube: xr.DataArray, parameters: dict
360
+ ) -> xr.DataArray:
361
+ """Applies and user-defined feature extractor, but locally. The
362
+ parameters are the same as in the `apply_feature_extractor` function,
363
+ excepts for the cube parameter which expects a `xarray.DataArray` instead of
364
+ a `openeo.rest.datacube.DataCube` object.
365
+ """
366
+ # Trying to get the local EPSG code
367
+ if EPSG_HARMONIZED_NAME not in parameters:
368
+ raise ValueError(
369
+ f"Please specify an EPSG code in the parameters with key: {EPSG_HARMONIZED_NAME} when "
370
+ f"running a Feature Extractor locally."
371
+ )
372
+
373
+ feature_extractor = feature_extractor_class()
374
+ output_labels = feature_extractor.output_labels()
375
+ dependencies = feature_extractor.dependencies()
376
+
377
+ if len(dependencies) > 0:
378
+ feature_extractor.logger.warning(
379
+ "Running UDFs locally with pip dependencies is not supported yet, "
380
+ "dependencies will not be installed."
381
+ )
382
+
383
+ cube = XarrayDataCube(cube)
384
+
385
+ return (
386
+ feature_extractor._execute(cube, parameters)
387
+ .get_array()
388
+ .assign_coords({"bands": output_labels})
389
+ )
@@ -0,0 +1,21 @@
1
+ """Extraction sub-module.
2
+
3
+ Logic behind the extraction of training or inference data. Different backends
4
+ are supported in order to obtain a very similar result at the end of this
5
+ component.
6
+ """
7
+
8
+ import logging
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
+ _log = logging.getLogger(__name__)
15
+
16
+ __all__ = [
17
+ "build_sentinel2_l2a_extractor",
18
+ "CollectionFetcher",
19
+ "FetchType",
20
+ "build_sentinel1_grd_extractor",
21
+ ]