From 46b4362ebde9948e9147de98dada77a664f06e68 Mon Sep 17 00:00:00 2001 From: Somia FLORET Date: Thu, 23 Nov 2023 15:05:43 +0100 Subject: [PATCH] add epoch propagation --- docs/mivot/index.rst | 25 +++--- pyvo/mivot/features/epoch_propagation.py | 89 ++++++++++++++++++++++ pyvo/mivot/tests/test_epoch_propagation.py | 58 ++++++++++++++ pyvo/mivot/version_checker.py | 1 - pyvo/mivot/viewer/mivot_class.py | 77 +++++++------------ 5 files changed, 185 insertions(+), 65 deletions(-) create mode 100644 pyvo/mivot/features/epoch_propagation.py create mode 100644 pyvo/mivot/tests/test_epoch_propagation.py diff --git a/docs/mivot/index.rst b/docs/mivot/index.rst index 99c31c41a..f52338653 100644 --- a/docs/mivot/index.rst +++ b/docs/mivot/index.rst @@ -30,18 +30,16 @@ The API allows you to obtain a model view on the last read data row, this usage .. doctest-remote-data:: >>> import pyvo - >>> import os >>> from astropy.utils.data import get_pkg_data_filename + >>> import astropy.units as u >>> from pyvo.mivot.viewer.model_viewer import ModelViewer - >>> from pyvo.mivot.version_checker import check_astropy_version >>> from pyvo.utils.prototype import activate_features >>> activate_features('MIVOT') >>> votable = get_pkg_data_filename("data/simple-annotation-votable.xml", package="pyvo.mivot.tests") - >>> if check_astropy_version(): # doctest: +SKIP - ... m_viewer = ModelViewer(votable) - ... row_view = m_viewer.get_next_row_view() - ... print(row_view.longitude.value) - ... print(row_view.Coordinate_coosys.PhysicalCoordSys_frame.spaceRefFrame.value) + >>> m_viewer = ModelViewer(votable) # doctest: +SKIP + >>> row_view = m_viewer.get_next_row_view() # doctest: +SKIP + >>> print(row_view.longitude.value) # doctest: +SKIP + >>> print(row_view.Coordinate_coosys.PhysicalCoordSys_frame.spaceRefFrame.value) # doctest: +SKIP 10.0 ICRS @@ -52,13 +50,12 @@ the dmroles of the MIVOT elements. There is no checking against the model struct Example for epoch propagation ----------------------------- .. doctest-remote-data:: - >>> if check_astropy_version(): - ... with ModelViewer(votable) as m_viewer: # doctest: +SKIP - ... row_view = m_viewer.get_next_row_view() - ... past_ra, past_dec = row_view.apply_space_motion(dt=-42 * u.year) - ... future_ra, future_dec = row_view.apply_space_motion(dt=2 * u.year) - ... print("past_ra, past_dec :", row_view.apply_space_motion(dt=-42 * u.year)) - ... print("future_ra, future_dec :", row_view.apply_space_motion(dt=2 * u.year)) + >>> with ModelViewer(votable) as m_viewer: # doctest: +SKIP + ... row_view = m_viewer.get_next_row_view() + ... past_ra, past_dec = row_view.epoch_propagation.apply_space_motion(dt=-42 * u.year) + ... future_ra, future_dec = row_view.epoch_propagation.apply_space_motion(dt=2 * u.year) + ... print("past_ra, past_dec :", row_view.epoch_propagation.apply_space_motion(dt=-42 * u.year)) + ... print("future_ra, future_dec :", row_view.epoch_propagation.apply_space_motion(dt=2 * u.year)) past_ra, past_dec : (, ) future_ra, future_dec : (, ) diff --git a/pyvo/mivot/features/epoch_propagation.py b/pyvo/mivot/features/epoch_propagation.py new file mode 100644 index 000000000..d8bcd7e81 --- /dev/null +++ b/pyvo/mivot/features/epoch_propagation.py @@ -0,0 +1,89 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +Implementation of the EpochPropagation in the MIVOT class with +astropy.coordinates.sky_coordinate.SkyCoord and +astropy.coordinates.sky_coordinate.SkyCoord.apply_space_motion. +""" +from astropy.coordinates import SkyCoord +import astropy.units as u +from pyvo.utils.prototype import prototype_feature + + +@prototype_feature('MIVOT') +class EpochPropagation: + """ + This class allows computing the position of a SkyCoord object at a new time dt. + It builds a SkyCoord object from the data row of the MivotClass, and then applies the space motion. + """ + fields = ["longitude", "latitude", "pm_longitude", "pm_latitude"] + + def __init__(self, name): + self.REFERENCE = {} + self.name = name + + @property + def ref_long(self): + return self.REFERENCE.get("longitude") + + @ref_long.setter + def ref_long(self, value): + self.REFERENCE["longitude"] = value + + @property + def ref_lat(self): + return self.REFERENCE.get("latitude") + + @ref_lat.setter + def ref_lat(self, value): + self.REFERENCE["latitude"] = value + + @property + def ref_pm_long(self): + return self.REFERENCE.get("pm_longitude") + + @ref_pm_long.setter + def ref_pm_long(self, value): + self.REFERENCE["pm_longitude"] = value + + @property + def ref_pm_lat(self): + return self.REFERENCE.get("pm_latitude") + + @ref_pm_lat.setter + def ref_pm_lat(self, value): + self.REFERENCE["pm_latitude"] = value + + def SkyCoordinate(self): + """ + Returns a SkyCoord object from the REFERENCE of the XML object. + """ + if self.REFERENCE["frame"] == ('icrs' or 'fk5' or 'fk4'): + return SkyCoord(distance=(self.REFERENCE["parallax"] / 4) * u.pc, + radial_velocity=self.REFERENCE["radial_velocity"] * u.km / u.s, + ra=self.REFERENCE["longitude"] * u.degree, + dec=self.REFERENCE["latitude"] * u.degree, + pm_ra_cosdec=self.REFERENCE["pm_longitude"] * u.mas / u.yr, + pm_dec=self.REFERENCE["pm_latitude"] * u.mas / u.yr, + frame=self.REFERENCE["frame"], + obstime=self.REFERENCE["epoch"]) + + elif self.REFERENCE["frame"] == 'galatic': + return SkyCoord(distance=self.REFERENCE["parallax"] * u.pc, + l=self.REFERENCE["longitude"] * u.degree, + b=self.REFERENCE["latitude"] * u.degree, + pm_l_cosb=self.REFERENCE["pm_longitude"] * u.mas / u.yr, + pm_b=self.REFERENCE["pm_latitude"] * u.mas / u.yr, + frame=self.REFERENCE["frame"], + obstime=self.REFERENCE["epoch"]) + + def apply_space_motion(self, dt): + """ + Returns ra and dec of a SkyCoord object by computing the position to a new time dt. + + Parameters + ---------- + dt : float + Time in years. + """ + retour = self.SkyCoordinate().apply_space_motion(dt=dt) + return retour.ra, retour.dec diff --git a/pyvo/mivot/tests/test_epoch_propagation.py b/pyvo/mivot/tests/test_epoch_propagation.py new file mode 100644 index 000000000..30e203e5d --- /dev/null +++ b/pyvo/mivot/tests/test_epoch_propagation.py @@ -0,0 +1,58 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +Test for mivot.seekers.annotation_seeker.py +""" +import os + +import pytest +from astropy.coordinates import SkyCoord +import astropy.units as u +from astropy.time import Time + +from pyvo.mivot.version_checker import check_astropy_version +from pyvo.mivot.viewer.model_viewer import ModelViewer +from pyvo.utils import activate_features + +activate_features('MIVOT') + + +def test_epoch_propagation(m_viewer): + if check_astropy_version() is False: + pytest.skip("MIVOT test skipped because of the astropy version.") + + row_view = m_viewer.get_next_row_view() + epoch_propagation = row_view.EpochPropagation + sky_coord_to_compare = (SkyCoord(distance=(row_view.parallax.value / 4) * u.pc, + radial_velocity=row_view.radialVelocity.value * u.km / u.s, + ra=row_view.longitude.value * u.degree, + dec=row_view.latitude.value * u.degree, + pm_ra_cosdec=row_view.latitude.value * u.mas / u.yr, + pm_dec=row_view.pmLatitude.value * u.mas / u.yr, + frame=row_view.Coordinate_coosys + .PhysicalCoordSys_frame.spaceRefFrame.value.lower(), + obstime=Time(row_view.epoch.value, format="decimalyear"))) + + assert sky_coord_to_compare == epoch_propagation.SkyCoordinate() + assert ((sky_coord_to_compare.apply_space_motion(dt=-42 * u.year).ra, + sky_coord_to_compare.apply_space_motion(dt=-42 * u.year).dec) + == epoch_propagation.apply_space_motion(dt=-42 * u.year)) + assert epoch_propagation.ref_long == 10.0 + assert epoch_propagation.ref_lat == 10.0 + assert epoch_propagation.ref_pm_long == 10.0 + assert epoch_propagation.ref_pm_lat == -20.0 + +@pytest.fixture +def m_viewer(data_path): + if check_astropy_version() is False: + pytest.skip("MIVOT test skipped because of the astropy version.") + votable = os.path.join(data_path, "data/simple-annotation-votable.xml") + return ModelViewer(votable_path=votable) + + +@pytest.fixture +def data_path(): + return os.path.dirname(os.path.realpath(__file__)) + + +if __name__ == '__main__': + pytest.main() diff --git a/pyvo/mivot/version_checker.py b/pyvo/mivot/version_checker.py index 82f3c2d8e..063cbd75c 100644 --- a/pyvo/mivot/version_checker.py +++ b/pyvo/mivot/version_checker.py @@ -4,7 +4,6 @@ def check_astropy_version(): """ Check if the installed version of astropy is compatible with MIVOT. - Check if defusedxml is installed. """ if not astropy_version.version: return False diff --git a/pyvo/mivot/viewer/mivot_class.py b/pyvo/mivot/viewer/mivot_class.py index 2a0068227..8069f2881 100644 --- a/pyvo/mivot/viewer/mivot_class.py +++ b/pyvo/mivot/viewer/mivot_class.py @@ -2,10 +2,9 @@ """ MivotClass keep as an attribute dictionary __dict__ all XML objects. """ -from astropy.coordinates import SkyCoord -import astropy.units as u from astropy.time import Time +from pyvo.mivot.features.epoch_propagation import EpochPropagation from pyvo.utils.prototype import prototype_feature @@ -19,6 +18,7 @@ class MivotClass: "key" : "value" means key is an element of ATTRIBUTE "key" : [] means key is the dmtype of a COLLECTION """ + EpochPropagation = EpochPropagation("EpochPropagation") REFERENCE = {} def __init__(self, **kwargs): @@ -42,63 +42,40 @@ def __init__(self, **kwargs): else: if isinstance(value, dict) and self._is_leaf(**value): self.__dict__[self._remove_model_name(key)] = MivotClass(**value) - key_low = key.lower() if self.dmtype == "EpochPosition": - if ("longitude" or "ra") in key_low: - if "pm" not in key_low and value["unit"] == "deg": - MivotClass.REFERENCE["longitude"] = value['value'] - elif "pm" in key_low and value["unit"] == "mas/year": - MivotClass.REFERENCE["pm_longitude"] = value['value'] - if ("latitude" or "dec") in key_low: - if "pm" not in key_low and value["unit"] == "deg": - MivotClass.REFERENCE["latitude"] = value['value'] - elif "pm" in key_low and value["unit"] == "mas/year": - MivotClass.REFERENCE["pm_latitude"] = value['value'] - if ("radial" or "velocity") in key_low and value["unit"] == "km/s": - MivotClass.REFERENCE["radial_velocity"] = value["value"] - if "parallax" in key_low and value["unit"] == ("mas" or "pc"): - MivotClass.REFERENCE["parallax"] = value["value"] - if "epoch" in key_low and value["unit"] == "year": - MivotClass.REFERENCE["epoch"] = Time(value["value"], format="decimalyear") - if "frame" in key_low and "string" in value["dmtype"]: - MivotClass.REFERENCE["frame"] = value["value"].lower() + self._fill_epoch_propagation(key.lower(), value) + if "frame" in key.lower() and "string" in value["dmtype"]: + self.EpochPropagation.REFERENCE["frame"] = value["value"].lower() else: self.__dict__[self._remove_model_name(key)] = self._remove_model_name(value) - def SkyCoordinate(self): + def _fill_epoch_propagation(self, key_low, value): """ - Returns a SkyCoord object from the REFERENCE of the XML object. - """ - if MivotClass.REFERENCE["frame"] == ('icrs' or 'fk5' or 'fk4'): - return SkyCoord(distance=(MivotClass.REFERENCE["parallax"] / 4) * u.pc, - radial_velocity=MivotClass.REFERENCE["radial_velocity"] * u.km / u.s, - ra=MivotClass.REFERENCE["longitude"] * u.degree, - dec=MivotClass.REFERENCE["latitude"] * u.degree, - pm_ra_cosdec=MivotClass.REFERENCE["pm_longitude"] * u.mas / u.yr, - pm_dec=MivotClass.REFERENCE["pm_latitude"] * u.mas / u.yr, - frame=MivotClass.REFERENCE["frame"], - obstime=MivotClass.REFERENCE["epoch"]) - - elif MivotClass.REFERENCE["frame"] == 'galatic': - return SkyCoord(l=MivotClass.REFERENCE["longitude"] * u.degree, - b=MivotClass.REFERENCE["latitude"] * u.degree, - distance=MivotClass.REFERENCE["parallax"] * u.pc, - pm_l_cosb=MivotClass.REFERENCE["pm_longitude"] * u.mas / u.yr, - pm_b=MivotClass.REFERENCE["pm_latitude"] * u.mas / u.yr, - frame=MivotClass.REFERENCE["frame"], - obstime=MivotClass.REFERENCE["epoch"]) - - def apply_space_motion(self, dt): - """ - Returns ra and dec of a SkyCoord object by computing the position to a new time dt. + Fill the REFERENCE dictionary of the EpochPropagation object. Parameters ---------- - dt : float - Time in years. + key_low : str + The key of the dictionary in lowercase. + value : dict + The value of the dictionary. """ - retour = self.SkyCoordinate().apply_space_motion(dt=dt) - return retour.ra, retour.dec + if ("longitude" or "ra") in key_low: + if "pm" not in key_low and value["unit"] == "deg": + self.EpochPropagation.REFERENCE["longitude"] = value['value'] + elif "pm" in key_low and value["unit"] == "mas/year": + self.EpochPropagation.REFERENCE["pm_longitude"] = value['value'] + if ("latitude" or "dec") in key_low: + if "pm" not in key_low and value["unit"] == "deg": + self.EpochPropagation.REFERENCE["latitude"] = value['value'] + elif "pm" in key_low and value["unit"] == "mas/year": + self.EpochPropagation.REFERENCE["pm_latitude"] = value['value'] + if ("radial" or "velocity") in key_low and value["unit"] == "km/s": + self.EpochPropagation.REFERENCE["radial_velocity"] = value["value"] + if "parallax" in key_low and value["unit"] == ("mas" or "pc"): + self.EpochPropagation.REFERENCE["parallax"] = value["value"] + if "epoch" in key_low and value["unit"] == "year": + self.EpochPropagation.REFERENCE["epoch"] = Time(value["value"], format="decimalyear") def _remove_model_name(self, value, role_instance=False): """