Source code for ATK.structures.Target

import copy
from dataclasses import dataclass, field

import astropy.units as u
import numpy as np
from astropy.coordinates import SkyCoord
from astropy.time import Time
from astropy.units import Quantity

from ..utilities.docstrings import get_docstring


def skycoord_equality_check(coords, other):
    same_position = False
    if coords.separation(other) < 1 * u.arcsec:
        same_position = True

    if hasattr(other, "obstime") and other.obstime is not None:
        same_time = abs(coords.obstime - other.obstime) < 1e-9 * u.day

        return same_time and same_position

    return same_position


[docs] @dataclass class Target: #: Initial (i.e. uncorrected) coordinates of target source. #: #: If an ``identifier`` is provided, a :class:`~astropy.coordinates.SkyCoord` is generated in the frame and epoch of the chosen astrometric backend survey. Otherwise the input :class:`~astropy.coordinates.SkyCoord` is used directly. initial_coords: SkyCoord #: Coordinates of target source, corrected for proper motion. Updated with each stage of astrometric correction. coords: SkyCoord #: Radius of search in which the Target is being utilised. radius: Quantity | None = None #: Unique identifier from a supported astrometric backend survey (if provided, otherwise ``None``). #: #: (e.g. a Gaia Source ID). identifier: int | None = None #: Survey from which ``identifier`` originates (if one was provided, otherwise ``None``). survey: str | None = None #: Degree of proper motion correction that the :class:`~ATK.Models.Target` can support. #: #: - ``'full'`` = complete 3-dimensional projection on the sky. #: - ``'partial'`` = 2-dimensional plane projection. #: - ``'none'`` = no correction. correction: str = "none" _key: str = field(init=False) _aliases: set[str] = field(default_factory=set, init=False) def __repr__(self): from ..io.struct_stdout import format_target return f"<{format_target(self)} {type(self).__name__}>" def __post_init__(self): id_key = f"id:{self.identifier}" coord_key = f"coord:{self.initial_coords.ra.deg:.8f},{self.initial_coords.dec.deg:.8f}" if self.identifier is not None: self._key = id_key self._aliases.add(id_key) else: self._key = coord_key self._aliases.add(coord_key) def __eq__(self, other): if isinstance(other, SkyCoord): init_coords_match = skycoord_equality_check(self.initial_coords, other) else: init_coords_match = skycoord_equality_check(self.initial_coords, other.initial_coords) identifier_match = self.identifier == other.identifier survey_match = self.survey == other.survey correction_match = self.correction == other.correction matches = [init_coords_match, identifier_match, survey_match, correction_match] return all(matches) return init_coords_match @property def _frame(self): return self.coords.frame.name @property def _epoch(self): return self.coords.obstime.fits @property def _initial_frame(self): return self.initial_coords.frame.name @property def _initial_epoch(self): return self.initial_coords.obstime.fits
[docs] def show(self, show_types: bool = False, show_all: bool = False, **kwargs) -> None: from ..io.struct_stdout import pprint_structure pprint_structure(self, show_types, show_all, **kwargs)
show.__doc__ = get_docstring("show")
[docs] @classmethod def from_id(cls, id: int, survey="gaia"): """ Construct a :class:`~ATK.Models.Target` using a unique identifier from a supported astrometric backend survey. The resulting :class:`~ATK.Models.Target` is initialised using the coordinate frame and reference epoch of the selected ``survey``. All astrometric parameters (e.g. position and proper motion) are retrieved from the survey’s `VizieR <https://vizier.cds.unistra.fr/>`_ catalogue. Parameters ---------- id: int Unique source identifier from a supported astrometric backend survey. E.g. a Gaia DR3 Source ID survey: {'gaia'}, optional Survey to use as an astrometric backend. Currently only Gaia DR3 (``gaia``) is supported. Returns ------- ``Self`` """ if survey == "gaia": from ..utilities.coordinates import get_gaia_target return get_gaia_target(id) else: raise NotImplementedError("Other astronometric surveys will be added at a later date.")
[docs] @classmethod def from_coord(cls, position: SkyCoord): """ Construct a :class:`~ATK.Models.Target` from a position on the sky. The provided :class:`~astropy.coordinates.SkyCoord` is transformed to the ICRS frame and used to initialise the target. If no observation epoch is defined (``obstime``), an epoch of J2000 is assumed. Astrometric correction (i.e. propagation between epochs) is only possible if the input coordinate includes proper motion information (and optionally distance). If these are not provided, no correction can be applied. Parameters ---------- position : :class:`~astropy.coordinates.SkyCoord` Input sky coordinate defining the target position. May optionally include proper motion and distance information. Returns ------- ``Self`` """ # if no epoch was set, assume J2000 if not position.obstime: j2000 = Time("2000-01-01T00:00:00.000", format="fits") position = SkyCoord(position.data, frame=position.frame, obstime=j2000) icrs_pos = position.transform_to("icrs") return cls(copy.deepcopy(icrs_pos), copy.deepcopy(icrs_pos), None, None, None, "none")