Coverage for python/astro_metadata_translator/translators/suprimecam.py : 31%

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 SuprimeCam FITS headers"""
14__all__ = ("SuprimeCamTranslator", )
16import re
17import logging
18import posixpath
20import astropy.units as u
21from astropy.coordinates import SkyCoord, Angle
23from ..translator import cache_translation, CORRECTIONS_RESOURCE_ROOT
24from .subaru import SubaruTranslator
25from .helpers import altaz_from_degree_headers
27log = logging.getLogger(__name__)
30class SuprimeCamTranslator(SubaruTranslator):
31 """Metadata translator for HSC standard headers.
32 """
34 name = "SuprimeCam"
35 """Name of this translation class"""
37 supported_instrument = "SuprimeCam"
38 """Supports the SuprimeCam instrument."""
40 default_resource_root = posixpath.join(CORRECTIONS_RESOURCE_ROOT, "SuprimeCam")
41 """Default resource path root to use to locate header correction files."""
43 _const_map = {"boresight_rotation_coord": "unknown",
44 "detector_group": None}
45 """Constant mappings"""
47 _trivial_map = {"observation_id": "EXP-ID",
48 "object": "OBJECT",
49 "science_program": "PROP-ID",
50 "detector_num": "DET-ID",
51 "detector_serial": "DETECTOR", # DETECTOR is the "call name"
52 "boresight_airmass": "AIRMASS",
53 "relative_humidity": "OUT-HUM",
54 "temperature": ("OUT-TMP", dict(unit=u.K)),
55 "pressure": ("OUT-PRS", dict(unit=u.hPa)),
56 "exposure_time": ("EXPTIME", dict(unit=u.s)),
57 "dark_time": ("EXPTIME", dict(unit=u.s)), # Assume same as exposure time
58 }
59 """One-to-one mappings"""
61 # Zero point for SuprimeCam dates: 2004-01-01
62 _DAY0 = 53005
64 @classmethod
65 def can_translate(cls, header, filename=None):
66 """Indicate whether this translation class can translate the
67 supplied header.
69 Parameters
70 ----------
71 header : `dict`-like
72 Header to convert to standardized form.
73 filename : `str`, optional
74 Name of file being translated.
76 Returns
77 -------
78 can : `bool`
79 `True` if the header is recognized by this class. `False`
80 otherwise.
81 """
82 if "INSTRUME" in header:
83 return header["INSTRUME"] == "SuprimeCam"
85 for k in ("EXP-ID", "FRAMEID"):
86 if cls.is_keyword_defined(header, k):
87 if header[k].startswith("SUP"):
88 return True
89 return False
91 def _get_adjusted_mjd(self):
92 """Calculate the modified julian date offset from reference day
94 Returns
95 -------
96 offset : `int`
97 Offset day count from reference day.
98 """
99 mjd = self._header["MJD"]
100 self._used_these_cards("MJD")
101 return int(mjd) - self._DAY0
103 @cache_translation
104 def to_physical_filter(self):
105 # Docstring will be inherited. Property defined in properties.py
106 value = self._header["FILTER01"].strip().upper()
107 self._used_these_cards("FILTER01")
108 return value
110 @cache_translation
111 def to_datetime_begin(self):
112 # Docstring will be inherited. Property defined in properties.py
113 # We know it is UTC
114 value = self._from_fits_date_string(self._header["DATE-OBS"],
115 time_str=self._header["UT-STR"], scale="utc")
116 self._used_these_cards("DATE-OBS", "UT-STR")
117 return value
119 @cache_translation
120 def to_datetime_end(self):
121 # Docstring will be inherited. Property defined in properties.py
122 # We know it is UTC
123 value = self._from_fits_date_string(self._header["DATE-OBS"],
124 time_str=self._header["UT-END"], scale="utc")
125 self._used_these_cards("DATE-OBS", "UT-END")
127 # Sometimes the end time is less than the begin time plus the
128 # exposure time so we have to check for that.
129 exposure_time = self.to_exposure_time()
130 datetime_begin = self.to_datetime_begin()
131 exposure_end = datetime_begin + exposure_time
132 if value < exposure_end:
133 value = exposure_end
135 return value
137 @cache_translation
138 def to_exposure_id(self):
139 """Calculate unique exposure integer for this observation
141 Returns
142 -------
143 visit : `int`
144 Integer uniquely identifying this exposure.
145 """
146 exp_id = self._header["EXP-ID"].strip()
147 m = re.search(r"^SUP[A-Z](\d{7})0$", exp_id)
148 if not m:
149 raise RuntimeError(f"{self._log_prefix}: Unable to interpret EXP-ID: {exp_id}")
150 exposure = int(m.group(1))
151 if int(exposure) == 0:
152 # Don't believe it
153 frame_id = self._header["FRAMEID"].strip()
154 m = re.search(r"^SUP[A-Z](\d{7})\d{1}$", frame_id)
155 if not m:
156 raise RuntimeError(f"{self._log_prefix}: Unable to interpret FRAMEID: {frame_id}")
157 exposure = int(m.group(1))
158 self._used_these_cards("EXP-ID", "FRAMEID")
159 return exposure
161 @cache_translation
162 def to_visit_id(self):
163 """Calculate the unique integer ID for this visit.
165 Assumed to be identical to the exposure ID in this implementation.
167 Returns
168 -------
169 exp : `int`
170 Unique visit identifier.
171 """
172 return self.to_exposure_id()
174 @cache_translation
175 def to_observation_type(self):
176 """Calculate the observation type.
178 Returns
179 -------
180 typ : `str`
181 Observation type. Normalized to standard set.
182 """
183 obstype = self._header["DATA-TYP"].strip().lower()
184 self._used_these_cards("DATA-TYP")
185 if obstype == "object":
186 return "science"
187 return obstype
189 @cache_translation
190 def to_tracking_radec(self):
191 # Docstring will be inherited. Property defined in properties.py
192 radec = SkyCoord(self._header["RA2000"], self._header["DEC2000"],
193 frame="icrs", unit=(u.hourangle, u.deg),
194 obstime=self.to_datetime_begin(), location=self.to_location())
195 self._used_these_cards("RA2000", "DEC2000")
196 return radec
198 @cache_translation
199 def to_altaz_begin(self):
200 # Docstring will be inherited. Property defined in properties.py
201 return altaz_from_degree_headers(self, (("ALTITUDE", "AZIMUTH"),),
202 self.to_datetime_begin())
204 @cache_translation
205 def to_boresight_rotation_angle(self):
206 # Docstring will be inherited. Property defined in properties.py
207 angle = Angle(self.quantity_from_card("INR-STR", u.deg))
208 angle = angle.wrap_at("360d")
209 return angle
211 @cache_translation
212 def to_detector_exposure_id(self):
213 # Docstring will be inherited. Property defined in properties.py
214 return self.to_exposure_id() * 10 + self.to_detector_num()
216 @cache_translation
217 def to_detector_name(self):
218 # Docstring will be inherited. Property defined in properties.py
219 # See https://subarutelescope.org/Observing/Instruments/SCam/ccd.html
220 num = self.to_detector_num()
222 names = (
223 "nausicaa",
224 "kiki",
225 "fio",
226 "sophie",
227 "sheeta",
228 "satsuki",
229 "chihiro",
230 "clarisse",
231 "ponyo",
232 "san",
233 )
235 return names[num]