Skip to content
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ svo_fps
^^^^^^^

- Add ``get_filter_metadata`` to allow retrieval of filter metadata. [#3528]
- Add ``get_zeropoint`` to allow retrieval of filter zeropoints and allow kwarg passing to ``get_filter_metadata``. [#3545]

heasarc
^^^^^^^
Expand Down
223 changes: 199 additions & 24 deletions astroquery/svo_fps/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
QUERY_PARAMETERS.update(("Instrument", "Facility", "PhotSystem", "ID", "PhotCalID",
"FORMAT", "VERB"))

ALLOWED_QUERY_PARAMETERS = {
"VERB": {0, 1, 2},
"FORMAT": {"metadata", None}
}


class SvoFpsClass(BaseQuery):
"""
Expand All @@ -31,21 +36,88 @@ class SvoFpsClass(BaseQuery):
SVO_MAIN_URL = conf.base_url
TIMEOUT = conf.timeout

def data_from_svo(self, query, *, cache=True, timeout=None,
error_msg='No data found for requested query'):
def data_from_svo(self,
*,
WavelengthRef_min=None,
WavelengthRef_max=None,
WavelengthMean_min=None,
WavelengthMean_max=None,
WavelengthEff_min=None,
WavelengthEff_max=None,
WavelengthMin_min=None,
WavelengthMin_max=None,
WavelengthMax_min=None,
WavelengthMax_max=None,
WidthEff_min=None,
WidthEff_max=None,
FWHM_min=None,
FWHM_max=None,
Instrument=None,
Facility=None,
PhotSystem=None,
ID=None,
PhotCalID=None,
FORMAT=None,
VERB=2,
cache=True, timeout=None,
error_msg='No data found for requested query',
):
"""Get data in response to the query send to SVO FPS.
This method is not generally intended for users, but it can be helpful
if you want something very specific from the SVO FPS service.
If you don't know what you're doing, try `get_filter_index`,
`get_filter_list`, and `get_transmission_data` instead.

Description of search parameters can be found at
https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice


Parameters
----------
query : dict
Used to create a HTTP query string i.e. send to SVO FPS to get data.
In dictionary, specify keys as search parameters (str) and
values as required. Description of search parameters can be found at
https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice
WavelengthRef_min : float, optional
Min value for WavelengthRef parameter
WavelengthRef_max : float, optional
Max value for WavelengthRef parameter
WavelengthMean_min : float, optional
Min value for WavelengthMean parameter
WavelengthMean_max : float, optional
Max value for WavelengthMean parameter
WavelengthEff_min : float, optional
Min value for WavelengthEff parameter
WavelengthEff_max : float, optional
Max value for WavelengthEff parameter
WavelengthMin_min : float, optional
Min value for WavelengthMin parameter
WavelengthMin_max : float, optional
Max value for WavelengthMin parameter
WavelengthMax_min : float, optional
Min value for WavelengthMax parameter
WavelengthMax_max : float, optional
Max value for WavelengthMax parameter
WidthEff_min : float, optional
Min value for WidthEff parameter
WidthEff_max : float, optional
Max value for WidthEff parameter
FWHM_min : float, optional
Min value for FWHM parameter
FWHM_max : float, optional
Max value for FWHM parameter
Instrument : str, optional
Instrument for filters (default is None). Leave empty if there are no instruments for specified facility
Facility : str, optional
Facility for filters (default is None)
PhotSystem : str, optional
Photometric system for filters (default is None)
ID : str, optional
Filter ID (default is None)
PhotCalID : str, optional
Photometric calibration ID (default is None)
FORMAT : str, optional
Format of the output. Default includes all data, ``metadata`` includes only metadata.
VERB : 0, 1, or 2
0: The resulting VOTable won't include the transmission curve or PARAM descriptions.
1: The resulting VOTable won't include the transmission curve but it will include PARAM descriptions.
2: The resulting VOTable will include the transmission curve and PARAM descriptions.
error_msg : str, optional
Error message to be shown in case no table element found in the
responded VOTable. Use this to make error message verbose in context
Expand All @@ -59,14 +131,39 @@ def data_from_svo(self, query, *, cache=True, timeout=None,
astropy.table.table.Table object
Table containing data fetched from SVO (in response to query)
"""
bad_params = [param for param in query if param not in QUERY_PARAMETERS]
if bad_params:
raise InvalidQueryError(
f"parameter{'s' if len(bad_params) > 1 else ''} "
f"{', '.join(bad_params)} {'are' if len(bad_params) > 1 else 'is'} "
f"invalid. For a description of valid query parameters see "
"https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice"
)

query = {
'WavelengthRef_min': WavelengthRef_min,
'WavelengthRef_max': WavelengthRef_max,
'WavelengthMean_min': WavelengthMean_min,
'WavelengthMean_max': WavelengthMean_max,
'WavelengthEff_min': WavelengthEff_min,
'WavelengthEff_max': WavelengthEff_max,
'WavelengthMin_min': WavelengthMin_min,
'WavelengthMin_max': WavelengthMin_max,
'WavelengthMax_min': WavelengthMax_min,
'WavelengthMax_max': WavelengthMax_max,
'WidthEff_min': WidthEff_min,
'WidthEff_max': WidthEff_max,
'FWHM_min': FWHM_min,
'FWHM_max': FWHM_max,
'Instrument': Instrument,
'Facility': Facility,
'PhotSystem': PhotSystem,
'ID': ID,
'PhotCalID': PhotCalID,
'FORMAT': FORMAT,
'VERB': VERB
}

# check validity of query parameters with limited allowed values
for key in ALLOWED_QUERY_PARAMETERS:
if key in query and query[key] not in ALLOWED_QUERY_PARAMETERS[key]:
raise InvalidQueryError(
f"Invalid value for parameter {key}. Allowed values are "
f"{ALLOWED_QUERY_PARAMETERS[key]}"
)

response = self._request("GET", self.SVO_MAIN_URL, params=query,
timeout=timeout or self.TIMEOUT,
cache=cache)
Expand Down Expand Up @@ -97,18 +194,21 @@ def get_filter_index(self, wavelength_eff_min, wavelength_eff_max, **kwargs):
astropy.table.table.Table object
Table containing data fetched from SVO (in response to query)
"""
query = {'WavelengthEff_min': wavelength_eff_min.to_value(u.angstrom),
'WavelengthEff_max': wavelength_eff_max.to_value(u.angstrom)}
error_msg = 'No filter found for requested Wavelength Effective range'
try:
return self.data_from_svo(query=query, error_msg=error_msg, **kwargs)
return self.data_from_svo(
WavelengthEff_min=wavelength_eff_min.to_value(u.angstrom),
WavelengthEff_max=wavelength_eff_max.to_value(u.angstrom),
error_msg=error_msg,
**kwargs
)
except requests.ReadTimeout:
raise TimeoutError(
"Query did not finish fast enough. A smaller wavelength range might "
"succeed. Try increasing the timeout limit if a large range is needed."
)

def get_filter_metadata(self, filter_id, *, cache=True, timeout=None):
def get_filter_metadata(self, filter_id, *, cache=True, timeout=None, **kwargs):
Comment thread
keflavich marked this conversation as resolved.
"""Get metadata/parameters for the requested Filter ID from SVO

Parameters
Expand All @@ -122,13 +222,38 @@ def get_filter_metadata(self, filter_id, *, cache=True, timeout=None):
See :ref:`caching documentation <astroquery_cache>`.
timeout : int
Timeout in seconds. If not specified, defaults to ``conf.timeout``.
kwargs : dict
Appended to the ``query`` dictionary sent to SVO. See the API
documentation of `data_from_svo` for the valid parameter names.

Returns
-------
params : dict
Dictionary of VOTable PARAM names and values.
"""
query = {'ID': filter_id, 'VERB': 0}
query.update(kwargs)

bad_params = [param for param in query if param not in QUERY_PARAMETERS]
if bad_params:
raise InvalidQueryError(
f"parameter{'s' if len(bad_params) > 1 else ''} "
f"{', '.join(bad_params)} {'are' if len(bad_params) > 1 else 'is'} "
f"invalid. For a description of valid query parameters see "
"https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice"
)
Comment thread
keflavich marked this conversation as resolved.
Outdated

query.update(kwargs)

bad_params = [param for param in query if param not in QUERY_PARAMETERS]
if bad_params:
raise InvalidQueryError(
f"parameter{'s' if len(bad_params) > 1 else ''} "
f"{', '.join(bad_params)} {'are' if len(bad_params) > 1 else 'is'} "
f"invalid. For a description of valid query parameters see "
"https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice"
)

response = self._request("GET", self.SVO_MAIN_URL, params=query,
timeout=timeout or self.TIMEOUT,
cache=cache)
Expand All @@ -143,6 +268,54 @@ def get_filter_metadata(self, filter_id, *, cache=True, timeout=None):
params[param.name] = param.value
return params

def get_zeropoint(self, filter_id, mag_system='Vega', **kwargs):
Comment thread
keflavich marked this conversation as resolved.
Outdated
"""
Get the zero point for a specififed filter in a specified system.

This is a highly-specific downselection of the metadata returned by
`get_filter_metadata`; the full metadata includes the zero point with
``Vega`` as the default system.

Parameters
----------
filter_id : str
Filter ID in the format SVO specifies it: 'facilty/instrument.filter'.
This is returned by `get_filter_list` and `get_filter_index` as the
``filterID`` column.
mag_system : str
The magnitude system for which to return the zero point.
Comment thread
keflavich marked this conversation as resolved.
kwargs : dict
Appended to the ``query`` dictionary sent to SVO. See the API
documentation of `data_from_svo` for the valid parameter names.

Examples
--------
>>> from astroquery.svo_fps import SvoFps # doctest: +REMOTE_DATA
>>> SvoFps.get_zeropoint(filter_id='2MASS/2MASS.J', mag_system='AB') # doctest: +REMOTE_DATA
{'MagSys': 'AB',
'ZeroPoint': <Quantity 3631. Jy>,
'ZeroPointUnit': 'Jy',
'ZeroPointType': 'Pogson'}
>>> SvoFps.get_filter_metadata(filter_id='2MASS/2MASS.J', PhotCalID='2MASS/2MASS.J/AB') # doctest: +REMOTE_DATA
{'FilterProfileService': 'ivo://svo/fps',
'filterID': '2MASS/2MASS.J',
...
'PhotCalID': '2MASS/2MASS.J/AB',
'MagSys': 'AB',
'ZeroPoint': <Quantity 3631. Jy>,
'ZeroPointUnit': 'Jy',
'ZeroPointType': 'Pogson'}

"""
metadata = self.get_filter_metadata(filter_id=filter_id,
PhotCalID=f'{filter_id}/{mag_system}', **kwargs)

zeropoint_keys = ['MagSys', 'ZeroPoint', 'ZeroPointUnit', 'ZeroPointType']

zp = {key: metadata[key] for key in zeropoint_keys if key in metadata}

return zp

def get_transmission_data(self, filter_id, **kwargs):
"""Get transmission data for the requested Filter ID from SVO

Expand All @@ -160,9 +333,8 @@ def get_transmission_data(self, filter_id, **kwargs):
astropy.table.table.Table object
Table containing data fetched from SVO (in response to query)
"""
query = {'ID': filter_id}
error_msg = 'No filter found for requested Filter ID'
return self.data_from_svo(query=query, error_msg=error_msg, **kwargs)
return self.data_from_svo(ID=filter_id, error_msg=error_msg, **kwargs)

def get_filter_list(self, facility, *, instrument=None, **kwargs):
"""Get filters data for requested facilty and instrument from SVO
Expand All @@ -182,10 +354,13 @@ def get_filter_list(self, facility, *, instrument=None, **kwargs):
astropy.table.table.Table object
Table containing data fetched from SVO (in response to query)
"""
query = {'Facility': facility,
'Instrument': instrument}
error_msg = 'No filter found for requested Facilty (and Instrument)'
return self.data_from_svo(query=query, error_msg=error_msg, **kwargs)
return self.data_from_svo(
Facility=facility,
Instrument=instrument,
error_msg=error_msg,
**kwargs
)


SvoFps = SvoFpsClass()
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<VOTABLE version="1.1" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<INFO name="QUERY_STATUS" value="OK"/>
<RESOURCE type="results">
<TABLE utype="photdm:PhotometryFilter.transmissionCurve.spectrum">
<PARAM name="FilterProfileService" value="ivo://svo/fps" ucd="meta.ref.ivorn" utype="PhotometryFilter.fpsIdentifier" datatype="char" arraysize="*"/>
<PARAM name="filterID" value="2MASS/2MASS.H" ucd="meta.id" utype="photdm:PhotometryFilter.identifier" datatype="char" arraysize="*"/>
<PARAM name="WavelengthUnit" value="Angstrom" ucd="meta.unit" utype="PhotometryFilter.SpectralAxis.unit" datatype="char" arraysize="*"/>
<PARAM name="Description" value="2MASS H" ucd="meta.note" utype="photdm:PhotometryFilter.description" datatype="char" arraysize="*"/>
<PARAM name="WavelengthEff" value="16620" unit="Angstrom" ucd="em.wl.effective" datatype="float" >
<DESCRIPTION>Manually specified. See reference</DESCRIPTION>
</PARAM>
<PARAM name="ZeroPoint" value="1024" unit="Jy" ucd="phot.flux.density" utype="photdm:PhotCal.ZeroPoint.Flux.value" datatype="float" />
<PARAM name="PhotCalID" value="2MASS/2MASS.H/Vega" ucd="meta.id" utype="photdm:PhotCal.identifier" datatype="char" arraysize="*"/>
<PARAM name="MagSys" value="Vega" ucd="meta.code" utype="photdm:PhotCal.MagnitudeSystem.type" datatype="char" arraysize="*"/>
<PARAM name="ZeroPointUnit" value="Jy" ucd="meta.unit" utype="photdm:PhotCal.ZeroPoint.Flux.unit" datatype="char" arraysize="*"/>
<PARAM name="ZeroPointType" value="Pogson" ucd="meta.code" utype="photdm:PhotCal.ZeroPoint.type" datatype="char" arraysize="*"/>
<DATA>
<TABLEDATA>
</TABLEDATA>
</DATA>
</TABLE>
</RESOURCE>
</VOTABLE>
30 changes: 22 additions & 8 deletions astroquery/svo_fps/tests/test_svo_fps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
from astropy import units as u
from requests import ReadTimeout

from astroquery.exceptions import InvalidQueryError, TimeoutError
from astroquery.exceptions import TimeoutError
from astroquery.utils.mocks import MockResponse
from ..core import SvoFps

DATA_FILES = {'filter_index': 'svo_fps_WavelengthEff_min=12000_WavelengthEff_max=12100.xml',
'transmission_data': 'svo_fps_ID=2MASS.2MASS.H.xml',
'filter_list': 'svo_fps_Facility=Keck_Instrument=NIRC2.xml'
'filter_list': 'svo_fps_Facility=Keck_Instrument=NIRC2.xml',
'zeropoint': 'svo_fps_PhotCalID=2MASS.2MASS.H.Vega.xml',
}
TEST_LAMBDA = 12000
TEST_FILTER_ID = '2MASS/2MASS.H'
TEST_FACILITY = 'Keck'
TEST_INSTRUMENT = 'NIRC2'
TEST_MAG_SYSTEM = 'Vega'


def data_path(filename):
Expand All @@ -35,6 +37,10 @@ def get_mockreturn(method, url, params=None, timeout=10, cache=None, **kwargs):
and (params['WavelengthEff_min'] == TEST_LAMBDA
and params['WavelengthEff_max'] == TEST_LAMBDA+100)):
filename = data_path(DATA_FILES['filter_index'])
elif ('PhotCalID' in params
and params.get('ID') == TEST_FILTER_ID
and params['PhotCalID'] == f'{TEST_FILTER_ID}/{TEST_MAG_SYSTEM}'):
filename = data_path(DATA_FILES['zeropoint'])
elif 'ID' in params and params['ID'] == TEST_FILTER_ID:
filename = data_path(DATA_FILES['filter_index'])
elif 'Facility' in params and (params['Facility'] == TEST_FACILITY
Expand Down Expand Up @@ -84,10 +90,18 @@ def test_get_filter_list(patch_get):
assert 'filterID' in table.colnames


def test_get_zeropoint(patch_get):
zp = SvoFps.get_zeropoint(TEST_FILTER_ID, mag_system=TEST_MAG_SYSTEM)
assert 'ZeroPoint' in zp
assert 'MagSys' in zp
assert zp['MagSys'] == TEST_MAG_SYSTEM
assert 'ZeroPointType' in zp
assert zp['ZeroPointType'] == 'Pogson'
assert 'ZeroPointUnit' in zp
assert zp['ZeroPoint'].unit == u.Jy


def test_invalid_query(patch_get):
msg = r"^parameter bad_param is invalid\. For a description of valid query "
with pytest.raises(InvalidQueryError, match=msg):
SvoFps.data_from_svo(query={"bad_param": 0, "FWHM": 20})
msg = r"^parameters invalid_param, bad_param are invalid\. For a description of "
with pytest.raises(InvalidQueryError, match=msg):
SvoFps.data_from_svo(query={"invalid_param": 0, 'bad_param': -1})
msg = r"^SvoFpsClass.data_from_svo\(\) got an unexpected keyword argument 'bad_param'"
with pytest.raises(TypeError, match=msg):
Comment thread
keflavich marked this conversation as resolved.
Outdated
SvoFps.data_from_svo(bad_param=0, FWHM_min=20)
Loading
Loading