Skip to content

Commit

Permalink
Read band information from obscore config and fix self-description doc
Browse files Browse the repository at this point in the history
  • Loading branch information
stvoutsin committed Dec 16, 2024
1 parent 359936b commit ff70ed7
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 20 deletions.
6 changes: 6 additions & 0 deletions changelog.d/20241216_192656_steliosvoutsinas_DM_46975.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- Delete the sections that don't apply -->

### New features

- Now reads bands information from obscore config
- Fix some of the self-descirption documentation (calibs and remove specify statements)
43 changes: 43 additions & 0 deletions src/sia/models/sia_query_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ..models.common import CaseInsensitiveEnum

__all__ = [
"BandInfo",
"BaseQueryParams",
"CalibLevel",
"DPType",
Expand Down Expand Up @@ -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."""

Expand Down
34 changes: 34 additions & 0 deletions src/sia/services/response_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
22 changes: 12 additions & 10 deletions src/sia/templates/self_description.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
<RESOURCE type="meta" utype="adhoc:this">
<DESCRIPTION>Self description and list of supported parameters</DESCRIPTION>
<GROUP name="inputParams">
<PARAM name="BAND" datatype="double" arraysize="2" unit="m" xtype="interval">
<DESCRIPTION>Energy bounds</DESCRIPTION>
<PARAM name="BAND" datatype="float" arraysize="2*" xtype="interval">
<DESCRIPTION>Wavelength/filter band selection</DESCRIPTION>
{%- for band in bands %}
<OPTION name="{{ band.label }}" value="{{ band.formatted_midpoint }}"/>
{%- endfor %}
</PARAM>
<PARAM name="CALIB" datatype="int" arraysize="*">
<DESCRIPTION>Calibration level</DESCRIPTION>
<VALUES type="actual">
<OPTION value="0"/>
<OPTION value="1"/>
<OPTION value="2"/>
<OPTION value="3"/>
<OPTION value="1" name="Raw Data"/>
<OPTION value="2" name="PVIs"/>
<OPTION value="3" name="Coadds and Difference Images"/>
</VALUES>
</PARAM>
<PARAM name="COLLECTION" datatype="char" arraysize="*">
Expand Down Expand Up @@ -81,10 +83,10 @@
</VALUES>
</PARAM>
<PARAM name="SPATRES" datatype="double" arraysize="2" unit="arcsec" xtype="interval">
<DESCRIPTION>Specify position resolution</DESCRIPTION>
<DESCRIPTION>Position resolution</DESCRIPTION>
</PARAM>
<PARAM name="SPECRP" datatype="double" arraysize="2" xtype="interval">
<DESCRIPTION>Specify energy resolving power</DESCRIPTION>
<DESCRIPTION>Energy resolving power</DESCRIPTION>
</PARAM>
<PARAM name="TARGET" datatype="char" arraysize="*">
<DESCRIPTION>Target name</DESCRIPTION>
Expand Down Expand Up @@ -121,7 +123,7 @@
<DESCRIPTION>Product type (e.g. science, calibration, auxiliary, preview, info)</DESCRIPTION>
</FIELD>
<FIELD name="calib_level" datatype="short" ucd="meta.code;obs.calib" utype="obscore:obsdataset.caliblevel">
<DESCRIPTION>Calibration level of the observation: in {0, 1, 2, 3, 4}</DESCRIPTION>
<DESCRIPTION>Calibration level of the observation: in {1, 2, 3}</DESCRIPTION>
</FIELD>
<FIELD name="dataproduct_type" datatype="char" arraysize="*" ucd="meta.id" utype="obscore:obsdataset.dataproducttype">
<DESCRIPTION>Data product (file content) primary type</DESCRIPTION>
Expand Down Expand Up @@ -198,4 +200,4 @@
</DATA>
</TABLE>
</RESOURCE>
</VOTABLE>
</VOTABLE>
18 changes: 18 additions & 0 deletions tests/handlers/external/external_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
59 changes: 59 additions & 0 deletions tests/models/sia_params_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from sia.models.common import CaseInsensitiveEnum
from sia.models.sia_query_params import (
BandInfo,
CalibLevel,
DPType,
Polarization,
Expand Down Expand Up @@ -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(
Expand Down
22 changes: 12 additions & 10 deletions tests/templates/self_description.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
<RESOURCE type="meta" utype="adhoc:this">
<DESCRIPTION>Self description and list of supported parameters</DESCRIPTION>
<GROUP name="inputParams">
<PARAM name="BAND" datatype="double" arraysize="2" unit="m" xtype="interval">
<DESCRIPTION>Energy bounds</DESCRIPTION>
<PARAM name="BAND" datatype="float" arraysize="2*" xtype="interval">
<DESCRIPTION>Wavelength/filter band selection</DESCRIPTION>
{%- for band in bands %}
<OPTION name="{{ band.label }}" value="{{ band.formatted_midpoint }}"/>
{%- endfor %}
</PARAM>
<PARAM name="CALIB" datatype="int" arraysize="*">
<DESCRIPTION>Calibration level</DESCRIPTION>
<VALUES type="actual">
<OPTION value="0"/>
<OPTION value="1"/>
<OPTION value="2"/>
<OPTION value="3"/>
<OPTION value="1" name="Raw Data"/>
<OPTION value="2" name="PVIs"/>
<OPTION value="3" name="Coadds and Difference Images"/>
</VALUES>
</PARAM>
<PARAM name="COLLECTION" datatype="char" arraysize="*">
Expand Down Expand Up @@ -81,10 +83,10 @@
</VALUES>
</PARAM>
<PARAM name="SPATRES" datatype="double" arraysize="2" unit="arcsec" xtype="interval">
<DESCRIPTION>Specify position resolution</DESCRIPTION>
<DESCRIPTION>Position resolution</DESCRIPTION>
</PARAM>
<PARAM name="SPECRP" datatype="double" arraysize="2" xtype="interval">
<DESCRIPTION>Specify energy resolving power</DESCRIPTION>
<DESCRIPTION>Energy resolving power</DESCRIPTION>
</PARAM>
<PARAM name="TARGET" datatype="char" arraysize="*">
<DESCRIPTION>Target name</DESCRIPTION>
Expand Down Expand Up @@ -121,7 +123,7 @@
<DESCRIPTION>Product type (e.g. science, calibration, auxiliary, preview, info)</DESCRIPTION>
</FIELD>
<FIELD name="calib_level" datatype="short" ucd="meta.code;obs.calib" utype="obscore:obsdataset.caliblevel">
<DESCRIPTION>Calibration level of the observation: in {0, 1, 2, 3, 4}</DESCRIPTION>
<DESCRIPTION>Calibration level of the observation: in {1, 2, 3}</DESCRIPTION>
</FIELD>
<FIELD name="dataproduct_type" datatype="char" arraysize="*" ucd="meta.id" utype="obscore:obsdataset.dataproducttype">
<DESCRIPTION>Data product (file content) primary type</DESCRIPTION>
Expand Down Expand Up @@ -198,4 +200,4 @@
</DATA>
</TABLE>
</RESOURCE>
</VOTABLE>
</VOTABLE>

0 comments on commit ff70ed7

Please sign in to comment.