Coverage for python/lsst/obs/lsst/translators/imsim.py: 46%
67 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 03:55 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 03:55 -0700
1# This file is currently part of obs_lsst but is written to allow it
2# to be migrated to the astro_metadata_translator package at a later date.
3#
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the LICENSE file in this directory for details of code ownership.
7#
8# Use of this source code is governed by a 3-clause BSD-style
9# license that can be found in the LICENSE file.
11"""Metadata translation code for LSSTCam imSim headers"""
13__all__ = ("LsstCamImSimTranslator", )
15import logging
16import astropy.units as u
17from astropy.coordinates import Angle, AltAz
18from astropy.time import TimeDelta
20try:
21 import erfa
22except ImportError:
23 import astropy._erfa as erfa
25from astro_metadata_translator import cache_translation
26from astro_metadata_translator.translators.helpers import tracking_from_degree_headers
28from .lsstsim import LsstSimTranslator
30log = logging.getLogger(__name__)
33class LsstCamImSimTranslator(LsstSimTranslator):
34 """Metadata translation class for LSSTCam imSim headers"""
36 name = "LSSTCam-imSim"
37 """Name of this translation class"""
39 _const_map = {
40 "instrument": "LSSTCam-imSim",
41 "boresight_rotation_coord": "sky",
42 "object": "UNKNOWN",
43 "pressure": None,
44 "temperature": None,
45 "relative_humidity": 40.0,
46 }
48 _trivial_map = {
49 "detector_group": "RAFTNAME",
50 "detector_name": "SENSNAME",
51 "observation_id": "OBSID",
52 "science_program": "RUNNUM",
53 "exposure_id": "OBSID",
54 "visit_id": "OBSID",
55 "dark_time": ("DARKTIME", dict(unit=u.s)),
56 "exposure_time": ("EXPTIME", dict(unit=u.s)),
57 "detector_serial": "LSST_NUM",
58 }
60 cameraPolicyFile = "policy/imsim.yaml"
62 _ROLLOVER_TIME = TimeDelta(0, scale="tai", format="sec")
63 """This instrument did not offset the observing day."""
65 @classmethod
66 def can_translate(cls, header, filename=None):
67 """Indicate whether this translation class can translate the
68 supplied header.
70 There is no ``INSTRUME`` header in ImSim data. Instead we use
71 the ``TESTTYPE`` header.
73 Parameters
74 ----------
75 header : `dict`-like
76 Header to convert to standardized form.
77 filename : `str`, optional
78 Name of file being translated.
80 Returns
81 -------
82 can : `bool`
83 `True` if the header is recognized by this class. `False`
84 otherwise.
85 """
86 return cls.can_translate_with_options(header, {"TESTTYPE": "IMSIM"},
87 filename=filename)
89 @cache_translation
90 def to_tracking_radec(self):
91 # Docstring will be inherited. Property defined in properties.py
92 radecsys = ("RADESYS",)
93 radecpairs = (("RATEL", "DECTEL"),)
94 return tracking_from_degree_headers(self, radecsys, radecpairs)
96 @cache_translation
97 def to_boresight_airmass(self):
98 # Docstring will be inherited. Property defined in properties.py
99 for key in ("AIRMASS", "AMSTART"):
100 if self.is_key_ok(key):
101 return self._header[key]
102 altaz = self.to_altaz_begin()
103 if altaz is not None:
104 return altaz.secz.to_value()
105 return None
107 @cache_translation
108 def to_boresight_rotation_angle(self):
109 angle = Angle(90.*u.deg) - Angle(self.quantity_from_card("ROTANGLE", u.deg))
110 angle = angle.wrap_at("360d")
111 return angle
113 @cache_translation
114 def to_physical_filter(self):
115 # Find throughputs version from imSim header data. For DC2
116 # data, we used throughputs version 1.4.
117 throughputs_version = None
118 for key, value in self._header.items():
119 if key.startswith("PKG") and value == "throughputs":
120 version_key = "VER" + key[len("PKG"):]
121 throughputs_version = self._header[version_key].strip()
122 break
123 if throughputs_version is None:
124 log.warning("%s: throughputs version not found. Using FILTER keyword value '%s'.",
125 self._log_prefix, self._header["FILTER"])
126 return self._header["FILTER"]
127 return "_".join((self._header["FILTER"], "sim", throughputs_version))
129 @cache_translation
130 def to_altaz_begin(self):
131 # Calculate from the hour angle if available
132 if self.to_observation_type() != "science":
133 return None
135 if not self.are_keys_ok(["HASTART", "DECTEL"]):
136 # Fallback to slow method
137 return super().to_altaz_begin()
139 location = self.to_location()
140 ha = Angle(self._header["HASTART"], unit=u.deg)
142 # For speed over accuracy, assume this is apparent Dec not ICRS
143 dec = Angle(self._header["DECTEL"], unit=u.deg)
145 # Use erfa directly
146 az, el = erfa.hd2ae(ha.radian, dec.radian, location.lat.radian)
148 return AltAz(az*u.radian, el*u.radian,
149 obstime=self.to_datetime_begin(), location=location)