Coverage for python/astro_metadata_translator/translators/helpers.py: 18%
56 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-01 01:03 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-01 01:03 -0700
1# This file is part of astro_metadata_translator.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the LICENSE file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12"""Generically useful translation helpers which translation classes
13can use.
15They are written as free functions. Some of them are written
16as if they are methods of `MetadataTranslator`, allowing them to be attached
17to translator classes that need them. These methods have full access to
18the translator methods.
20Other functions are pure helpers that can be imported and used to help
21translation classes without using `MetadataTranslator` properties.
23"""
25from __future__ import annotations
27__all__ = (
28 "to_location_via_telescope_name",
29 "is_non_science",
30 "tracking_from_degree_headers",
31 "altitude_from_zenith_distance",
32)
34import logging
35from typing import TYPE_CHECKING, Optional, Sequence, Set, Tuple
37import astropy.units as u
38from astropy.coordinates import AltAz, EarthLocation, SkyCoord
40if TYPE_CHECKING: 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never true
41 import astropy.units
43 from ..translator import MetadataTranslator
45log = logging.getLogger(__name__)
48def to_location_via_telescope_name(self: MetadataTranslator) -> EarthLocation:
49 """Calculate the observatory location via the telescope name.
51 Returns
52 -------
53 loc : `astropy.coordinates.EarthLocation`
54 Location of the observatory.
55 """
56 return EarthLocation.of_site(self.to_telescope())
59def is_non_science(self: MetadataTranslator) -> None:
60 """Raise an exception if this is a science observation.
62 Raises
63 ------
64 KeyError
65 Is a science observation.
66 """
67 if self.to_observation_type() == "science":
68 raise KeyError(f"{self._log_prefix}: Header represents science observation and can not default")
69 return
72def altitude_from_zenith_distance(zd: astropy.units.Quantity) -> astropy.units.Quantity:
73 """Convert zenith distance to altitude
75 Parameters
76 ----------
77 zd : `astropy.units.Quantity`
78 Zenith distance as an angle.
80 Returns
81 -------
82 alt : `astropy.units.Quantity`
83 Altitude.
84 """
85 return 90.0 * u.deg - zd
88def tracking_from_degree_headers(
89 self: MetadataTranslator,
90 radecsys: Sequence[str],
91 radecpairs: Tuple[Tuple[str, str], ...],
92 unit: astropy.units.Unit = u.deg,
93) -> SkyCoord:
94 """Calculate the tracking coordinates from lists of headers.
96 Parameters
97 ----------
98 radecsys : `list` or `tuple`
99 Header keywords to try corresponding to the tracking system. If none
100 match ICRS will be assumed.
101 radecpairs : `tuple` of `tuple` of pairs of `str`
102 Pairs of keywords specifying the RA/Dec in units of ``unit``.
103 unit : `astropy.unit.BaseUnit` or `tuple`
104 Unit definition suitable for the `~astropy.coordinate.SkyCoord`
105 constructor.
107 Returns
108 -------
109 radec = `astropy.coordinates.SkyCoord`
110 The RA/Dec coordinates. None if this is a moving target or a
111 non-science observation without any RA/Dec definition.
113 Raises
114 ------
115 KeyError
116 No RA/Dec keywords were found and this observation is a science
117 observation.
118 """
119 used = []
120 for k in radecsys:
121 if self.is_key_ok(k):
122 frame = self._header[k].strip().lower()
123 used.append(k)
124 if frame == "gappt":
125 self._used_these_cards(*used)
126 # Moving target
127 return None
128 break
129 else:
130 frame = "icrs"
131 for ra_key, dec_key in radecpairs:
132 if self.are_keys_ok([ra_key, dec_key]):
133 radec = SkyCoord(
134 self._header[ra_key],
135 self._header[dec_key],
136 frame=frame,
137 unit=unit,
138 obstime=self.to_datetime_begin(),
139 location=self.to_location(),
140 )
141 self._used_these_cards(ra_key, dec_key, *used)
142 return radec
143 if self.to_observation_type() == "science":
144 raise KeyError(f"{self._log_prefix}: Unable to determine tracking RA/Dec of science observation")
145 return None
148def altaz_from_degree_headers(
149 self: MetadataTranslator,
150 altazpairs: Tuple[Tuple[str, str], ...],
151 obstime: astropy.time.Time,
152 is_zd: Optional[Set[str]] = None,
153) -> AltAz:
154 """Calculate the altitude/azimuth coordinates from lists of headers.
156 If the altitude is found but is greater than 90 deg, it will be returned
157 fixed at 90 deg.
158 If the altitude or azimuth are negative and this is a calibration
159 observation, `None` will be returned.
161 Parameters
162 ----------
163 altazpairs : `tuple` of `str`
164 Pairs of keywords specifying Alt/Az in degrees. Each pair is tried
165 in turn.
166 obstime : `astropy.time.Time`
167 Reference time to use for these coordinates.
168 is_zd : `set`, optional
169 Contains keywords that correspond to zenith distances rather than
170 altitude.
172 Returns
173 -------
174 altaz = `astropy.coordinates.AltAz`
175 The AltAz coordinates associated with the telescope location
176 and provided time. Returns `None` if this observation is not
177 a science observation and no AltAz keys were located.
179 Raises
180 ------
181 KeyError
182 No AltAz keywords were found and this observation is a science
183 observation.
184 """
185 for alt_key, az_key in altazpairs:
186 if self.are_keys_ok([az_key, alt_key]):
187 az = self._header[az_key]
188 alt = self._header[alt_key]
190 # Check for zenith distance
191 if is_zd and alt_key in is_zd:
192 alt = altitude_from_zenith_distance(alt * u.deg).value
194 if az < -360.0 or alt < 0.0:
195 # Break out of loop since we have found values but
196 # they are bad.
197 break
198 if alt > 90.0:
199 log.warning("%s: Clipping altitude (%f) at 90 degrees", self._log_prefix, alt)
200 alt = 90.0
202 altaz = AltAz(az * u.deg, alt * u.deg, obstime=obstime, location=self.to_location())
203 self._used_these_cards(az_key, alt_key)
204 return altaz
205 if self.to_observation_type() == "science":
206 raise KeyError(f"{self._log_prefix}: Unable to determine AltAz of science observation")
207 return None