From 3a941c24673b275a34310eac6fe6d505e5b646bf Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 11:26:06 -0500 Subject: [PATCH 1/7] add get_zeropoint and more flexible metadata querying to SVO FPS --- astroquery/svo_fps/core.py | 63 +++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/astroquery/svo_fps/core.py b/astroquery/svo_fps/core.py index e8686ada8c..fe7ad9c554 100644 --- a/astroquery/svo_fps/core.py +++ b/astroquery/svo_fps/core.py @@ -108,7 +108,7 @@ def get_filter_index(self, wavelength_eff_min, wavelength_eff_max, **kwargs): "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): """Get metadata/parameters for the requested Filter ID from SVO Parameters @@ -122,6 +122,8 @@ def get_filter_metadata(self, filter_id, *, cache=True, timeout=None): See :ref:`caching documentation `. timeout : int Timeout in seconds. If not specified, defaults to ``conf.timeout``. + kwargs : dict + Appended to the ``query`` dictionary sent to SVO. Returns ------- @@ -129,6 +131,17 @@ def get_filter_metadata(self, filter_id, *, cache=True, timeout=None): 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" + ) + response = self._request("GET", self.SVO_MAIN_URL, params=query, timeout=timeout or self.TIMEOUT, cache=cache) @@ -143,6 +156,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): + """ + 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. + kwargs : dict + Appended to the ``query`` dictionary sent to SVO. + + Examples + -------- + >>> from astroquery.svo_fps import SvoFps + >>> SvoFps.get_zeropoint(filter_id='2MASS/2MASS.J', mag_system='AB') + {'MagSys': 'AB', + 'ZeroPoint': , + 'ZeroPointUnit': 'Jy', + 'ZeroPointType': 'Pogson'} + + >>> SvoFps.get_filter_metadata(filter_id='2MASS/2MASS.J', PhotCalID='2MASS/2MASS.J/AB') + {'FilterProfileService': 'ivo://svo/fps', + 'filterID': '2MASS/2MASS.J', + ... + 'PhotCalID': '2MASS/2MASS.J/AB', + 'MagSys': 'AB', + 'ZeroPoint': , + '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 From bed922fdb1348eeacb2aa4e2a2cb7508db538c33 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 11:33:20 -0500 Subject: [PATCH 2/7] comma, whitespace --- astroquery/svo_fps/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/astroquery/svo_fps/core.py b/astroquery/svo_fps/core.py index fe7ad9c554..2cbc2e24e4 100644 --- a/astroquery/svo_fps/core.py +++ b/astroquery/svo_fps/core.py @@ -183,7 +183,7 @@ def get_zeropoint(self, filter_id, mag_system='Vega', **kwargs): 'ZeroPoint': , 'ZeroPointUnit': 'Jy', 'ZeroPointType': 'Pogson'} - + >>> SvoFps.get_filter_metadata(filter_id='2MASS/2MASS.J', PhotCalID='2MASS/2MASS.J/AB') {'FilterProfileService': 'ivo://svo/fps', 'filterID': '2MASS/2MASS.J', @@ -193,13 +193,13 @@ def get_zeropoint(self, filter_id, mag_system='Vega', **kwargs): 'ZeroPoint': , 'ZeroPointUnit': 'Jy', 'ZeroPointType': 'Pogson'} - + """ metadata = self.get_filter_metadata(filter_id=filter_id, - PhotCalID=f'{filter_id}/{mag_system}',**kwargs) + 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 From 21ace8bea26ffb056356ff302c4650890da6ab27 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 11:55:07 -0500 Subject: [PATCH 3/7] add tests --- astroquery/svo_fps/tests/test_svo_fps.py | 21 +++++++++++++++++-- .../svo_fps/tests/test_svo_fps_remote.py | 17 +++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/astroquery/svo_fps/tests/test_svo_fps.py b/astroquery/svo_fps/tests/test_svo_fps.py index d8c8c36047..5b87f9d379 100644 --- a/astroquery/svo_fps/tests/test_svo_fps.py +++ b/astroquery/svo_fps/tests/test_svo_fps.py @@ -9,12 +9,14 @@ 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): @@ -35,8 +37,12 @@ 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']) + filename = data_path(DATA_FILES['transmission_data']) elif 'Facility' in params and (params['Facility'] == TEST_FACILITY and params['Instrument'] == TEST_INSTRUMENT): filename = data_path(DATA_FILES['filter_list']) @@ -84,6 +90,17 @@ 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): diff --git a/astroquery/svo_fps/tests/test_svo_fps_remote.py b/astroquery/svo_fps/tests/test_svo_fps_remote.py index e195318dc0..cab0bdfc13 100644 --- a/astroquery/svo_fps/tests/test_svo_fps_remote.py +++ b/astroquery/svo_fps/tests/test_svo_fps_remote.py @@ -40,6 +40,23 @@ def test_get_filter_list(self, test_facility, test_instrument): # Check if column for Filter ID (named 'filterID') exists in table assert 'filterID' in table.colnames + @pytest.mark.parametrize('test_filter_id, mag_system, expected_zp_jy', [ + ('2MASS/2MASS.J', 'Vega', 1594.0), + ('2MASS/2MASS.J', 'AB', 3631.0), + ]) + def test_get_zeropoint(self, test_filter_id, mag_system, expected_zp_jy): + zp = SvoFps.get_zeropoint(test_filter_id, mag_system=mag_system) + # Check all expected keys are present + assert 'ZeroPoint' in zp + assert 'MagSys' in zp + assert 'ZeroPointType' in zp + assert 'ZeroPointUnit' in zp + # Check the magnitude system matches what was requested + assert zp['MagSys'] == mag_system + # Check zero point has the right unit and an approximately correct value + assert zp['ZeroPoint'].unit == u.Jy + assert abs(zp['ZeroPoint'].value - expected_zp_jy) < 10.0 + def test_query_parameter_names(self): # Checks if `QUERY_PARAMETERS` is up to date. query = {"FORMAT": "metadata"} From ca29c111c8eb374774da4eef77fc820bc5c7c8c1 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 11:55:36 -0500 Subject: [PATCH 4/7] add missing test data --- .../svo_fps_PhotCalID=2MASS.2MASS.H.Vega.xml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 astroquery/svo_fps/tests/data/svo_fps_PhotCalID=2MASS.2MASS.H.Vega.xml diff --git a/astroquery/svo_fps/tests/data/svo_fps_PhotCalID=2MASS.2MASS.H.Vega.xml b/astroquery/svo_fps/tests/data/svo_fps_PhotCalID=2MASS.2MASS.H.Vega.xml new file mode 100644 index 0000000000..cc7dab20af --- /dev/null +++ b/astroquery/svo_fps/tests/data/svo_fps_PhotCalID=2MASS.2MASS.H.Vega.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + Manually specified. See reference + + + + + + + + + + +
+
+
From fdbdeb4ca77887b51a43dd2292c12fd23a65b452 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 11:57:06 -0500 Subject: [PATCH 5/7] revert an unintended change --- astroquery/svo_fps/tests/test_svo_fps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroquery/svo_fps/tests/test_svo_fps.py b/astroquery/svo_fps/tests/test_svo_fps.py index 5b87f9d379..9d8b289793 100644 --- a/astroquery/svo_fps/tests/test_svo_fps.py +++ b/astroquery/svo_fps/tests/test_svo_fps.py @@ -42,7 +42,7 @@ def get_mockreturn(method, url, params=None, timeout=10, cache=None, **kwargs): 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['transmission_data']) + filename = data_path(DATA_FILES['filter_index']) elif 'Facility' in params and (params['Facility'] == TEST_FACILITY and params['Instrument'] == TEST_INSTRUMENT): filename = data_path(DATA_FILES['filter_list']) From cae9fd4fa611d7375987e52f1d916d7386bf952d Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 12:12:26 -0500 Subject: [PATCH 6/7] add doctest remote data tags --- astroquery/svo_fps/core.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/astroquery/svo_fps/core.py b/astroquery/svo_fps/core.py index 2cbc2e24e4..b2717b51a1 100644 --- a/astroquery/svo_fps/core.py +++ b/astroquery/svo_fps/core.py @@ -177,14 +177,13 @@ def get_zeropoint(self, filter_id, mag_system='Vega', **kwargs): Examples -------- - >>> from astroquery.svo_fps import SvoFps - >>> SvoFps.get_zeropoint(filter_id='2MASS/2MASS.J', mag_system='AB') + >>> 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': , 'ZeroPointUnit': 'Jy', 'ZeroPointType': 'Pogson'} - - >>> SvoFps.get_filter_metadata(filter_id='2MASS/2MASS.J', PhotCalID='2MASS/2MASS.J/AB') + >>> 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', ... From bb8206123dff7816e9d9f779f72abd596c31fbcf Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Tue, 3 Mar 2026 12:22:12 -0500 Subject: [PATCH 7/7] add changelog entry --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9be6958c7a..c13f80e842 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -48,6 +48,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 ^^^^^^^