Skip to content

Commit 63ac783

Browse files
committed
Issue #699 bands_from_stac_item: consult_collection/consult_assets
and unit test coverage
1 parent 2c5debd commit 63ac783

File tree

6 files changed

+218
-21
lines changed

6 files changed

+218
-21
lines changed

openeo/metadata.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ def bands_from_stac_collection(
883883
return _BandList(self._band_from_common_bands_metadata(b) for b in collection.summaries.lists["bands"])
884884
elif consult_items:
885885
bands = _BandList.merge(
886-
self.bands_from_stac_item(item=i, consult_parent=False, consult_assets=consult_assets)
886+
self.bands_from_stac_item(item=i, consult_collection=False, consult_assets=consult_assets)
887887
for i in collection.get_items()
888888
)
889889
if bands:
@@ -894,17 +894,18 @@ def bands_from_stac_collection(
894894
return _BandList([])
895895

896896
def bands_from_stac_item(
897-
self, item: pystac.Item, *, consult_parent: bool = True, consult_assets: bool = True
897+
self, item: pystac.Item, *, consult_collection: bool = True, consult_assets: bool = True
898898
) -> _BandList:
899899
# TODO: "eo:bands" vs "bands" priority based on STAC and EO extension version information
900900
self._log(f"bands_from_stac_item with {item.properties.keys()=}")
901901
if "eo:bands" in item.properties:
902902
return _BandList(self._band_from_eo_bands_metadata(b) for b in item.properties["eo:bands"])
903903
elif "bands" in item.properties:
904904
return _BandList(self._band_from_common_bands_metadata(b) for b in item.properties["bands"])
905-
elif consult_parent and (parent_collection := item.get_collection()) is not None:
905+
elif consult_collection and (parent_collection := item.get_collection()) is not None:
906906
return self.bands_from_stac_collection(collection=parent_collection)
907907
elif consult_assets:
908+
# TODO: filter on asset roles?
908909
bands = _BandList.merge(self.bands_from_stac_asset(asset=a) for a in item.get_assets().values())
909910
if bands:
910911
return bands

openeo/testing/stac.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,19 @@ def catalog(
108108
if stac_extensions is not None:
109109
d["stac_extensions"] = stac_extensions
110110
return d
111+
112+
@classmethod
113+
def asset(
114+
cls,
115+
href: str = "https://stac.test/asset.tiff",
116+
type: str = "image/tiff; application=geotiff",
117+
roles: Optional[List[str]] = None,
118+
**kwargs,
119+
):
120+
d = {"href": href, **kwargs}
121+
if type:
122+
d["type"] = type
123+
if roles is not None:
124+
d["roles"] = roles
125+
126+
return d

openeo/utils/normalize.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Iterable, Tuple, Union
1+
from typing import Callable, Iterable, Optional, Tuple, Union
22

33

44
def normalize_resample_resolution(
@@ -16,9 +16,11 @@ def normalize_resample_resolution(
1616
raise ValueError(f"Invalid resolution {resolution!r}")
1717

1818

19-
def unique(iterable, key=lambda x: x) -> Iterable:
19+
def unique(iterable, key: Optional[Callable] = None) -> Iterable:
2020
"""Deduplicate an iterable based on a key function."""
21+
# TODO: also support non-hashable items?
2122
seen = set()
23+
key = key or (lambda x: x)
2224
for x in iterable:
2325
k = key(x)
2426
if k not in seen:

tests/test_metadata.py

Lines changed: 169 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,10 @@ def test_bands_from_stac_item(self, data, expected):
13611361
item = pystac.Item.from_dict(data)
13621362
assert _StacMetadataParser().bands_from_stac_item(item=item) == expected
13631363

1364+
def test_bands_from_stac_item_no_bands(self):
1365+
item = pystac.Item.from_dict(StacDummyBuilder.item())
1366+
assert _StacMetadataParser().bands_from_stac_item(item=item) == []
1367+
13641368
@pytest.mark.parametrize(
13651369
["data", "expected"],
13661370
[
@@ -1390,33 +1394,182 @@ def test_bands_from_stac_item_band_names(self, data, expected):
13901394
assert _StacMetadataParser().bands_from_stac_item(item=item).band_names() == expected
13911395

13921396
@pytest.mark.parametrize(
1393-
["data", "expected"],
1397+
["entities", "kwargs", "expected"],
13941398
[
13951399
(
1396-
StacDummyBuilder.collection(
1400+
{
1401+
"item.json": StacDummyBuilder.item(
1402+
stac_version="1.0.0",
1403+
links=[{"rel": "collection", "href": "collection.json"}],
1404+
),
1405+
"collection.json": StacDummyBuilder.collection(
1406+
stac_version="1.0.0",
1407+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1408+
summaries={"eo:bands": [{"name": "B02"}, {"name": "B03"}]},
1409+
),
1410+
},
1411+
{},
1412+
[Band("B02"), Band("B03")],
1413+
),
1414+
(
1415+
{
1416+
"item.json": StacDummyBuilder.item(
1417+
stac_version="1.1.0",
1418+
links=[{"rel": "collection", "href": "collection.json"}],
1419+
),
1420+
"collection.json": StacDummyBuilder.collection(
1421+
stac_version="1.1.0",
1422+
summaries={"bands": [{"name": "B02"}, {"name": "B03"}]},
1423+
),
1424+
},
1425+
{},
1426+
[Band("B02"), Band("B03")],
1427+
),
1428+
(
1429+
{
1430+
"item.json": StacDummyBuilder.item(
1431+
stac_version="1.1.0",
1432+
links=[{"rel": "collection", "href": "collection.json"}],
1433+
),
1434+
"collection.json": StacDummyBuilder.collection(
1435+
stac_version="1.1.0",
1436+
summaries={"bands": [{"name": "B02"}, {"name": "B03"}]},
1437+
),
1438+
},
1439+
{"consult_collection": False},
1440+
[],
1441+
),
1442+
(
1443+
{
1444+
"item.json": StacDummyBuilder.item(
1445+
stac_version="1.1.0",
1446+
properties={"bands": [{"name": "B03"}]},
1447+
links=[{"rel": "collection", "href": "collection.json"}],
1448+
),
1449+
"collection.json": StacDummyBuilder.collection(
1450+
stac_version="1.1.0",
1451+
summaries={"bands": [{"name": "B02"}, {"name": "B03"}]},
1452+
),
1453+
},
1454+
{},
1455+
[Band("B03")],
1456+
),
1457+
],
1458+
)
1459+
def test_bands_from_stac_item_consult_collection(self, tmp_path, entities, kwargs, expected):
1460+
for name, content in entities.items():
1461+
(tmp_path / name).write_text(json.dumps(content))
1462+
item = pystac.Item.from_file(tmp_path / "item.json")
1463+
assert _StacMetadataParser().bands_from_stac_item(item=item, **kwargs) == expected
1464+
1465+
@pytest.mark.parametrize(
1466+
["data", "kwargs", "expected"],
1467+
[
1468+
(
1469+
StacDummyBuilder.item(
13971470
stac_version="1.0.0",
13981471
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1399-
summaries={"eo:bands": [{"name": "B02"}, {"name": "B03"}, {"name": "B04"}]},
1472+
properties={
1473+
"eo:bands": [{"name": "B03"}, {"name": "B04"}],
1474+
},
1475+
assets={
1476+
"asset1": {
1477+
"href": "https://stac.test/asset.tif",
1478+
"roles": ["data"],
1479+
}
1480+
},
14001481
),
1401-
[Band("B02"), Band("B03"), Band("B04")],
1482+
{},
1483+
[Band("B03"), Band("B04")],
14021484
),
14031485
(
1404-
StacDummyBuilder.collection(
1405-
stac_version="1.1.0",
1406-
summaries={"bands": [{"name": "B02"}, {"name": "B03"}, {"name": "B04"}]},
1486+
StacDummyBuilder.item(
1487+
stac_version="1.0.0",
1488+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1489+
properties={
1490+
"eo:bands": [{"name": "B03"}, {"name": "B04"}],
1491+
},
1492+
assets={
1493+
"asset1": {
1494+
"href": "https://stac.test/asset.tif",
1495+
"roles": ["data"],
1496+
"eo:bands": [{"name": "B02"}],
1497+
}
1498+
},
14071499
),
1408-
[Band("B02"), Band("B03"), Band("B04")],
1500+
{},
1501+
[Band("B03"), Band("B04")],
1502+
),
1503+
(
1504+
StacDummyBuilder.item(
1505+
stac_version="1.0.0",
1506+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1507+
assets={
1508+
"asset1": {
1509+
"href": "https://stac.test/asset.tif",
1510+
"roles": ["data"],
1511+
"eo:bands": [{"name": "B02"}],
1512+
}
1513+
},
1514+
),
1515+
{},
1516+
[Band("B02")],
1517+
),
1518+
(
1519+
StacDummyBuilder.item(
1520+
stac_version="1.0.0",
1521+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1522+
assets={
1523+
"asset1": {
1524+
"href": "https://stac.test/asset.tif",
1525+
"roles": ["data"],
1526+
"eo:bands": [{"name": "B02"}],
1527+
},
1528+
"asset2": {
1529+
"href": "https://stac.test/asset.tif",
1530+
"roles": ["data"],
1531+
"eo:bands": [{"name": "B05"}],
1532+
},
1533+
},
1534+
),
1535+
{},
1536+
[Band("B02"), Band("B05")],
1537+
),
1538+
(
1539+
StacDummyBuilder.item(
1540+
stac_version="1.0.0",
1541+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1542+
assets={
1543+
"asset1": {
1544+
"href": "https://stac.test/asset.tif",
1545+
"roles": ["data"],
1546+
"eo:bands": [{"name": "B02"}],
1547+
}
1548+
},
1549+
),
1550+
{"consult_assets": False},
1551+
[],
1552+
),
1553+
(
1554+
StacDummyBuilder.item(
1555+
stac_version="1.0.0",
1556+
stac_extensions=["https://stac-extensions.github.io/eo/v1.1.0/schema.json"],
1557+
assets={
1558+
"asset1": {
1559+
"href": "https://stac.test/asset.tif",
1560+
"roles": ["data"],
1561+
"bands": [{"name": "B02", "eo:common_name": "red", "eo:center_wavelength": 0.665}],
1562+
}
1563+
},
1564+
),
1565+
{},
1566+
[Band("B02", common_name="red", wavelength_um=0.665)],
14091567
),
14101568
],
14111569
)
1412-
def test_bands_from_stac_item_parent_collection(self, data, expected):
1413-
collection = pystac.Collection.from_dict(data)
1414-
item = pystac.Item.from_dict(StacDummyBuilder.item(collection=collection))
1415-
assert _StacMetadataParser().bands_from_stac_item(item=item) == expected
1416-
1417-
def test_bands_from_stac_item_no_bands(self):
1418-
item = pystac.Item.from_dict(StacDummyBuilder.item())
1419-
assert _StacMetadataParser().bands_from_stac_item(item=item) == []
1570+
def test_bands_from_stac_item_consult_assets(self, data, expected, kwargs):
1571+
item = pystac.Item.from_dict(data)
1572+
assert _StacMetadataParser().bands_from_stac_item(item=item, **kwargs) == expected
14201573

14211574
@pytest.mark.parametrize(
14221575
["data", "expected"],

tests/testing/test_stac.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,12 @@ def test_catalog_default(self):
8181
}
8282
# Check if the default catalog validates
8383
pystac.Catalog.from_dict(catalog)
84+
85+
def test_asset_default(self):
86+
asset = StacDummyBuilder.asset()
87+
assert asset == {
88+
"href": "https://stac.test/asset.tiff",
89+
"type": "image/tiff; application=geotiff",
90+
}
91+
# Check if the default asset validates
92+
pystac.Asset.from_dict(asset)

tests/utils/test_nomalize.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,19 @@ def test_unique(input, expected):
5050
actual = unique(input)
5151
assert isinstance(actual, Iterable)
5252
assert list(actual) == expected
53+
54+
55+
@pytest.mark.parametrize(
56+
["input", "key", "expected"],
57+
[
58+
(["apple", "banana", "Apple", "Banana"], None, ["apple", "banana", "Apple", "Banana"]),
59+
(["apple", "banana", "Apple", "Banana"], lambda x: x.lower(), ["apple", "banana"]),
60+
([(1, 2), (2, 1), (2, 3)], sum, [(1, 2), (2, 3)]),
61+
([(1, 2), (2, 1), (2, 3)], lambda x: x[0], [(1, 2), (2, 1)]),
62+
([(1, 2), (2, 1), (2, 3)], lambda x: x[1], [(1, 2), (2, 1), (2, 3)]),
63+
],
64+
)
65+
def test_unique_with_key(input, key, expected):
66+
actual = unique(input, key=key)
67+
assert isinstance(actual, Iterable)
68+
assert list(actual) == expected

0 commit comments

Comments
 (0)