Skip to content

Commit 12c84bb

Browse files
authored
Merge pull request #3545 from keflavich/svo_fps-zeropoint
Add get_zeropoint and more flexible metadata querying to SVO FPS
2 parents a4e8411 + d4ca72b commit 12c84bb

5 files changed

Lines changed: 328 additions & 51 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ svo_fps
9696
^^^^^^^
9797

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

100101
heasarc
101102
^^^^^^^

astroquery/svo_fps/core.py

Lines changed: 232 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,61 @@
1313

1414
__all__ = ['SvoFpsClass', 'SvoFps']
1515

16-
# Valid query parameters taken from
16+
17+
# Parameters the SVO service accepts as either ``Name`` (base) or as a
18+
# range pair ``Name_min``/``Name_max``.
19+
RANGE_BASES = {"wavelength_ref", "wavelength_mean", "wavelength_eff",
20+
"wavelength_min", "wavelength_max", "width_eff", "fwhm"}
21+
22+
# Base mapping from public (snake_case, lowercase) keyword names to the
23+
# CamelCase parameter names the SVO FPS server actually accepts. The SVO
24+
# service is case-sensitive: lowercase parameters silently fall through
25+
# (the server returns the full unfiltered response), so every value sent
26+
# in a query must come from this mapping. See
1727
# https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice
18-
_params_with_range = {"WavelengthRef", "WavelengthMean", "WavelengthEff",
19-
"WavelengthMin", "WavelengthMax", "WidthEff", "FWHM"}
20-
QUERY_PARAMETERS = _params_with_range.copy()
21-
for suffix in ("_min", "_max"):
22-
QUERY_PARAMETERS.update(param + suffix for param in _params_with_range)
23-
QUERY_PARAMETERS.update(("Instrument", "Facility", "PhotSystem", "ID", "PhotCalID",
24-
"FORMAT", "VERB"))
28+
BASE_PARAM_NAMES = {
29+
"wavelength_ref": "WavelengthRef",
30+
"wavelength_mean": "WavelengthMean",
31+
"wavelength_eff": "WavelengthEff",
32+
"wavelength_min": "WavelengthMin",
33+
"wavelength_max": "WavelengthMax",
34+
"width_eff": "WidthEff",
35+
"fwhm": "FWHM",
36+
"instrument": "Instrument",
37+
"facility": "Facility",
38+
"phot_system": "PhotSystem",
39+
"id": "ID",
40+
"phot_cal_id": "PhotCalID",
41+
"format": "FORMAT",
42+
"verb": "VERB",
43+
}
44+
45+
# Full mapping including the range variants, so a single dict lookup
46+
# resolves any user-facing kwarg.
47+
SVO_PARAM_NAMES = {
48+
**BASE_PARAM_NAMES,
49+
**{f"{k}_min": f"{v}_min"
50+
for k, v in BASE_PARAM_NAMES.items() if k in RANGE_BASES},
51+
**{f"{k}_max": f"{v}_max"
52+
for k, v in BASE_PARAM_NAMES.items() if k in RANGE_BASES},
53+
}
54+
55+
# Set of every valid public keyword name (used to validate **kwargs
56+
# passed to ``get_filter_metadata``).
57+
QUERY_PARAMETERS = set(SVO_PARAM_NAMES)
58+
59+
ALLOWED_QUERY_PARAMETERS = {
60+
"verb": {0, 1, 2},
61+
"format": {"metadata", None}
62+
}
63+
64+
65+
def to_svo_query(local_params):
66+
"""Translate a dict of public (snake_case) kwargs to the SVO native
67+
parameter names the server expects. ``None`` values are dropped so
68+
they aren't sent over the wire."""
69+
return {SVO_PARAM_NAMES[k]: v for k, v in local_params.items()
70+
if v is not None}
2571

2672

2773
class SvoFpsClass(BaseQuery):
@@ -31,21 +77,88 @@ class SvoFpsClass(BaseQuery):
3177
SVO_MAIN_URL = conf.base_url
3278
TIMEOUT = conf.timeout
3379

