Coverage for python/astro_metadata_translator/translators/hsc.py : 30%

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 HSC FITS headers"""
14__all__ = ("HscTranslator", )
16import re
17import logging
18import posixpath
20import astropy.units as u
21from astropy.coordinates import Angle
23from ..translator import cache_translation, CORRECTIONS_RESOURCE_ROOT
24from .suprimecam import SuprimeCamTranslator
26log = logging.getLogger(__name__)
29class HscTranslator(SuprimeCamTranslator):
30 """Metadata translator for HSC standard headers.
31 """
33 name = "HSC"
34 """Name of this translation class"""
36 supported_instrument = "HSC"
37 """Supports the HSC instrument."""
39 default_resource_root = posixpath.join(CORRECTIONS_RESOURCE_ROOT, "HSC")
40 """Default resource path root to use to locate header correction files."""
42 _const_map = {"instrument": "HSC",
43 "boresight_rotation_coord": "sky"}
44 """Hard wire HSC even though modern headers call it Hyper Suprime-Cam"""
46 _trivial_map = {"detector_serial": "T_CCDSN",
47 }
48 """One-to-one mappings"""
50 # Zero point for HSC dates: 2012-01-01 51544 -> 2000-01-01
51 _DAY0 = 55927
53 # CCD index mapping for commissioning run 2
54 _CCD_MAP_COMMISSIONING_2 = {112: 106,
55 107: 105,
56 113: 107,
57 115: 109,
58 108: 110,
59 114: 108,
60 }
62 _DETECTOR_NUM_TO_UNIQUE_NAME = [
63 '1_53',
64 '1_54',
65 '1_55',
66 '1_56',
67 '1_42',
68 '1_43',
69 '1_44',
70 '1_45',
71 '1_46',
72 '1_47',
73 '1_36',
74 '1_37',
75 '1_38',
76 '1_39',
77 '1_40',
78 '1_41',
79 '0_30',
80 '0_29',
81 '0_28',
82 '1_32',
83 '1_33',
84 '1_34',
85 '0_27',
86 '0_26',
87 '0_25',
88 '0_24',
89 '1_00',
90 '1_01',
91 '1_02',
92 '1_03',
93 '0_23',
94 '0_22',
95 '0_21',
96 '0_20',
97 '1_04',
98 '1_05',
99 '1_06',
100 '1_07',
101 '0_19',
102 '0_18',
103 '0_17',
104 '0_16',
105 '1_08',
106 '1_09',
107 '1_10',
108 '1_11',
109 '0_15',
110 '0_14',
111 '0_13',
112 '0_12',
113 '1_12',
114 '1_13',
115 '1_14',
116 '1_15',
117 '0_11',
118 '0_10',
119 '0_09',
120 '0_08',
121 '1_16',
122 '1_17',
123 '1_18',
124 '1_19',
125 '0_07',
126 '0_06',
127 '0_05',
128 '0_04',
129 '1_20',
130 '1_21',
131 '1_22',
132 '1_23',
133 '0_03',
134 '0_02',
135 '0_01',
136 '0_00',
137 '1_24',
138 '1_25',
139 '1_26',
140 '1_27',
141 '0_34',
142 '0_33',
143 '0_32',
144 '1_28',
145 '1_29',
146 '1_30',
147 '0_41',
148 '0_40',
149 '0_39',
150 '0_38',
151 '0_37',
152 '0_36',
153 '0_47',
154 '0_46',
155 '0_45',
156 '0_44',
157 '0_43',
158 '0_42',
159 '0_56',
160 '0_55',
161 '0_54',
162 '0_53',
163 '0_31',
164 '1_35',
165 '0_35',
166 '1_31',
167 '1_48',
168 '1_51',
169 '1_52',
170 '1_57',
171 '0_57',
172 '0_52',
173 '0_51',
174 '0_48',
175 ]
177 @classmethod
178 def can_translate(cls, header, filename=None):
179 """Indicate whether this translation class can translate the
180 supplied header.
182 There is no ``INSTRUME`` header in early HSC files, so this method
183 looks for HSC mentions in other headers. In more recent files the
184 instrument is called "Hyper Suprime-Cam".
186 Parameters
187 ----------
188 header : `dict`-like
189 Header to convert to standardized form.
190 filename : `str`, optional
191 Name of file being translated.
193 Returns
194 -------
195 can : `bool`
196 `True` if the header is recognized by this class. `False`
197 otherwise.
198 """
199 if "INSTRUME" in header:
200 return header["INSTRUME"] == "Hyper Suprime-Cam"
202 for k in ("EXP-ID", "FRAMEID"):
203 if cls.is_keyword_defined(header, k):
204 if header[k].startswith("HSC"):
205 return True
206 return False
208 @cache_translation
209 def to_exposure_id(self):
210 """Calculate unique exposure integer for this observation
212 Returns
213 -------
214 visit : `int`
215 Integer uniquely identifying this exposure.
216 """
217 exp_id = self._header["EXP-ID"].strip()
218 m = re.search(r"^HSCE(\d{8})$", exp_id) # 2016-06-14 and new scheme
219 if m:
220 self._used_these_cards("EXP-ID")
221 return int(m.group(1))
223 # Fallback to old scheme
224 m = re.search(r"^HSC([A-Z])(\d{6})00$", exp_id)
225 if not m:
226 raise RuntimeError(f"{self._log_prefix}: Unable to interpret EXP-ID: {exp_id}")
227 letter, visit = m.groups()
228 visit = int(visit)
229 if visit == 0:
230 # Don't believe it
231 frame_id = self._header["FRAMEID"].strip()
232 m = re.search(r"^HSC([A-Z])(\d{6})\d{2}$", frame_id)
233 if not m:
234 raise RuntimeError(f"{self._log_prefix}: Unable to interpret FRAMEID: {frame_id}")
235 letter, visit = m.groups()
236 visit = int(visit)
237 if visit % 2: # Odd?
238 visit -= 1
239 self._used_these_cards("EXP-ID", "FRAMEID")
240 return visit + 1000000*(ord(letter) - ord("A"))
242 @cache_translation
243 def to_boresight_rotation_angle(self):
244 # Docstring will be inherited. Property defined in properties.py
245 # Rotation angle formula determined empirically from visual inspection
246 # of HSC images. See DM-9111.
247 angle = Angle(270.*u.deg) - Angle(self.quantity_from_card("INST-PA", u.deg))
248 angle = angle.wrap_at("360d")
249 return angle
251 @cache_translation
252 def to_detector_num(self):
253 """Calculate the detector number.
255 Focus CCDs were numbered incorrectly in the readout software during
256 commissioning run 2. This method maps to the correct ones.
258 Returns
259 -------
260 num : `int`
261 Detector number.
262 """
264 ccd = super().to_detector_num()
265 try:
266 tjd = self._get_adjusted_mjd()
267 except Exception:
268 return ccd
270 if tjd > 390 and tjd < 405:
271 ccd = self._CCD_MAP_COMMISSIONING_2.get(ccd, ccd)
273 return ccd
275 @cache_translation
276 def to_detector_exposure_id(self):
277 # Docstring will be inherited. Property defined in properties.py
278 return self.to_exposure_id() * 200 + self.to_detector_num()
280 @cache_translation
281 def to_detector_group(self):
282 # Docstring will be inherited. Property defined in properties.py
283 unique = self.to_detector_unique_name()
284 return unique.split("_")[0]
286 @cache_translation
287 def to_detector_unique_name(self):
288 # Docstring will be inherited. Property defined in properties.py
289 # Mapping from number to unique name is defined solely in camera
290 # geom files.
291 # There is no header for it.
292 num = self.to_detector_num()
293 return self._DETECTOR_NUM_TO_UNIQUE_NAME[num]
295 @cache_translation
296 def to_detector_name(self):
297 # Docstring will be inherited. Property defined in properties.py
298 # Name is defined from unique name
299 unique = self.to_detector_unique_name()
300 return unique.split("_")[1]