From bad897097a0fb825901ed1f994832f06dd5921fb Mon Sep 17 00:00:00 2001 From: Matt Savoie Date: Thu, 30 May 2024 10:23:06 -0700 Subject: [PATCH] DAS-2146 - select correct asset from stac Item (#14) --- CHANGELOG.md | 29 +++++++++-- bin/extract-release-notes.sh | 13 ++++- docker/service.Dockerfile | 2 +- docker/service_version.txt | 2 +- harmony_browse_image_generator/adapter.py | 31 ++++++++++-- tests/unit/test_adapter.py | 60 +++++++++++++++++++++++ 6 files changed, 124 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f7ffa..ec2bfdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ -## v1.1.0 - 2024-04-30 +# Changelog + +HyBIG follows semantic versioning. All notable changes to this project will be +documented in this file. The format is based on [Keep a +Changelog](http://keepachangelog.com/en/1.0.0/). + + +## [v1.2.0] - 2024-05-28 + +### Added +Adds functionality to retrieve '`visual`' asset for multi-file +granules. Harmony creates this type of asset when the UMM-G is correctly +configured with a "BROWSE IMAGE SOURCE" subtype. + +## [v1.1.0] - 2024-04-30 ### Changed Changes the computation for an output image's default scale extent. Previously @@ -8,17 +22,17 @@ and uses that transformed boundry as the default region to make a scale extent f Upgraded harmony-service-lib to v1.0.26 -## v1.0.2 - 2024-04-05 +## [v1.0.2] - 2024-04-05 This version of HyBIG correctly handles missing/bad input data marked by _FillValue or NoData. Anytime a bad value occurs in the input raster, the output png image will set to transparent. -## v1.0.1 - 2024-04-05 +## [v1.0.1] - 2024-04-05 This version of HyBIG updates the repository to use `black` code formatting throughout. There should be no functional change to the service. -## v1.0.0 - 2024-01-22 +## [v1.0.0] - 2024-01-22 This version of the Harmony Browse Image Generator (HyBIG) contains all functionality previously released internally to EOSDIS as sds/harmony-browse-image-generator:0.0.11. @@ -28,3 +42,10 @@ outlined by the NASA open-source guidelines. For more information on internal releases prior to NASA open-source approval, see legacy-CHANGELOG.md. + +[unreleased]:https://github.com/nasa/harmony-browse-image-generator/compare/1.2.0..HEAD +[v1.2.0]: https://github.com/nasa/harmony-browse-image-generator/compare/1.1.0..1.2.0 +[v1.1.0]: https://github.com/nasa/harmony-browse-image-generator/compare/1.0.2..1.1.0 +[v1.0.2]: https://github.com/nasa/harmony-browse-image-generator/compare/1.0.1..1.0.2 +[v1.0.1]: https://github.com/nasa/harmony-browse-image-generator/compare/1.0.0..1.0.1 +[v1.0.0]: https://github.com/nasa/harmony-browse-image-generator/compare/0.0.11-legacy..1.0.0 diff --git a/bin/extract-release-notes.sh b/bin/extract-release-notes.sh index bb15f23..232cfd5 100755 --- a/bin/extract-release-notes.sh +++ b/bin/extract-release-notes.sh @@ -12,11 +12,20 @@ ############################################################################### CHANGELOG_FILE="CHANGELOG.md" -VERSION_PATTERN="^## v" + +## captures versions +## >## v1.0.0 +## >## [v1.0.0] +VERSION_PATTERN="^## [\[]v" + +## captures url links +## [unreleased]:https://github.com/nasa/harmony-browse-image-generator/compare/1.2.0..HEAD +## [v1.2.0]: https://github.com/nasa/harmony-browse-image-generator/compare/1.1.0..1.2.0 +LINK_PATTERN="^\[.*\].*\.\..*" # Read the file and extract text between the first two occurrences of the # VERSION_PATTERN result=$(awk "/$VERSION_PATTERN/{c++; if(c==2) exit;} c==1" "$CHANGELOG_FILE") # Print the result -echo "$result" | grep -v "$VERSION_PATTERN" +echo "$result" | grep -v "$VERSION_PATTERN" | grep -v "$LINK_PATTERN" diff --git a/docker/service.Dockerfile b/docker/service.Dockerfile index d727a9f..19e93ea 100644 --- a/docker/service.Dockerfile +++ b/docker/service.Dockerfile @@ -19,7 +19,7 @@ WORKDIR "/home" # Create Conda environment: COPY conda_requirements.txt conda_requirements.txt RUN conda create --yes --name hybig --file conda_requirements.txt \ - python=3.10 --channel conda-forge --channel defaults -q && \ + python=3.11 --channel conda-forge --channel defaults -q && \ conda clean --all --force-pkgs-dirs --yes # Install additional Pip dependencies diff --git a/docker/service_version.txt b/docker/service_version.txt index 9084fa2..26aaba0 100644 --- a/docker/service_version.txt +++ b/docker/service_version.txt @@ -1 +1 @@ -1.1.0 +1.2.0 diff --git a/harmony_browse_image_generator/adapter.py b/harmony_browse_image_generator/adapter.py index 77d86f7..5d6bdf3 100644 --- a/harmony_browse_image_generator/adapter.py +++ b/harmony_browse_image_generator/adapter.py @@ -65,18 +65,39 @@ def validate_message(self): 'Harmony ScaleExtents must be in order [xmin,ymin,xmax,ymax].' ) + def get_asset_from_item(self, item: Item) -> Asset: + """Returns the correct browse asset from a stac Item. + + This is used to select which asset is used by HyBIG to generate + the browse image following these steps: + + 1. If found, return the first asset with 'visual' in any of the item's values' roles. + 2. If found, return the first asset that has 'data' in its item's values' roles. + 3. Raise a StopIteration error. + + """ + try: + return next( + item_asset + for item_asset in item.assets.values() + if 'visual' in (item_asset.roles or []) + ) + except StopIteration: + return next( + item_asset + for item_asset in item.assets.values() + if 'data' in (item_asset.roles or []) + ) + def process_item(self, item: Item, source: HarmonySource) -> Item: """Processes a single input STAC item.""" + try: working_directory = mkdtemp() results = item.clone() results.assets = {} - asset = next( - item_asset - for item_asset in item.assets.values() - if 'data' in (item_asset.roles or []) - ) + asset = self.get_asset_from_item(item) color_palette = get_color_palette_from_item(item) diff --git a/tests/unit/test_adapter.py b/tests/unit/test_adapter.py index 081bb30..2a34b84 100644 --- a/tests/unit/test_adapter.py +++ b/tests/unit/test_adapter.py @@ -1,7 +1,10 @@ +from datetime import datetime from unittest import TestCase +from unittest.mock import Mock from harmony.message import Message from harmony.util import config +from pystac import Asset, Item from harmony_browse_image_generator.adapter import BrowseImageGeneratorAdapter from harmony_browse_image_generator.exceptions import HyBIGInvalidMessageError @@ -152,3 +155,60 @@ def test_create_output_stac_items(self): self.assertEqual( output_stac_item.assets['auxiliary'].title, 'browse.png.aux.xml' ) + + +class TestAdapterAssetFromItem(TestCase): + """A class testing get_asset_from_item function.""" + + def setUp(self): + self.adapter = BrowseImageGeneratorAdapter({}, {}) + self.visual_asset = Asset(Mock(), roles=['visual']) + self.data_asset = Asset(Mock(), roles=['data']) + self.none_asset = Asset(Mock(), roles=[]) + self.other_asset = Asset(Mock(), roles=['other']) + + def item_fixture(self, assets: dict) -> Item: + item = Item(Mock(), None, None, datetime.now(), {}) + item.assets = assets + return item + + def test_get_asset_from_item_with_visual_role(self): + with self.subTest('data asset first'): + item = self.item_fixture( + {'data': self.data_asset, 'visual': self.visual_asset} + ) + expected = self.visual_asset + + actual = self.adapter.get_asset_from_item(item) + + self.assertEqual(expected, actual) + + with self.subTest('visual asset first'): + item = self.item_fixture( + {'visual': self.visual_asset, 'data': self.data_asset} + ) + expected = self.visual_asset + + actual = self.adapter.get_asset_from_item(item) + + self.assertEqual(expected, actual) + + def test_get_asset_from_item_with_data_role(self): + item = self.item_fixture({'data': self.data_asset, 'other': self.other_asset}) + expected = self.data_asset + + actual = self.adapter.get_asset_from_item(item) + + self.assertEqual(expected, actual) + + def test_get_asset_from_item_no_roles(self): + item = self.item_fixture({'none': self.none_asset}) + with self.assertRaises(StopIteration): + self.adapter.get_asset_from_item(item) + + def test_get_asset_from_item_no_matching_roles(self): + item = self.item_fixture( + {'first': self.other_asset, 'second': self.other_asset} + ) + with self.assertRaises(StopIteration): + self.adapter.get_asset_from_item(item)