Skip to content

Commit b0580c7

Browse files
author
dsamaey
committed
Merge remote-tracking branch 'origin/master' into 747-robustranged-download-support
2 parents 7642ffc + 0cf3bdd commit b0580c7

File tree

7 files changed

+397
-36
lines changed

7 files changed

+397
-36
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
### Changed
13+
14+
### Removed
15+
16+
### Fixed
17+
18+
19+
## [0.40.0] - 2025-04-14
20+
21+
### Added
22+
1223
- `sar_backscatter`: try to retrieve coefficient options from backend ([#693](https://github.com/Open-EO/openeo-python-client/issues/693))
1324
- Improve error message when OIDC provider is unavailable ([#751](https://github.com/Open-EO/openeo-python-client/issues/751))
1425
- Added `on_response_headers` argument to `DataCube.download()` and related to handle (e.g. `print`) the response headers ([#560](https://github.com/Open-EO/openeo-python-client/issues/560))
1526
- Added more robust download for large job result files (if supported by the server)
1627

1728
### Changed
1829

19-
### Removed
30+
- When the bands provided to `Connection.load_stac(..., bands=[...])` do not fully match the bands the client extracted from the STAC metadata, a warning will be triggered, but the provided band names will still be used during the client-side preparation of the process graph. This is a pragmatic approach to bridge the gap between differing interpretations of band detection in STAC. Note that this might produce process graphs that are technically invalid and might not work on other backends or future versions of the backend you currently use. It is recommended to consult with the provider of the STAC metadata and openEO backend on the correct and future-proof band names. ([#752](https://github.com/Open-EO/openeo-python-client/issues/752))
2031

2132
### Fixed
2233

docs/development.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ we will use a concrete version ``0.8.0`` in the examples below.
246246
#. Create release commit:
247247

248248
A. **Drop the pre-release suffix** from the version string in ``openeo/_version.py``
249-
so that it just a "final" semantic versioning string, e.g. ``0.8.0``
249+
so that it is just a "final" semantic versioning string, e.g. ``0.8.0``
250250

251251
B. **Update CHANGELOG.md**: rename the "Unreleased" section title
252252
to contain version and date, e.g.::

openeo/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.40.0a1"
1+
__version__ = "0.41.0a1"

openeo/metadata.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ def rename_labels(self, target, source) -> Dimension:
218218
def rename(self, name) -> Dimension:
219219
return BandDimension(name=name, bands=self.bands)
220220

221+
def contains_band(self, band: Union[int, str]) -> bool:
222+
"""
223+
Check if the given band name or index is present in the dimension.
224+
"""
225+
try:
226+
self.band_index(band)
227+
return True
228+
except ValueError:
229+
return False
230+
221231

222232
class GeometryDimension(Dimension):
223233
# TODO: how to model/store labels of geometry dimension?
@@ -365,22 +375,20 @@ def rename_labels(self, dimension: str, target: list, source: list = None) -> Cu
365375
:return: Updated metadata
366376
"""
367377
self.assert_valid_dimension(dimension)
368-
loc = self.dimension_names().index(dimension)
369-
new_dimensions = self._dimensions.copy()
370-
new_dimensions[loc] = new_dimensions[loc].rename_labels(target, source)
371-
372-
return self._clone_and_update(dimensions=new_dimensions)
378+
return self._clone_and_update(
379+
dimensions=[
380+
d.rename_labels(target=target, source=source) if d.name == dimension else d for d in self._dimensions
381+
]
382+
)
373383

374384
def rename_dimension(self, source: str, target: str) -> CubeMetadata:
375385
"""
376386
Rename source dimension into target, preserving other properties
377387
"""
378388
self.assert_valid_dimension(source)
379-
loc = self.dimension_names().index(source)
380-
new_dimensions = self._dimensions.copy()
381-
new_dimensions[loc] = new_dimensions[loc].rename(target)
382-
383-
return self._clone_and_update(dimensions=new_dimensions)
389+
return self._clone_and_update(
390+
dimensions=[d.rename(name=target) if d.name == source else d for d in self._dimensions]
391+
)
384392

385393
def reduce_dimension(self, dimension_name: str) -> CubeMetadata:
386394
"""Create new CubeMetadata object by collapsing/reducing a dimension."""
@@ -413,6 +421,31 @@ def add_dimension(self, name: str, label: Union[str, float], type: Optional[str]
413421
dim = Dimension(type=type or "other", name=name)
414422
return self._clone_and_update(dimensions=self._dimensions + [dim])
415423

424+
def _ensure_band_dimension(
425+
self, *, name: Optional[str] = None, bands: List[Union[Band, str]], warning: str
426+
) -> CubeMetadata:
427+
"""
428+
Create new CubeMetadata object, ensuring a band dimension with given bands.
429+
This will override any existing band dimension, and is intended for
430+
special cases where pragmatism necessitates to ignore the original metadata.
431+
For example, to overrule badly/incomplete detected band names from STAC metadata.
432+
433+
.. note::
434+
It is required to specify a warning message as this method is only intended
435+
to be used as temporary stop-gap solution for use cases that are possibly not future-proof.
436+
Enforcing a warning should make that clear and avoid that users unknowingly depend on
437+
metadata handling behavior that is not guaranteed to be stable.
438+
"""
439+
_log.warning(warning or "ensure_band_dimension: overriding band dimension metadata with user-defined bands.")
440+
if name is None:
441+
# Preserve original band dimension name if possible
442+
name = self.band_dimension.name if self.has_band_dimension() else "bands"
443+
bands = [b if isinstance(b, Band) else Band(name=b) for b in bands]
444+
band_dimension = BandDimension(name=name, bands=bands)
445+
return self._clone_and_update(
446+
dimensions=[d for d in self._dimensions if not isinstance(d, BandDimension)] + [band_dimension]
447+
)
448+
416449
def drop_dimension(self, name: str = None) -> CubeMetadata:
417450
"""Create new CubeMetadata object without dropped dimension with given name"""
418451
dimension_names = self.dimension_names()
@@ -656,13 +689,13 @@ def is_band_asset(asset: pystac.Asset) -> bool:
656689
raise ValueError(stac_object)
657690

658691
# At least assume there are spatial dimensions
659-
# TODO: are there conditions in which we even should not assume the presence of spatial dimensions?
692+
# TODO #743: are there conditions in which we even should not assume the presence of spatial dimensions?
660693
dimensions = [
661694
SpatialDimension(name="x", extent=[None, None]),
662695
SpatialDimension(name="y", extent=[None, None]),
663696
]
664697

665-
# TODO: conditionally include band dimension when there was actual indication of band metadata?
698+
# TODO #743: conditionally include band dimension when there was actual indication of band metadata?
666699
band_dimension = BandDimension(name="bands", bands=bands)
667700
dimensions.append(band_dimension)
668701

openeo/rest/datacube.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,10 +441,27 @@ def load_stac(
441441
graph = PGNode("load_stac", arguments=arguments)
442442
try:
443443
metadata = metadata_from_stac(url)
444+
# TODO: also apply spatial/temporal filters to metadata?
445+
444446
if isinstance(bands, list):
445-
# TODO: also apply spatial/temporal filters to metadata?
446-
metadata = metadata.filter_bands(band_names=bands)
447-
except Exception:
447+
if metadata.has_band_dimension():
448+
unknown_bands = [b for b in bands if not metadata.band_dimension.contains_band(b)]
449+
if len(unknown_bands) == 0:
450+
# Ideal case: bands requested by user correspond with bands extracted from metadata.
451+
metadata = metadata.filter_bands(band_names=bands)
452+
else:
453+
metadata = metadata._ensure_band_dimension(
454+
bands=bands,
455+
warning=f"The specified bands {bands} in `load_stac` are not a subset of the bands {metadata.band_dimension.band_names} found in the STAC metadata (unknown bands: {unknown_bands}). Working with specified bands as is.",
456+
)
457+
else:
458+
metadata = metadata._ensure_band_dimension(
459+
name="bands",
460+
bands=bands,
461+
warning=f"Bands {bands} were specified in `load_stac`, but no band dimension was detected in the STAC metadata. Working with band dimension and specified bands.",
462+
)
463+
464+
except Exception as e:
448465
log.warning(f"Failed to extract cube metadata from STAC URL {url}", exc_info=True)
449466
metadata = None
450467
return cls(graph=graph, connection=connection, metadata=metadata)

0 commit comments

Comments
 (0)