Coverage for python/lsst/obs/lsst/translators/lsstCam.py: 37%

53 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-17 11:52 +0000

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. 

10 

11"""Metadata translation code for the main LSST Camera""" 

12 

13__all__ = ("LsstCamTranslator", ) 

14 

15import logging 

16import astropy.units as u 

17 

18from astro_metadata_translator import cache_translation 

19from astro_metadata_translator.translators.helpers import is_non_science 

20 

21from .lsst import LsstBaseTranslator, SIMONYI_TELESCOPE, FILTER_DELIMITER 

22 

23log = logging.getLogger(__name__) 

24 

25# Normalized name of the LSST Camera 

26LSST_CAM = "LSSTCam" 

27 

28 

29def is_non_science_or_lab(self): 

30 """Pseudo method to determine whether this is a lab or non-science 

31 header. 

32 

33 Raises 

34 ------ 

35 KeyError 

36 If this is a science observation and on the mountain. 

37 """ 

38 if is_non_science(self): 

39 return 

40 if not self._is_on_mountain(): 

41 return 

42 raise KeyError(f"{self._log_prefix}: Required key is missing and this is a mountain science observation") 

43 

44 

45class LsstCamTranslator(LsstBaseTranslator): 

46 """Metadata translation for the main LSST Camera.""" 

47 

48 name = LSST_CAM 

49 """Name of this translation class""" 

50 

51 supported_instrument = LSST_CAM 

52 """Supports the lsstCam instrument.""" 

53 

54 _const_map = { 

55 "instrument": LSST_CAM, 

56 "telescope": SIMONYI_TELESCOPE, 

57 # Migrate these to full translations once test data appears that 

58 # includes them 

59 "altaz_begin": None, 

60 "object": "UNKNOWN", 

61 "relative_humidity": None, 

62 "temperature": None, 

63 "pressure": None, 

64 } 

65 

66 _trivial_map = { 

67 "detector_group": "RAFTBAY", 

68 "detector_name": "CCDSLOT", 

69 "observation_id": "OBSID", 

70 "exposure_time": ("EXPTIME", dict(unit=u.s)), 

71 "detector_serial": "LSST_NUM", 

72 "science_program": (["PROGRAM", "RUNNUM"], dict(default="unknown")), 

73 "boresight_rotation_angle": (["ROTPA", "ROTANGLE"], dict(checker=is_non_science_or_lab, 

74 default=float("nan"), unit=u.deg)), 

75 } 

76 

77 # Use Imsim raft definitions until a true lsstCam definition exists 

78 cameraPolicyFile = "policy/lsstCam.yaml" 

79 

80 @classmethod 

81 def fix_header(cls, header, instrument, obsid, filename=None): 

82 """Fix LSSTCam headers. 

83 

84 Notes 

85 ----- 

86 See `~astro_metadata_translator.fix_header` for details of the general 

87 process. 

88 """ 

89 

90 modified = False 

91 

92 # Calculate the standard label to use for log messages 

93 log_label = cls._construct_log_prefix(obsid, filename) 

94 

95 if "FILTER" not in header and header.get("FILTER2") is not None: 

96 ccdslot = header.get("CCDSLOT", "unknown") 

97 raftbay = header.get("RAFTBAY", "unknown") 

98 

99 log.warning("%s %s_%s: No FILTER key found but FILTER2=\"%s\" (removed)", 

100 log_label, raftbay, ccdslot, header["FILTER2"]) 

101 header["FILTER2"] = None 

102 modified = True 

103 

104 return modified 

105 

106 @classmethod 

107 def can_translate(cls, header, filename=None): 

108 """Indicate whether this translation class can translate the 

109 supplied header. 

110 

111 Parameters 

112 ---------- 

113 header : `dict`-like 

114 Header to convert to standardized form. 

115 filename : `str`, optional 

116 Name of file being translated. 

117 

118 Returns 

119 ------- 

120 can : `bool` 

121 `True` if the header is recognized by this class. `False` 

122 otherwise. 

123 """ 

124 # INSTRUME keyword might be of two types 

125 if "INSTRUME" in header: 

126 instrume = header["INSTRUME"].lower() 

127 if instrume == cls.supported_instrument.lower(): 

128 return True 

129 return False 

130 

131 @cache_translation 

132 def to_physical_filter(self): 

133 """Calculate the physical filter name. 

134 

135 Returns 

136 ------- 

137 filter : `str` 

138 Name of filter. Can be a combination of FILTER and FILTER2 

139 headers joined by a "~" if FILTER2 is set and not empty. 

140 Returns "UNKNOWN" if no filter is declared. 

141 """ 

142 physical_filter = self._determine_primary_filter() 

143 

144 filter2 = None 

145 if self.is_key_ok("FILTER2"): 

146 self._used_these_cards("FILTER2") 

147 filter2 = self._header["FILTER2"] 

148 if self._is_filter_empty(filter2): 

149 filter2 = None 

150 

151 if filter2: 

152 physical_filter = f"{physical_filter}{FILTER_DELIMITER}{filter2}" 

153 

154 return physical_filter