Coverage for python/astro_metadata_translator/translators/megaprime.py : 41%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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"""Metadata translation code for CFHT MegaPrime FITS headers"""
14__all__ = ("MegaPrimeTranslator", )
16import re
17import posixpath
18from astropy.coordinates import EarthLocation, Angle
19import astropy.units as u
21from ..translator import cache_translation, CORRECTIONS_RESOURCE_ROOT
22from .fits import FitsTranslator
23from .helpers import tracking_from_degree_headers, altaz_from_degree_headers
26class MegaPrimeTranslator(FitsTranslator):
27 """Metadata translator for CFHT MegaPrime standard headers.
28 """
30 name = "MegaPrime"
31 """Name of this translation class"""
33 supported_instrument = "MegaPrime"
34 """Supports the MegaPrime instrument."""
36 default_resource_root = posixpath.join(CORRECTIONS_RESOURCE_ROOT, "CFHT")
37 """Default resource path root to use to locate header correction files."""
39 # CFHT Megacam has no rotator, and the instrument angle on sky is set to
40 # +Y=N, +X=W which we define as a 0 degree rotation.
41 _const_map = {"boresight_rotation_angle": Angle(0*u.deg),
42 "boresight_rotation_coord": "sky",
43 "detector_group": None}
45 _trivial_map = {"physical_filter": "FILTER",
46 "dark_time": ("DARKTIME", dict(unit=u.s)),
47 "exposure_time": ("EXPTIME", dict(unit=u.s)),
48 "observation_id": "OBSID",
49 "object": "OBJECT",
50 "science_program": "RUNID",
51 "exposure_id": "EXPNUM",
52 "visit_id": "EXPNUM",
53 "detector_serial": "CCDNAME",
54 "relative_humidity": ["RELHUMID", "HUMIDITY"],
55 "temperature": (["TEMPERAT", "AIRTEMP"], dict(unit=u.deg_C)),
56 "boresight_airmass": ["AIRMASS", "BORE-AIRMASS"]}
58 @cache_translation
59 def to_datetime_begin(self):
60 # Docstring will be inherited. Property defined in properties.py
61 # We know it is UTC
62 value = self._from_fits_date_string(self._header["DATE-OBS"],
63 time_str=self._header["UTC-OBS"], scale="utc")
64 self._used_these_cards("DATE-OBS", "UTC-OBS")
65 return value
67 @cache_translation
68 def to_datetime_end(self):
69 # Docstring will be inherited. Property defined in properties.py
70 # Older files are missing UTCEND
71 if self.is_key_ok("UTCEND"):
72 # We know it is UTC
73 value = self._from_fits_date_string(self._header["DATE-OBS"],
74 time_str=self._header["UTCEND"], scale="utc")
75 self._used_these_cards("DATE-OBS", "UTCEND")
76 else:
77 # Take a guess by adding on the exposure time
78 value = self.to_datetime_begin() + self.to_exposure_time()
79 return value
81 @cache_translation
82 def to_location(self):
83 """Calculate the observatory location.
85 Returns
86 -------
87 location : `astropy.coordinates.EarthLocation`
88 An object representing the location of the telescope.
89 """
90 # Height is not in some MegaPrime files. Use the value from
91 # EarthLocation.of_site("CFHT")
92 # Some data uses OBS-LONG, OBS-LAT, other data uses LONGITUD and
93 # LATITUDE
94 for long_key, lat_key in (("LONGITUD", "LATITUDE"), ("OBS-LONG", "OBS-LAT")):
95 if self.are_keys_ok([long_key, lat_key]):
96 value = EarthLocation.from_geodetic(self._header[long_key], self._header[lat_key], 4215.0)
97 self._used_these_cards(long_key, lat_key)
98 break
99 else:
100 value = EarthLocation.of_site("CFHT")
101 return value
103 @cache_translation
104 def to_detector_name(self):
105 # Docstring will be inherited. Property defined in properties.py
106 if self.is_key_ok("EXTNAME"):
107 name = self._header["EXTNAME"]
108 # Only valid name has form "ccdNN"
109 if re.match(r"ccd\d+$", name):
110 self._used_these_cards("EXTNAME")
111 return name
113 # Dummy value, intended for PHU (need something to get filename)
114 return "ccd99"
116 @cache_translation
117 def to_detector_num(self):
118 name = self.to_detector_name()
119 return int(name[3:])
121 @cache_translation
122 def to_observation_type(self):
123 """Calculate the observation type.
125 Returns
126 -------
127 typ : `str`
128 Observation type. Normalized to standard set.
129 """
130 obstype = self._header["OBSTYPE"].strip().lower()
131 self._used_these_cards("OBSTYPE")
132 if obstype == "object":
133 return "science"
134 return obstype
136 @cache_translation
137 def to_tracking_radec(self):
138 """Calculate the tracking RA/Dec for this observation.
140 Currently will be `None` for geocentric apparent coordinates.
141 Additionally, can be `None` for non-science observations.
143 The method supports multiple versions of header defining tracking
144 coordinates.
146 Returns
147 -------
148 coords : `astropy.coordinates.SkyCoord`
149 The tracking coordinates.
150 """
151 radecsys = ("RADECSYS", "OBJRADEC", "RADESYS")
152 radecpairs = (("RA_DEG", "DEC_DEG"), ("BORE-RA", "BORE-DEC"))
153 return tracking_from_degree_headers(self, radecsys, radecpairs)
155 @cache_translation
156 def to_altaz_begin(self):
157 # Docstring will be inherited. Property defined in properties.py
158 return altaz_from_degree_headers(self, (("TELALT", "TELAZ"), ("BORE-ALT", "BORE-AZ")),
159 self.to_datetime_begin())
161 @cache_translation
162 def to_detector_exposure_id(self):
163 # Docstring will be inherited. Property defined in properties.py
164 return self.to_exposure_id() * 36 + self.to_detector_num()
166 @cache_translation
167 def to_pressure(self):
168 # Docstring will be inherited. Property defined in properties.py
169 # Can be either AIRPRESS in Pa or PRESSURE in mbar
170 for key, unit in (("PRESSURE", u.hPa), ("AIRPRESS", u.Pa)):
171 if self.is_key_ok(key):
172 return self.quantity_from_card(key, unit)
173 else:
174 raise KeyError(f"{self._log_prefix}: Could not find pressure keywords in header")
176 @cache_translation
177 def to_observation_counter(self):
178 """Return the lifetime exposure number.
180 Returns
181 -------
182 sequence : `int`
183 The observation counter.
184 """
185 return self.to_exposure_id()