Coverage for python/astro_metadata_translator/translators/suprimecam.py: 35%
Shortcuts 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
Shortcuts 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 # Map potential "unknown" values to standard form
109 if value in {"UNRECOGNIZED", "UNRECOGNISED", "NOTSET", "UNKNOWN"}:
110 value = "unknown"
111 elif value == "NONE":
112 value = "empty"
113 return value
115 @cache_translation
116 def to_datetime_begin(self):
117 # Docstring will be inherited. Property defined in properties.py
118 # We know it is UTC
119 value = self._from_fits_date_string(self._header["DATE-OBS"],
120 time_str=self._header["UT-STR"], scale="utc")
121 self._used_these_cards("DATE-OBS", "UT-STR")
122 return value
124 @cache_translation
125 def to_datetime_end(self):
126 # Docstring will be inherited. Property defined in properties.py
127 # We know it is UTC
128 value = self._from_fits_date_string(self._header["DATE-OBS"],
129 time_str=self._header["UT-END"], scale="utc")
130 self._used_these_cards("DATE-OBS", "UT-END")
132 # Sometimes the end time is less than the begin time plus the
133 # exposure time so we have to check for that.
134 exposure_time = self.to_exposure_time()
135 datetime_begin = self.to_datetime_begin()
136 exposure_end = datetime_begin + exposure_time
137 if value < exposure_end:
138 value = exposure_end
140 return value
142 @cache_translation
143 def to_exposure_id(self):
144 """Calculate unique exposure integer for this observation
146 Returns
147 -------
148 visit : `int`
149 Integer uniquely identifying this exposure.
150 """
151 exp_id = self._header["EXP-ID"].strip()
152 m = re.search(r"^SUP[A-Z](\d{7})0$", exp_id)
153 if not m:
154 raise RuntimeError(f"{self._log_prefix}: Unable to interpret EXP-ID: {exp_id}")
155 exposure = int(m.group(1))
156 if int(exposure) == 0:
157 # Don't believe it
158 frame_id = self._header["FRAMEID"].strip()
159 m = re.search(r"^SUP[A-Z](\d{7})\d{1}$", frame_id)
160 if not m:
161 raise RuntimeError(f"{self._log_prefix}: Unable to interpret FRAMEID: {frame_id}")
162 exposure = int(m.group(1))
163 self._used_these_cards("EXP-ID", "FRAMEID")
164 return exposure
166 @cache_translation
167 def to_visit_id(self):
168 """Calculate the unique integer ID for this visit.
170 Assumed to be identical to the exposure ID in this implementation.
172 Returns
173 -------
174 exp : `int`
175 Unique visit identifier.
176 """
177 return self.to_exposure_id()
179 @cache_translation
180 def to_observation_type(self):
181 """Calculate the observation type.
183 Returns
184 -------
185 typ : `str`
186 Observation type. Normalized to standard set.
187 """
188 obstype = self._header["DATA-TYP"].strip().lower()
189 self._used_these_cards("DATA-TYP")
190 if obstype == "object":
191 return "science"
192 return obstype
194 @cache_translation
195 def to_tracking_radec(self):
196 # Docstring will be inherited. Property defined in properties.py
197 radec = SkyCoord(self._header["RA2000"], self._header["DEC2000"],
198 frame="icrs", unit=(u.hourangle, u.deg),
199 obstime=self.to_datetime_begin(), location=self.to_location())
200 self._used_these_cards("RA2000", "DEC2000")
201 return radec
203 @cache_translation
204 def to_altaz_begin(self):
205 # Docstring will be inherited. Property defined in properties.py
206 return altaz_from_degree_headers(self, (("ALTITUDE", "AZIMUTH"),),
207 self.to_datetime_begin())
209 @cache_translation
210 def to_boresight_rotation_angle(self):
211 # Docstring will be inherited. Property defined in properties.py
212 angle = Angle(self.quantity_from_card("INR-STR", u.deg))
213 angle = angle.wrap_at("360d")
214 return angle
216 @cache_translation
217 def to_detector_exposure_id(self):
218 # Docstring will be inherited. Property defined in properties.py
219 return self.to_exposure_id() * 10 + self.to_detector_num()
221 @cache_translation
222 def to_detector_name(self):
223 # Docstring will be inherited. Property defined in properties.py
224 # See https://subarutelescope.org/Observing/Instruments/SCam/ccd.html
225 num = self.to_detector_num()
227 names = (
228 "nausicaa",
229 "kiki",
230 "fio",
231 "sophie",
232 "sheeta",
233 "satsuki",
234 "chihiro",
235 "clarisse",
236 "ponyo",
237 "san",
238 )
240 return names[num]