34-
def data_from_svo(self, query, *, cache=True, timeout=None,
35-
error_msg='No data found for requested query'):
80+
def data_from_svo(self,
81+
*,
82+
wavelength_ref_min=None,
83+
wavelength_ref_max=None,
84+
wavelength_mean_min=None,
85+
wavelength_mean_max=None,
86+
wavelength_eff_min=None,
87+
wavelength_eff_max=None,
88+
wavelength_min_min=None,
89+
wavelength_min_max=None,
90+
wavelength_max_min=None,
91+
wavelength_max_max=None,
92+
width_eff_min=None,
93+
width_eff_max=None,
94+
fwhm_min=None,
95+
fwhm_max=None,
96+
instrument=None,
97+
facility=None,
98+
phot_system=None,
99+
id=None,
100+
phot_cal_id=None,
101+
format=None,
102+
verb=2,
103+
cache=True, timeout=None,
104+
error_msg='No data found for requested query',
105+
):
36106
"""Get data in response to the query send to SVO FPS.
37107
This method is not generally intended for users, but it can be helpful
38108
if you want something very specific from the SVO FPS service.
39109
If you don't know what you're doing, try `get_filter_index`,
40110
`get_filter_list`, and `get_transmission_data` instead.
41111
112+
Description of search parameters can be found at
113+
https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice
114+
115+
42116
Parameters
43117
----------
44-
query : dict
45-
Used to create a HTTP query string i.e. send to SVO FPS to get data.
46-
In dictionary, specify keys as search parameters (str) and
47-
values as required. Description of search parameters can be found at
48-
https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice
118+
wavelength_ref_min : float, optional
119+
Min value for WavelengthRef parameter
120+
wavelength_ref_max : float, optional
121+
Max value for WavelengthRef parameter
122+
wavelength_mean_min : float, optional
123+
Min value for WavelengthMean parameter
124+
wavelength_mean_max : float, optional
125+
Max value for WavelengthMean parameter
126+
wavelength_eff_min : float, optional
127+
Min value for WavelengthEff parameter
128+
wavelength_eff_max : float, optional
129+
Max value for WavelengthEff parameter
130+
wavelength_min_min : float, optional
131+
Min value for WavelengthMin parameter
132+
wavelength_min_max : float, optional
133+
Max value for WavelengthMin parameter
134+
wavelength_max_min : float, optional
135+
Min value for WavelengthMax parameter
136+
wavelength_max_max : float, optional
137+
Max value for WavelengthMax parameter
138+
width_eff_min : float, optional
139+
Min value for WidthEff parameter
140+
width_eff_max : float, optional
141+
Max value for WidthEff parameter
142+
fwhm_min : float, optional
143+
Min value for FWHM parameter
144+
fwhm_max : float, optional
145+
Max value for FWHM parameter
146+
instrument : str, optional
147+
Instrument for filters (default is None). Leave empty if there are no instruments for specified facility
148+
facility : str, optional
149+
Facility for filters (default is None)
150+
phot_system : str, optional
151+
Photometric system for filters (default is None)
152+
id : str, optional
153+
Filter ID (default is None)
154+
phot_cal_id : str, optional
155+
Photometric calibration ID (default is None)
156+
format : str, optional
157+
Format of the output. Default includes all data, ``metadata`` includes only metadata.
158+
verb : 0, 1, or 2
159+
0: The resulting VOTable won't include the transmission curve or PARAM descriptions.
160+
1: The resulting VOTable won't include the transmission curve but it will include PARAM descriptions.
161+
2: The resulting VOTable will include the transmission curve and PARAM descriptions.
49162
error_msg : str, optional
50163
Error message to be shown in case no table element found in the
51164
responded VOTable. Use this to make error message verbose in context
@@ -59,14 +172,40 @@ def data_from_svo(self, query, *, cache=True, timeout=None,
59172
astropy.table.table.Table object
60173
Table containing data fetched from SVO (in response to query)
61174
"""
62-
bad_params = [param for param in query if param not in QUERY_PARAMETERS]
63-
if bad_params:
64-
raise InvalidQueryError(
65-
f"parameter{'s' if len(bad_params) > 1 else ''} "
66-
f"{', '.join(bad_params)} {'are' if len(bad_params) > 1 else 'is'} "
67-
f"invalid. For a description of valid query parameters see "
68-
"https://svo2.cab.inta-csic.es/theory/fps/index.php?mode=voservice"
69-
)
175+
176+
local_params = {
177+
'wavelength_ref_min': wavelength_ref_min,
178+
'wavelength_ref_max': wavelength_ref_max,
179+
'wavelength_mean_min': wavelength_mean_min,
180+
'wavelength_mean_max': wavelength_mean_max,
181+
'wavelength_eff_min': wavelength_eff_min,
182+
'wavelength_eff_max': wavelength_eff_max,
183+
'wavelength_min_min': wavelength_min_min,
184+
'wavelength_min_max': wavelength_min_max,
185+
'wavelength_max_min': wavelength_max_min,
186+
'wavelength_max_max': wavelength_max_max,
187+
'width_eff_min': width_eff_min,
188+
'width_eff_max': width_eff_max,
189+
'fwhm_min': fwhm_min,
190+
'fwhm_max': fwhm_max,
191+
'instrument': instrument,
192+
'facility': facility,
193+
'phot_system': phot_system,
194+
'id': id,
195+
'phot_cal_id': phot_cal_id,
196+
'format': format,
197+
'verb': verb,
198+
}
199+
200+
# check validity of query parameters with limited allowed values
201+
for key in ALLOWED_QUERY_PARAMETERS:
202+
if key in local_params and local_params[key] not in ALLOWED_QUERY_PARAMETERS[key]:
203+
raise InvalidQueryError(
204+
f"Invalid value for parameter {key}. Allowed values are "
205+
f"{ALLOWED_QUERY_PARAMETERS[key]}"
206+
)
207+
208+
query = to_svo_query(local_params)
70209
response = self._request("GET", self.SVO_MAIN_URL, params=query,
71210
timeout=timeout or self.TIMEOUT,
72211
cache=cache)
@@ -97,18 +236,21 @@ def get_filter_index(self, wavelength_eff_min, wavelength_eff_max, **kwargs):
97236
astropy.table.table.Table object
98237
Table containing data fetched from SVO (in response to query)
99238
"""
100-
query = {'WavelengthEff_min': wavelength_eff_min.to_value(u.angstrom),
101-
'WavelengthEff_max': wavelength_eff_max.to_value(u.angstrom)}
102239
error_msg = 'No filter found for requested Wavelength Effective range'
103240
try:
104-
return self.data_from_svo(query=query, error_msg=error_msg, **kwargs)
241+
return self.data_from_svo(
242+
wavelength_eff_min=wavelength_eff_min.to_value(u.angstrom),
243+
wavelength_eff_max=wavelength_eff_max.to_value(u.angstrom),
244+
error_msg=error_msg,
245+
**kwargs
246+
)
105247
except requests.ReadTimeout:
106248
raise TimeoutError(
107249
"Query did not finish fast enough. A smaller wavelength range might "
108250
"succeed. Try increasing the timeout limit if a large range is needed."
109251
)
110252

111-
def get_filter_metadata(self, filter_id, *, cache=True, timeout=None):
253+
def get_filter_metadata(self, filter_id, *, cache=True, timeout=None, **kwargs):
112254
"""Get metadata/parameters for the requested Filter ID from SVO
113255
114256
Parameters
@@ -122,13 +264,27 @@ def get_filter_metadata(self, filter_id, *, cache=True, timeout=None):
122264
See :ref:`caching documentation <astroquery_cache>`.
123265
timeout : int
124266
Timeout in seconds. If not specified, defaults to ``conf.timeout``.
267+
kwargs : dict
268+
Appended to the ``query`` dictionary sent to SVO. See the API
269+
documentation of `data_from_svo` for the valid parameter names.
125270
126271
Returns
127272
-------
128273
params : dict
129274
Dictionary of VOTable PARAM names and values.
130275
"""
131-
query = {'ID': filter_id, 'VERB': 0}
276+
local_params = {'id': filter_id, 'verb': 0}
277+
local_params.update(kwargs)
278+
279+
bad_params = [param for param in local_params if param not in QUERY_PARAMETERS]
280+
if bad_params:
281+
raise InvalidQueryError(
282+
f"parameter{'s' if len(bad_params) > 1 else ''} "
283+
f"{', '.join(bad_params)} {'are' if len(bad_params) > 1 else 'is'} "
284+
f"invalid. For a description of valid query parameters see the docstring for SvoFps.data_from_svo"
285+
)
286+
287+
query = to_svo_query(local_params)
132288
response = self._request("GET", self.SVO_MAIN_URL, params=query,
133289
timeout=timeout or self.TIMEOUT,
134290
cache=cache)
@@ -143,6 +299,47 @@ def get_filter_metadata(self, filter_id, *, cache=True, timeout=None):
143299
params[param.name] = param.value
144300
return params
145301

302+
def get_zeropoint(self, filter_id, *, mag_system='Vega', **kwargs):
303+
"""
304+
Get the zero point for a specififed filter in a specified system.
305+
306+
This is a highly-specific downselection of the metadata returned by
307+
`get_filter_metadata`; the full metadata includes the zero point with
308+
``Vega`` as the default system.
309+
310+
Parameters
311+
----------
312+
filter_id : str
313+
Filter ID in the format SVO specifies it: 'facilty/instrument.filter'.
314+
This is returned by `get_filter_list` and `get_filter_index` as the
315+
``filterID`` column.
316+
mag_system : 'Vega' (default), 'AB', or 'ST'
317+
The magnitude system for which to return the zero point.
318+
kwargs : dict
319+
Appended to the ``query`` dictionary sent to SVO. See the API
320+
documentation of `data_from_svo` for the valid parameter names.
321+
322+
Examples
323+
--------
324+
>>> from astroquery.svo_fps import SvoFps # doctest: +REMOTE_DATA
325+
>>> SvoFps.get_zeropoint(filter_id='2MASS/2MASS.J', mag_system='AB') # doctest: +REMOTE_DATA
326+
{'MagSys': 'AB',
327+
'ZeroPoint': <Quantity 3631. Jy>,
328+
'ZeroPointUnit': 'Jy',
329+
'ZeroPointType': 'Pogson'}
330+
"""
331+
if mag_system not in ['Vega', 'AB', 'ST']:
332+
raise InvalidQueryError("Invalid magnitude system. Allowed values are 'Vega', 'AB', and 'ST'.")
333+
334+
metadata = self.get_filter_metadata(filter_id=filter_id,
335+
phot_cal_id=f'{filter_id}/{mag_system}', **kwargs)
336+
337+
zeropoint_keys = ['MagSys', 'ZeroPoint', 'ZeroPointUnit', 'ZeroPointType']
338+
339+
zp = {key: metadata[key] for key in zeropoint_keys if key in metadata}
340+
341+
return zp
342+
146343
def get_transmission_data(self, filter_id, **kwargs):
147344
"""Get transmission data for the requested Filter ID from SVO
148345
@@ -160,9 +357,8 @@ def get_transmission_data(self, filter_id, **kwargs):
160357
astropy.table.table.Table object
161358
Table containing data fetched from SVO (in response to query)
162359
"""
163-
query = {'ID': filter_id}
164360
error_msg = 'No filter found for requested Filter ID'
165-
return self.data_from_svo(query=query, error_msg=error_msg, **kwargs)
361+
return self.data_from_svo(id=filter_id, error_msg=error_msg, **kwargs)
166362

167363
def get_filter_list(self, facility, *, instrument=None, **kwargs):
168364
"""Get filters data for requested facilty and instrument from SVO
@@ -182,10 +378,13 @@ def get_filter_list(self, facility, *, instrument=None, **kwargs):
182378
astropy.table.table.Table object
183379
Table containing data fetched from SVO (in response to query)
184380
"""
185-
query = {'Facility': facility,
186-
'Instrument': instrument}
187381
error_msg = 'No filter found for requested Facilty (and Instrument)'
188-
return self.data_from_svo(query=query, error_msg=error_msg, **kwargs)
382+
return self.data_from_svo(
383+
facility=facility,
384+
instrument=instrument,
385+
error_msg=error_msg,
386+
**kwargs
387+
)
189388

190389

191390
SvoFps = SvoFpsClass()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0"?>
2+
<VOTABLE version="1.1" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3+
<INFO name="QUERY_STATUS" value="OK"/>
4+
<RESOURCE type="results">
5+
<TABLE utype="photdm:PhotometryFilter.transmissionCurve.spectrum">
6+
<PARAM name="FilterProfileService" value="ivo://svo/fps" ucd="meta.ref.ivorn" utype="PhotometryFilter.fpsIdentifier" datatype="char" arraysize="*"/>
7+
<PARAM name="filterID" value="2MASS/2MASS.H" ucd="meta.id" utype="photdm:PhotometryFilter.identifier" datatype="char" arraysize="*"/>
8+
<PARAM name="WavelengthUnit" value="Angstrom" ucd="meta.unit" utype="PhotometryFilter.SpectralAxis.unit" datatype="char" arraysize="*"/>
9+
<PARAM name="Description" value="2MASS H" ucd="meta.note" utype="photdm:PhotometryFilter.description" datatype="char" arraysize="*"/>
10+
<PARAM name="WavelengthEff" value="16620" unit="Angstrom" ucd="em.wl.effective" datatype="float" >
11+
<DESCRIPTION>Manually specified. See reference</DESCRIPTION>
12+
</PARAM>
13+
<PARAM name="ZeroPoint" value="1024" unit="Jy" ucd="phot.flux.density" utype="photdm:PhotCal.ZeroPoint.Flux.value" datatype="float" />
14+
<PARAM name="PhotCalID" value="2MASS/2MASS.H/Vega" ucd="meta.id" utype="photdm:PhotCal.identifier" datatype="char" arraysize="*"/>
15+
<PARAM name="MagSys" value="Vega" ucd="meta.code" utype="photdm:PhotCal.MagnitudeSystem.type" datatype="char" arraysize="*"/>
16+
<PARAM name="ZeroPointUnit" value="Jy" ucd="meta.unit" utype="photdm:PhotCal.ZeroPoint.Flux.unit" datatype="char" arraysize="*"/>
17+
<PARAM name="ZeroPointType" value="Pogson" ucd="meta.code" utype="photdm:PhotCal.ZeroPoint.type" datatype="char" arraysize="*"/>
18+
<DATA>
19+
<TABLEDATA>
20+
</TABLEDATA>
21+
</DATA>
22+
</TABLE>
23+
</RESOURCE>
24+
</VOTABLE>

0 commit comments

Comments
 (0)