From ff70ed73ffc17ce2d6ebe5216b9e121b85c5e7cd Mon Sep 17 00:00:00 2001 From: Stelios Voutsinas Date: Mon, 16 Dec 2024 12:29:24 -0700 Subject: [PATCH] Read band information from obscore config and fix self-description doc --- ...241216_192656_steliosvoutsinas_DM_46975.md | 6 ++ src/sia/models/sia_query_params.py | 43 ++++++++++++++ src/sia/services/response_handler.py | 34 +++++++++++ src/sia/templates/self_description.xml | 22 +++---- tests/handlers/external/external_test.py | 18 ++++++ tests/models/sia_params_test.py | 59 +++++++++++++++++++ tests/templates/self_description.xml | 22 +++---- 7 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 changelog.d/20241216_192656_steliosvoutsinas_DM_46975.md diff --git a/changelog.d/20241216_192656_steliosvoutsinas_DM_46975.md b/changelog.d/20241216_192656_steliosvoutsinas_DM_46975.md new file mode 100644 index 0000000..98d3905 --- /dev/null +++ b/changelog.d/20241216_192656_steliosvoutsinas_DM_46975.md @@ -0,0 +1,6 @@ + + +### New features + +- Now reads bands information from obscore config +- Fix some of the self-descirption documentation (calibs and remove specify statements) diff --git a/src/sia/models/sia_query_params.py b/src/sia/models/sia_query_params.py index 4f48034..0da7bfc 100644 --- a/src/sia/models/sia_query_params.py +++ b/src/sia/models/sia_query_params.py @@ -14,6 +14,7 @@ from ..models.common import CaseInsensitiveEnum __all__ = [ + "BandInfo", "BaseQueryParams", "CalibLevel", "DPType", @@ -66,6 +67,48 @@ class Polarization(CaseInsensitiveEnum): YX = "YX" +@dataclass +class BandInfo: + """A class to represent a band's wavelength range. + + Attributes + ---------- + label + The band's label. + low + The low end of the band's wavelength range. + high + The high end of the band's wavelength range. + """ + + label: str + low: float + high: float + + @property + def midpoint(self) -> float: + """Calculate the midpoint of the band's wavelength range. + + Returns + ------- + float + The midpoint of the band's wavelength range + """ + return (self.low + self.high) / 2 + + @property + def formatted_midpoint(self) -> str: + """Return the midpoint formatted in scientific notation. + + Returns + ------- + str + The midpoint formatted in scientific notation + """ + nm_value = self.midpoint * 1e9 + return f"{nm_value:.1f}e-9" + + class BaseQueryParams(ABC): """Base class for query parameters.""" diff --git a/src/sia/services/response_handler.py b/src/sia/services/response_handler.py index 9918120..ce0cd3f 100644 --- a/src/sia/services/response_handler.py +++ b/src/sia/services/response_handler.py @@ -16,6 +16,7 @@ from ..constants import RESULT_NAME as RESULT from ..factory import Factory from ..models.data_collections import ButlerDataCollection +from ..models.sia_query_params import BandInfo from ..services.votable import VotableConverterService logger = structlog.get_logger(__name__) @@ -32,6 +33,34 @@ class ResponseHandlerService: """Service for handling the SIAv2 query response.""" + @staticmethod + def _generate_band_info( + spectral_ranges: dict[str, tuple[float | None, float | None]], + ) -> list[BandInfo]: + """Generate band information from spectral ranges dictionary. + + Parameters + ---------- + spectral_ranges + The spectral ranges dictionary. + + Returns + ------- + list[BandInfo] + The list of BandInfo objects. + """ + bands = [] + for band_name, (low, high) in spectral_ranges.items(): + if low is not None and high is not None: + # The Rubin label is hardcoded here, but it could be + # parameterized if needed in the future. + bands.append( + BandInfo( + label=f"Rubin band {band_name}", low=low, high=high + ) + ) + return bands + @staticmethod def self_description_response( request: Request, @@ -59,6 +88,10 @@ def self_description_response( Response The response containing the self-description. """ + bands = ResponseHandlerService._generate_band_info( + obscore_config.spectral_ranges + ) + return _TEMPLATES.TemplateResponse( request, "self_description.xml", @@ -77,6 +110,7 @@ def self_description_response( "query", collection_name=butler_collection.name ), "facility_name": obscore_config.facility_name.strip(), + "bands": bands, }, headers={ "content-disposition": f"attachment; filename={RESULT}.xml", diff --git a/src/sia/templates/self_description.xml b/src/sia/templates/self_description.xml index 99702ac..793ad4f 100644 --- a/src/sia/templates/self_description.xml +++ b/src/sia/templates/self_description.xml @@ -5,16 +5,18 @@ Self description and list of supported parameters - - Energy bounds + + Wavelength/filter band selection + {%- for band in bands %} + - \ No newline at end of file + diff --git a/tests/handlers/external/external_test.py b/tests/handlers/external/external_test.py index d79aaec..ba362f8 100644 --- a/tests/handlers/external/external_test.py +++ b/tests/handlers/external/external_test.py @@ -12,6 +12,7 @@ from sia.config import config from sia.constants import RESULT_NAME +from sia.models.sia_query_params import BandInfo from tests.support.butler import MockButler, MockButlerQueryService from tests.support.constants import EXCEPTION_MESSAGES from tests.support.validators import validate_votable_error @@ -240,12 +241,29 @@ async def test_query_maxrec_zero( ) templates_dir = Jinja2Templates(template_dir) + bands = [ + BandInfo(label="Rubin band HSC-G", low=406.0e-9, high=545.0e-9), + BandInfo(label="Rubin band HSC-R", low=543.0e-9, high=693.0e-9), + BandInfo(label="Rubin band HSC-R2", low=542.0e-9, high=693.0e-9), + BandInfo(label="Rubin band HSC-I", low=690.0e-9, high=842.0e-9), + BandInfo(label="Rubin band HSC-I2", low=692.0e-9, high=850.0e-9), + BandInfo(label="Rubin band HSC-Z", low=852.0e-9, high=928.0e-9), + BandInfo(label="Rubin band HSC-Y", low=937.0e-9, high=1015.0e-9), + BandInfo(label="Rubin band N921", low=914.7e-9, high=928.1e-9), + BandInfo(label="Rubin band g", low=406.0e-9, high=545.0e-9), + BandInfo(label="Rubin band r", low=542.0e-9, high=693.0e-9), + BandInfo(label="Rubin band i", low=692.0e-9, high=850.0e-9), + BandInfo(label="Rubin band z", low=852.0e-9, high=928.0e-9), + BandInfo(label="Rubin band y", low=937.0e-9, high=1015.0e-9), + ] + context = { "instruments": ["HSC"], "collections": ["LSST.CI"], "resource_identifier": "ivo://rubin//ci_hsc_gen3", "access_url": "https://example.com/api/sia/hsc/query", "facility_name": "Subaru", + "bands": bands, } template_rendered = templates_dir.get_template( diff --git a/tests/models/sia_params_test.py b/tests/models/sia_params_test.py index b4ec4cb..a357980 100644 --- a/tests/models/sia_params_test.py +++ b/tests/models/sia_params_test.py @@ -6,6 +6,7 @@ from sia.models.common import CaseInsensitiveEnum from sia.models.sia_query_params import ( + BandInfo, CalibLevel, DPType, Polarization, @@ -135,6 +136,64 @@ async def test_sia_params_default_values() -> None: assert params.responseformat is None +def test_band_info_initialization() -> None: + """Test proper initialization of BandInfo.""" + band = BandInfo(label="Rubin band u", low=330.0e-9, high=400.0e-9) + assert band.label == "Rubin band u" + assert band.low == 330.0e-9 + assert band.high == 400.0e-9 + + +def test_band_info_midpoint_calculation() -> None: + """Test midpoint calculations for different bands.""" + band_u = BandInfo(label="Rubin band u", low=330.0e-9, high=400.0e-9) + expected_u_midpoint = (330.0e-9 + 400.0e-9) / 2 + assert band_u.midpoint == expected_u_midpoint + + band_y = BandInfo(label="Rubin band y", low=970.0e-9, high=1060.0e-9) + expected_y_midpoint = (970.0e-9 + 1060.0e-9) / 2 + assert band_y.midpoint == expected_y_midpoint + + +def test_band_info_formatted_midpoint() -> None: + """Test formatted midpoint string representations.""" + test_cases = [ + { + "label": "Rubin band u", + "low": 330.0e-9, + "high": 400.0e-9, + "expected": "365.0e-9", + }, + { + "label": "Rubin band g", + "low": 402.0e-9, + "high": 552.0e-9, + "expected": "477.0e-9", + }, + { + "label": "Rubin band y", + "low": 970.0e-9, + "high": 1060.0e-9, + "expected": "1015.0e-9", + }, + ] + + for case in test_cases: + low = ( + float(case["low"]) + if isinstance(case["low"], (int | float)) + else 0.0 + ) + high = ( + float(case["high"]) + if isinstance(case["high"], (int | float)) + else 0.0 + ) + + band = BandInfo(label=str(case["label"]), low=low, high=high) + assert band.formatted_midpoint == case["expected"] + + @pytest.fixture def sample_sia_params() -> SIAQueryParams: return SIAQueryParams( diff --git a/tests/templates/self_description.xml b/tests/templates/self_description.xml index 99702ac..793ad4f 100644 --- a/tests/templates/self_description.xml +++ b/tests/templates/self_description.xml @@ -5,16 +5,18 @@ Self description and list of supported parameters - - Energy bounds + + Wavelength/filter band selection + {%- for band in bands %} + - \ No newline at end of file +